6.1K Star 11.8K Fork 4K

GVPdotNET China / Furion

 / 详情

Scoped.Create 里执行sql .SqlNonQuery()或者 obj.insert() 问题

已完成
创建于  
2021-06-20 11:35

特别说明:如果 Issue 报告为问题且开发成员回复确认问题之后但三天内都不能得到反馈,则视为无效Issue。

Furion 版本号

2.9.3

Web 项目类型

  • WebApi
  • Mvc
  • Razor Pages
  • Blazor Server

描述你的问题

我通过messageCenter发布一个事件后,执行了一个方法,方法执行如下方法,其中skus(是实体类的Sku的数组)是事件总线发布的参数

        Scoped.Create((_, scope) =>
        {                
            var services = scope.ServiceProvider;

            var service = (ISkuService)services.GetService(typeof(ISkuService));
            service.GenerateSkuT(skus);
        });

然后再genergeSkuT里边执行了

通过sku数组根据一定的规则生成skuts(实体类SkuT)数组.
最后执行 (1)skuts.ForEach(r=>r.Insert()); (2) sql.SqlNonQuery()//sql是某个语句
然后提交保存 rep.Context.SaveChanges();
如上执行 如果只执行1,则系统不报错但是数据库没有插入,如果执行了1和2,则2报错如下堆栈

然后通过改变方法。(1) skuts.ForEach(r => { rep.Insert(r); }); (2)rep.Context.Database.ExecuteNonQuery(sql); 然后提交保存 rep.Context.SaveChanges();则提交成功

根据错误提示,和变通代码的成功,猜测可能是数据库上下文串线程和作用域了,可能是主线程里的context执行完被disposed,影响到了另外一个线程的context也被释放了,是不是可以考虑用ThreadLocal线程安全来管理数据库上下文。另外使用Scoped.CreateUow同样会出现别的错误。


异常堆栈信息

Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'DefaultDbContext'.
at MySqlConnector.Core.ServerSession.StartQuerying(ICancellableCommand command) in //src/MySqlConnector/Core/ServerSession.cs:line 290
at MySqlConnector.Core.CommandExecutor.d__0.MoveNext() in /
/src/MySqlConnector/Core/CommandExecutor.cs:line 56
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at MySqlConnector.MySqlCommand.d__69.MoveNext() in /_/src/MySqlConnector/MySqlCommand.cs:line 266
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at MySqlConnector.MySqlCommand.ExecuteNonQuery() in /_/src/MySqlConnector/MySqlCommand.cs:line 101 at StackExchange.Profiling.Data.ProfiledDbCommand.ExecuteNonQuery() in C:\projects\dotnet\src\MiniProfiler.Shared\Data\ProfiledDbCommand.cs:line 288 at Furion.DatabaseAccessor.SqlAdoNetExtensions.ExecuteNonQuery(DatabaseFacade databaseFacade, String sql, DbParameter[] parameters, CommandType commandType) at Furion.DatabaseAccessor.PrivateSqlRepository.SqlNonQuery(String sql, DbParameter[] parameters) at Furion.DatabaseAccessor.SqlStringExecutePart.SqlNonQuery(DbParameter[] parameters) at Furion.DatabaseAccessor.Extensions.SqlStringExecuteExtensions.SqlNonQuery(String sql, DbParameter[] parameters) at LL.Refound.Core.Service.SkuService.GenerateSkuT(Object skuso) inService\Sku\SkuService.cs:line 279 at LL.Refound.Core.Service.SkuChangeSubscribeHandler.<>c__DisplayClass3_0.<DealSku>b__0(IServiceScopeFactory _, IServiceScope scope) in xxxx\Service\Sku\Event\SkuChangeSubscribeHandler.cs:line 60 at Furion.DependencyInjection.Scoped.Create(Action2 handler, IServiceScopeFactory scopeFactory)
at LL.Refound.Core.Service.SkuChangeSubscribeHandler.DealSku(Sku[] skus) in xxxx\Service\Sku\Event\SkuChangeSubscribeHandler.cs:line 52
at LL.Refound.Core.Service.SkuChangeSubscribeHandler.CreateSku(String eventId, Object payload) in xxxx\Service\Sku\Event\SkuChangeSubscribeHandler.cs:line 39
at Furion.EventBus.InternalMessageCenter.<>c__DisplayClass7_1.<b__0>d.MoveNext()


代码或代码仓库

//SkuService:ISkuService
public class SkuService : BaseController, ISkuService, ITransient
    {
        public IRepository<Sku> _skuRep;  // 菜单表仓储  
      public   SkuService (){
        
            //注入rep
    }

        [HttpPost("batchCreate")]
        public async Task batchCreate()
        {
              List<Sku> skus = new List<Sku>();
            for (var i = 0; i < 3; i++)
            {
                var Sku = new Sku() { Id = 3 + i, Cn = "测试" + i, En = "1" };
                Sku.Insert();
                skus.Add(Sku);
            }
            _skuRep.Context.SaveChanges();

            Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
            MessageCenter.Send("create:Sku", skus.ToArray());
        }

         [NonAction]
        public void GenerateSkuT(Sku[] skus)
        {
            Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
            for (var i = 0; i < 2; i++)
            {
                var Sku = new SkuT() { Id = i, Cn = "SKUT测试" + i, En = "1" };
                _skuRepository.Context.Add<SkuT>(Sku);
                //Sku.Insert();

            }


            string sql = "update sku set Cn ='sdf' where id =3";

            sql.SqlNonQuery();
            _skuRepository.Context.SaveChanges();

        }
}
   //事件
 public class SkuChangeSubscribeHandler : ISubscribeHandler
    {

        // 定义一条消息
        [SubscribeMessage("create:Sku")]
        public void CreateSku(string eventId, object payload)
        {
            Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
            DealSku((Sku[])payload);


        }

        public void DealSku(Sku[] skus)
        {


            Scoped.Create((_, scope) =>
            {
                Debug.WriteLine(Thread.CurrentThread.ManagedThreadId.ToString());
                var services = scope.ServiceProvider;

                var service = (TestService)services.GetService(typeof(TestService));
                service.GenerateSkuT(skus);
            });

        }
    }


数据库信息

  • Sqlite
  • SqlServer
  • Mysql
  • Oracle
  • PGSql
  • Firebird
  • Cosmos

期待结果

我的需求是,当用户批量导入数据后,快速提交并相应成功,通过异步事件方法GenerateSkuT去执行其他多余的任务(这个任务可能很繁琐)。
另外针对单个操作A方法,我用UnitOfWork, 我希望在数据提交后再发布事件去执行其他的操作,除了手动提交外有没有其他办法


评论 (14)

yibey 创建了任务
yibey 关联仓库设置为dotNET China/Furion
展开全部操作日志

收到,您作用域没搞好

作用域怎么处理

我现在在外面,今天父亲节,回来给你答复(今天)

好的

yibey 修改了描述

我看明白了你的问题了!

1、sql 拓展方法实际上是有自己的作用域的,您这里是通过多线程去调用这个方法,但是 sql 拓展方法是自己维护一个作用域,你这里应该改为 共享 作用域:

string sql = "update sku set Cn ='sdf' where id =3";
sql.SetContextScoped(_skuRepository.ServiceProvider).SqlNonQuery();
_skuRepository.Context.SaveChanges();

这样即可。

所有字符串拓展都有设置 Scoped 作用域的方法,如:.SetContextScoped(serviceProvider)

所有的 字符串 拓展方法都有 独立 的作用域,但是都提供了 .SetXXXScoped(serviceProvider) 共享上下文作用域方法,这里是:.SetContextScoped(xxx)

底层源码:输入图片说明

输入图片说明

还有最简单的代码,通过仓储执行:

_skuRepository.SqlNonQuery("update sku set Cn ='sdf' where id =3");

这样不用字符串拓展方法就没问题了。

反正只要用 字符串实体 或任何拓展方法,都有一个 SetXXXScoped 作用域拓展方法,这样就可以把上下文的作用域共享了,同一个生命周期,不然它们只会维护各自生命周期。比如:

obj.SetContextScoped(services).Insert();
"sql".SetContextScoped(services).SqlNonQuery();

如果在 Web 环境中,无需设置,但是多线程操作,或者非web环境,比如(时间总线,定时任务,worker services)等都要创建作用域并设置共享。

百小僧 任务状态待办的 修改为进行中
百小僧 任务状态进行中 修改为已完成
百小僧 负责人设置为百小僧
百小僧 添加了
 
建议
标签
百小僧 移除了
 
建议
标签
百小僧 添加了
 
疑问
标签
百小僧 里程碑设置为Furion 2021
百小僧 关联分支设置为master
百小僧 计划截止日期设置为2021-06-21
百小僧 计划开始日期设置为2021-06-20
百小僧 计划截止日期2021-06-21 修改为2021-06-20

感谢百小僧, 通过你的解答配合源码我看明白了,既然事服务提供对象的原因的话,在多线程中使用ThreadLocal, 把 Scoped.Create中新生成的IServiceProvider 存储起来,等待Insert/sqlnonquery使用的时候再通过 ThreadLocal取出来,那么对于用户使用起来就无差别了。

`

这个可行么

@yibey 你这个想法不错,但是有可能会导致内存积压和溢出,实际上通过这种共享的模式,可以把内存降到最低,而且还能得到准确的内存释放。

百小僧 关联分支master 修改为未关联

登录 后才可以发表评论

状态
负责人
里程碑
Pull Requests
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
开始日期   -   截止日期
-
置顶选项
优先级
参与者(2)
974299 monksoul 1578937227
C#
1
https://gitee.com/dotnetchina/Furion.git
git@gitee.com:dotnetchina/Furion.git
dotnetchina
Furion
Furion

搜索帮助