更新到ABP v3.0.0后,我在向存储库运行并行请求时开始获得DbContext disposed
异常,这样创建新的UnitOfWork
如下:
using (var uow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew))
我查看了源代码并在AsyncLocalCurrentUnitOfWorkProvider
中找到了一些我不太了解的代码。设置当前uow
时,它在包装器中设置属性:
private static readonly AsyncLocal<LocalUowWrapper> AsyncLocalUow = new AsyncLocal<LocalUowWrapper>();
private static void SetCurrentUow(IUnitOfWork value)
{
lock (AsyncLocalUow)
{
if (value == null)
{
if (AsyncLocalUow.Value == null)
{
return;
}
if (AsyncLocalUow.Value.UnitOfWork?.Outer == null)
{
AsyncLocalUow.Value.UnitOfWork = null;
AsyncLocalUow.Value = null;
return;
}
AsyncLocalUow.Value.UnitOfWork = AsyncLocalUow.Value.UnitOfWork.Outer;
}
else
{
if (AsyncLocalUow.Value?.UnitOfWork == null)
{
if (AsyncLocalUow.Value != null)
{
AsyncLocalUow.Value.UnitOfWork = value;
}
AsyncLocalUow.Value = new LocalUowWrapper(value);
return;
}
value.Outer = AsyncLocalUow.Value.UnitOfWork;
AsyncLocalUow.Value.UnitOfWork = value;
}
}
}
private class LocalUowWrapper
{
public IUnitOfWork UnitOfWork { get; set; }
public LocalUowWrapper(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
}
这看起来不是线程安全的,因为任何线程都可以设置新的UnitOfWork
然后处置它。
这是一个错误吗?我可以使用我自己的ICurrentUnitOfWorkProvider
实现,而不包装,但我不确定这是否正确。
我无法提供DbContext disposed
例外的示例,但null reference
方法中有一个repository.GetAll()
例外。我认为它有同样的原因。
namespace TestParallelEFRequest
{
public class Ent1 : Entity<int> { public string Name { get; set; } }
public class Ent2 : Entity<int> { public string Name { get; set; } }
public class Ent3 : Entity<int> { public string Name { get; set; } }
public class Ent4 : Entity<int> { public string Name { get; set; } }
public class DomainStartEvent : EventData {}
public class DBContext : AbpDbContext {
public DBContext(): base("Default") {}
public IDbSet<Ent1> Ent1 { get; set; }
public IDbSet<Ent2> Ent2 { get; set; }
public IDbSet<Ent3> Ent3 { get; set; }
public IDbSet<Ent4> Ent4 { get; set; }
}
public class TestService : DomainService, IEventHandler<DomainStartEvent>
{
private readonly IRepository<Ent1> _rep1;
private readonly IRepository<Ent2> _rep2;
private readonly IRepository<Ent3> _rep3;
private readonly IRepository<Ent4> _rep4;
public TestService(IRepository<Ent1> rep1, IRepository<Ent2> rep2, IRepository<Ent3> rep3, IRepository<Ent4> rep4) {
_rep1 = rep1;_rep2 = rep2;_rep3 = rep3;_rep4 = rep4;
}
Task HandleEntityes<T>(IRepository<T> rep, int i) where T : class, IEntity<int> {
return Task.Run(() => {
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.RequiresNew)) {
Thread.Sleep(i); // Simulating work
rep.GetAll(); // <- Exception here
}
});
}
public void HandleEvent(DomainStartEvent eventData) {
using (var uow = UnitOfWorkManager.Begin(TransactionScopeOption.RequiresNew)) {
var t1 = HandleEntityes(_rep1, 10);
var t2 = HandleEntityes(_rep2, 10);
var t3 = HandleEntityes(_rep3, 10);
var t4 = HandleEntityes(_rep4, 1000);
Task.WaitAll(t1, t2, t3, t4);
}
}
}
[DependsOn(typeof(AbpEntityFrameworkModule))]
public class ProgrammModule : AbpModule
{
public override void PreInitialize() { Configuration.DefaultNameOrConnectionString = "Default"; }
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
Database.SetInitializer<DBContext>(null);
}
}
class Program {
static void Main(string[] args) {
using (var bootstrapper = AbpBootstrapper.Create<ProgrammModule>()) {
bootstrapper.Initialize();
bootstrapper.IocManager.Resolve<IEventBus>().Trigger(new DomainStartEvent());
Console.ReadKey();
}
}
}
}