问题描述:
异常场景: 在应用层或者领域层的某个方法 MethodA() 对 A 表进行了增删改查操作,同时在 领域事件EventHanlderA 中绑定了A表的增删改查对应的领域事件,如果在MethodA方法和领域事件中用到了一个相同的领域服务DomainServiceA,这种场景下MethodA中的方法可以正常执行,但是领域事件中就会因为DomainServiceA已被释放而提示异常:具体原因和ABP的架构有关系,有兴趣的朋友可以去看看源代码!
异常内容: Cannot access a disposed object ObjectDisposedException: Cannot access a disposed object. A common cause of this error is disposing a context 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, or wrapping the context 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: 'NetEarnMultiTenancyDbContext'. STACK TRACE: at Microsoft.EntityFrameworkCore.DbContext.CheckDisposed() at Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies() at Microsoft.EntityFrameworkCore.DbContext.get_Model() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityType() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.CheckState() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.get_EntityQueryable() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.System.Linq.IQueryable.get_Provider() at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.Include[TEntity,TProperty](IQueryable`1 source, Expression`1 navigationPropertyPath) at NetEarnMultiTenancy.TenantDomain.Tenants.UTaskEventHandler.HandleEventAsync(EntityCreatedEventData`1 eventData) in E:\Projects\NetEarnMultiTenancy\aspnet-core\src\NetEarnMultiTenancy.Core\UTaskDomain\UTaskEventHandler.cs:line 43 at Abp.Events.Bus.EventBus.TriggerAsyncHandlingException(IEventHandlerFactory asyncHandlerFactory, Type eventType, IEventData eventData, List`1 exceptions) at Abp.Events.Bus.EventBus.Trigger(Type eventType, Object eventSource, IEventData eventData) at Abp.Events.Bus.EventBus.Trigger(Type eventType, IEventData eventData) at Abp.Events.Bus.Entities.EntityChangeEventHelper.<>c__DisplayClass17_0.<TriggerEventWithEntity>b__0(Object sender, EventArgs args) at Abp.Extensions.EventHandlerExtensions.InvokeSafely(EventHandler eventHandler, Object sender, EventArgs e) at Abp.Extensions.EventHandlerExtensions.InvokeSafely(EventHandler eventHandler, Object sender) at Abp.Domain.Uow.UnitOfWorkBase.OnCompleted() at Abp.Domain.Uow.UnitOfWorkBase.CompleteAsync() at Abp.AspNetCore.Mvc.Uow.AbpUowActionFilter.OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextExceptionFilterAsync>g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
解决方案: 如:注册领域事件的类:xxxEventHanlder.cs
1、继承实现接口 IAsyncEventHandler<EntityCreatedEventData>
2、实现的接口方法添加工作单元属性 [UnitOfWork]
3、实现的接口方法添加 virtual 修饰符 public class UTaskEventHandler
NetEarnMultiTenancyDomainServiceBase, ITransientDependency, IAsyncEventHandler<EntityCreatedEventData<UTask>> { private readonly UTaskDomainService _uTaskDomainService; public UTaskEventHandler(UTaskDomainService uTaskDomainService) { _uTaskDomainService = uTaskDomainService; } [UnitOfWork] public virtual async Task HandleEventAsync(EntityCreatedEventData<UTask> eventData) { //内部使用_uTaskDomainService 领域服务 } }
为什么加了[UnitOfWork]和virtual 可以解决问题?
答:
1. **关于`[UnitOfWork]`属性的作用** - 在ABP框架中,`UnitOfWork`是一个非常重要的概念。
`[UnitOfWork]`属性用于定义一个方法应该在一个工作单元(Unit of Work)的上下文中执行。 - 当一个方法标记了`[UnitOfWork]`属性时,ABP框架会为这个方法创建一个新的工作单元。工作单元用于管理数据库事务和资源的生命周期。
- 在原始的异常场景中,由于在应用层或领域层的`MethodA`方法执行后可能导致相关的数据库上下文(如`NetEarnMultiTenancyDbContext`)被释放,而领域事件处理方法尝试访问已经释放的资源。通过添加`[UnitOfWork]`属性,ABP会确保在领域事件处理方法执行时,相关的数据库资源(如数据库上下文)会被正确地管理和维护,不会出现已经被释放的情况。
它会为这个方法单独开启一个工作单元,使得在这个方法内部可以安全地访问数据库相关的服务(如领域服务`DomainServiceA`)。
2. **关于`virtual`关键字的作用**
- 在C#中,`virtual`关键字用于修饰方法,表示这个方法可以在派生类中被重写。在ABP框架的这个场景中,使用`virtual`关键字使得方法`HandleEventAsync`可以在继承层次结构中被适当的代理或拦截机制处理。 -
动态代理做了什么?
ABP框架可能会使用动态代理等技术来处理标记了`[UnitOfWork]`的方法。通过将方法标记为`virtual`,框架能够在运行时通过继承和重写机制来插入必要的代码逻辑,以正确地处理工作单元的创建、事务管理以及与数据库资源相关的操作。
例如,ABP可能会在运行时创建一个代理类来包装`HandleEventAsync`方法,这个代理类可以在方法调用前后执行一些与工作单元相关的操作,如事务的开启和提交,以及确保数据库上下文的正确生命周期管理等。
如果方法不是`virtual`的,这种动态代理和代码插入机制可能会受到限制,无法有效地处理工作单元相关的逻辑,从而无法正确地解决数据库资源已经被释放的问题。
1. **动态代理与ABP框架中的方法拦截**
- 在ABP框架中,为了实现一些横切关注点(如工作单元管理、日志记录、权限验证等),常常会使用动态代理技术。动态代理是一种在运行时创建代理对象来代替原始对象的技术。
当一个方法被标记为`[UnitOfWork]`时,ABP框架希望能够在这个方法调用的前后插入一些与工作单元相关的逻辑。
- 例如,在方法调用前,框架需要创建一个新的工作单元,这可能包括开启一个数据库事务、获取一个数据库上下文实例等操作。在方法调用结束后,根据方法的执行情况(是否抛出异常等),框架需要决定是提交还是回滚事务,以及正确地释放数据库上下文等资源。
2. **`virtual`方法在动态代理中的作用**
- 对于C#语言,当一个方法是`virtual`时,它可以在派生类中被重写。
这就为动态代理提供了一个切入点。
ABP框架可以在运行时创建一个派生自包含`HandleEventAsync`方法的类的代理类。
- 假设原始类是`UTaskEventHandler`,框架可以创建一个类似`UTaskEventHandlerProxy`的代理类。这个代理类继承自`UTaskEventHandler`,并且重写了`HandleEventAsync`方法。在重写的`HandleEventAsync`方法中,代理类可以先执行工作单元的创建逻辑(如获取数据库上下文),然后调用原始的`HandleEventAsync`方法(通过`base.HandleEventAsync`),最后执行工作单元的完成逻辑(如提交或回滚事务、释放数据库上下文)。
class UTaskEventHandlerProxy : UTaskEventHandler
{
public UTaskEventHandlerProxy(UTaskDomainService uTaskDomainService) : base(uTaskDomainService) {}
public override async Task HandleEventAsync(EntityCreatedEventData<UTask> eventData)
{
// 工作单元创建逻辑,例如开启事务、获取数据库上下文
var unitOfWork = UnitOfWorkManager.Begin();
try
{
// 调用原始的HandleEventAsync方法
await base.HandleEventAsync(eventData);
// 工作单元完成逻辑,例如提交事务
unitOfWork.Complete();
}
catch (Exception ex)
{
// 工作单元完成逻辑,例如回滚事务
unitOfWork.Rollback();
throw;
}
}
}