特别说明:如果 Issue 报告为问题且开发成员回复确认问题之后但三天内都不能得到反馈,则视为无效Issue。
我通过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(Action
2 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);
});
}
}
我的需求是,当用户批量导入数据后,快速提交并相应成功,通过异步事件方法GenerateSkuT去执行其他多余的任务(这个任务可能很繁琐)。
另外针对单个操作A方法,我用UnitOfWork, 我希望在数据提交后再发布事件去执行其他的操作,除了手动提交外有没有其他办法
收到,您作用域没搞好
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
我看明白了你的问题了!
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)等都要创建作用域并设置共享。
感谢百小僧, 通过你的解答配合源码我看明白了,既然事服务提供对象的原因的话,在多线程中使用ThreadLocal, 把 Scoped.Create中新生成的IServiceProvider 存储起来,等待Insert/sqlnonquery使用的时候再通过 ThreadLocal取出来,那么对于用户使用起来就无差别了。
`
登录 后才可以发表评论