在上面一篇中我们主要是了解了在ABP系统中是如何使用UnitOfWork以及整个ABP系统中如何执行这些过程的,那么这一篇就让我们来看看UnitOfWorkManager中在执行Begin和Complete方法中到底执行了些什么?还是和往常一样来看看UnitOfWorkManager这个类,如果没有读过上面一篇,请点击这里

/// <summary>
/// Unit of work manager.
/// </summary>
internal class UnitOfWorkManager : IUnitOfWorkManager, ITransientDependency
{
private readonly IIocResolver _iocResolver;
private readonly ICurrentUnitOfWorkProvider _currentUnitOfWorkProvider;
private readonly IUnitOfWorkDefaultOptions _defaultOptions; public IActiveUnitOfWork Current
{
get { return _currentUnitOfWorkProvider.Current; }
} public UnitOfWorkManager(
IIocResolver iocResolver,
ICurrentUnitOfWorkProvider currentUnitOfWorkProvider,
IUnitOfWorkDefaultOptions defaultOptions)
{
_iocResolver = iocResolver;
_currentUnitOfWorkProvider = currentUnitOfWorkProvider;
_defaultOptions = defaultOptions;
} public IUnitOfWorkCompleteHandle Begin()
{
return Begin(new UnitOfWorkOptions());
} public IUnitOfWorkCompleteHandle Begin(TransactionScopeOption scope)
{
return Begin(new UnitOfWorkOptions { Scope = scope });
} public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
{
options.FillDefaultsForNonProvidedOptions(_defaultOptions); var outerUow = _currentUnitOfWorkProvider.Current; if (options.Scope == TransactionScopeOption.Required && outerUow != null)
{
return new InnerUnitOfWorkCompleteHandle();
} var uow = _iocResolver.Resolve<IUnitOfWork>(); uow.Completed += (sender, args) =>
{
_currentUnitOfWorkProvider.Current = null;
}; uow.Failed += (sender, args) =>
{
_currentUnitOfWorkProvider.Current = null;
}; uow.Disposed += (sender, args) =>
{
_iocResolver.Release(uow);
}; //Inherit filters from outer UOW
if (outerUow != null)
{
options.FillOuterUowFiltersForNonProvidedOptions(outerUow.Filters.ToList());
} uow.Begin(options); //Inherit tenant from outer UOW
if (outerUow != null)
{
uow.SetTenantId(outerUow.GetTenantId(), false);
} _currentUnitOfWorkProvider.Current = uow; return uow;
}
}

  在分析这个类之前我们首先来看看这个类的初始化过程到底做了些什么?在这个类初始化中,注入了IIocResolver、ICurrentUnitOfWorkProvider、IUnitOfWorkDefaultOptions对于这三个接口,第一个IIocResolver应该比较熟悉了,主要是为了获取IocContainer中特定接口对应的特定实例,IUnitOfWorkDefaultOptions这个在上一篇中已经说过了,这个主要是用于配置UnitOfWork中的一些关键属性及其他配置项的构建,那么ICurrentUnitOfWorkProvider这个到底起什么作用呢?我们来看看这个接口的具体实现吧?

/// <summary>
/// CallContext implementation of <see cref="ICurrentUnitOfWorkProvider"/>.
/// This is the default implementation.
/// </summary>
public class AsyncLocalCurrentUnitOfWorkProvider : ICurrentUnitOfWorkProvider, ITransientDependency
{
/// <inheritdoc />
[DoNotWire]
public IUnitOfWork Current
{
get { return GetCurrentUow(); }
set { SetCurrentUow(value); }
} public ILogger Logger { get; set; } private static readonly AsyncLocal<LocalUowWrapper> AsyncLocalUow = new AsyncLocal<LocalUowWrapper>(); public AsyncLocalCurrentUnitOfWorkProvider()
{
Logger = NullLogger.Instance;
} private static IUnitOfWork GetCurrentUow()
{
var uow = AsyncLocalUow.Value?.UnitOfWork;
if (uow == null)
{
return null;
} if (uow.IsDisposed)
{
AsyncLocalUow.Value = null;
return null;
} return uow;
} 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;
}
}
}

  这个类中最核心的就是GetCurrentUow()和SetCurrentUow(IUnitOfWork value)这两个方法了,初一看还是不太理解,这里我们可以看看ABP的官方文档上面有这么一段描述:

  A Unit Of Work Method Calls Another 

The unit of work is ambient. If a unit of work method calls another unit of work method, they share the same connection and transaction. The first method manages the connection and then the other methods reuse it.这段话的意思就是如果一个带工作单元的方法调用了另外一个带工作单元的方法的时候,那么这两个方法是会共享相同的连接和事物的,并且第一个调用的方法管理这个工作单元,第二个进行复用,所以上面的这个ICurrentUnitOfWorkProvider接口主要是为了解决多个工作单元互相调用的问题。

  但是我们来结合UnitOfWorkManager中的Begin方法试着来一起理解这个类。

public IUnitOfWorkCompleteHandle Begin(UnitOfWorkOptions options)
{
options.FillDefaultsForNonProvidedOptions(_defaultOptions); var outerUow = _currentUnitOfWorkProvider.Current; if (options.Scope == TransactionScopeOption.Required && outerUow != null)
{
return new InnerUnitOfWorkCompleteHandle();
} var uow = _iocResolver.Resolve<IUnitOfWork>(); uow.Completed += (sender, args) =>
{
_currentUnitOfWorkProvider.Current = null;
}; uow.Failed += (sender, args) =>
{
_currentUnitOfWorkProvider.Current = null;
}; uow.Disposed += (sender, args) =>
{
_iocResolver.Release(uow);
}; //Inherit filters from outer UOW
if (outerUow != null)
{
options.FillOuterUowFiltersForNonProvidedOptions(outerUow.Filters.ToList());
} uow.Begin(options); //Inherit tenant from outer UOW
if (outerUow != null)
{
uow.SetTenantId(outerUow.GetTenantId(), false);
} _currentUnitOfWorkProvider.Current = uow; return uow;
}

  在这个方法中,首先会获取_currentUnitOfWorkProvider.Current即获取当前执行方法唯一的UnitOfWork,如果当前方法的UnitOfWork不为null,那么就简单返回一个InnerUnitOfWorkCompleteHandle对象回去,这个里面都是一些简单的常规操作,我们来看看代码。

internal class InnerUnitOfWorkCompleteHandle : IUnitOfWorkCompleteHandle
{
public const string DidNotCallCompleteMethodExceptionMessage = "Did not call Complete method of a unit of work."; private volatile bool _isCompleteCalled;
private volatile bool _isDisposed; public void Complete()
{
_isCompleteCalled = true;
} public Task CompleteAsync()
{
_isCompleteCalled = true;
return Task.FromResult(0);
} public void Dispose()
{
if (_isDisposed)
{
return;
} _isDisposed = true; if (!_isCompleteCalled)
{
if (HasException())
{
return;
} throw new AbpException(DidNotCallCompleteMethodExceptionMessage);
}
} private static bool HasException()
{
try
{
return Marshal.GetExceptionCode() != 0;
}
catch (Exception)
{
return false;
}
}
}

  其实这个也很好理解,如果一个UnitOfWork的内部再调用另外一个UnitOfWork的时候,那么第二个UnitOfWork内部在执行_currentUnitOfWorkProvider.Current的时候,那么获取到的结果一定不为null,所以当第二个UnitOfWork执行完毕的时候只需要执行简单的设置_isCompleteCalled=true的操作就可以了,如果是第一个工作单元执行这个Begin方法时,当执行_currentUnitOfWorkProvider.Current的时候是无法获取到特定的工作单元的,再在后面会通过 var uow = _iocResolver.Resolve<IUnitOfWork>()来获取一个UnitOfWorkWork的对象,然后再执行_currentUnitOfWorkProvider.Current = uow这样第一个执行Begin方法的UnitOfWork也就获取到了一个唯一的IUnitOfWork的实例,并且其内部调用的工作单元也只会共享这一个唯一的IUnitOfWork的实例,这段代码我觉得真的非常巧妙,永远保证了嵌套调用的时候第一个方法和内部的方法拥有唯一的一个UnitOfWork,并且只有在第一个UnitOfWork退出的时候才执行后续的一系类操作。

  在这里我们发现最后的核心逻辑还是在唯一的继承自IUnitOfWork的对象中来完成和数据库的一些连接及事物操作,由于在ABP框架中继承自IUnitOfWork的对象众多,这里只以EntityFrameworkCore来进行说明,其它一些MongoDBUnitOfWork这里就不再作为分析的重点。这个IUnitOfWork中定义的方法是在UnitOfWorkBase中操作的,由于这里内容太多,仅仅贴出关键代码。

 public void Begin(UnitOfWorkOptions options)
{
Check.NotNull(options, nameof(options)); PreventMultipleBegin();
Options = options; //TODO: Do not set options like that, instead make a copy? SetFilters(options.FilterOverrides); SetTenantId(AbpSession.TenantId, false); BeginUow();
}

  这个里面主要是进行一些IReadOnlyList<DataFilterConfiguration> Filters的赋值一些操作,还有设置TenantId等操作,最后执行的BeginUow是一个虚方法,不同的ORM框架会去重载这个方法,然后完成相应的一些数据库连接以及数据库事物的操作。这里我们重点看一看EfCoreUnitOfWork这个类里面做了些什么?  

/// <summary>
/// Implements Unit of work for Entity Framework.
/// </summary>
public class EfCoreUnitOfWork : UnitOfWorkBase, ITransientDependency
{
protected IDictionary<string, DbContext> ActiveDbContexts { get; }
protected IIocResolver IocResolver { get; } private readonly IDbContextResolver _dbContextResolver;
private readonly IDbContextTypeMatcher _dbContextTypeMatcher;
private readonly IEfCoreTransactionStrategy _transactionStrategy; /// <summary>
/// Creates a new <see cref="EfCoreUnitOfWork"/>.
/// </summary>
public EfCoreUnitOfWork(
IIocResolver iocResolver,
IConnectionStringResolver connectionStringResolver,
IUnitOfWorkFilterExecuter filterExecuter,
IDbContextResolver dbContextResolver,
IUnitOfWorkDefaultOptions defaultOptions,
IDbContextTypeMatcher dbContextTypeMatcher,
IEfCoreTransactionStrategy transactionStrategy)
: base(
connectionStringResolver,
defaultOptions,
filterExecuter)
{
IocResolver = iocResolver;
_dbContextResolver = dbContextResolver;
_dbContextTypeMatcher = dbContextTypeMatcher;
_transactionStrategy = transactionStrategy; ActiveDbContexts = new Dictionary<string, DbContext>();
} protected override void BeginUow()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.InitOptions(Options);
}
} public override void SaveChanges()
{
foreach (var dbContext in GetAllActiveDbContexts())
{
SaveChangesInDbContext(dbContext);
}
} public override async Task SaveChangesAsync()
{
foreach (var dbContext in GetAllActiveDbContexts())
{
await SaveChangesInDbContextAsync(dbContext);
}
} protected override void CompleteUow()
{
SaveChanges();
CommitTransaction();
} protected override async Task CompleteUowAsync()
{
await SaveChangesAsync();
CommitTransaction();
} private void CommitTransaction()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.Commit();
}
} public IReadOnlyList<DbContext> GetAllActiveDbContexts()
{
return ActiveDbContexts.Values.ToImmutableList();
} public virtual TDbContext GetOrCreateDbContext<TDbContext>(MultiTenancySides? multiTenancySide = null)
where TDbContext : DbContext
{
var concreteDbContextType = _dbContextTypeMatcher.GetConcreteType(typeof(TDbContext)); var connectionStringResolveArgs = new ConnectionStringResolveArgs(multiTenancySide);
connectionStringResolveArgs["DbContextType"] = typeof(TDbContext);
connectionStringResolveArgs["DbContextConcreteType"] = concreteDbContextType;
var connectionString = ResolveConnectionString(connectionStringResolveArgs); var dbContextKey = concreteDbContextType.FullName + "#" + connectionString; DbContext dbContext;
if (!ActiveDbContexts.TryGetValue(dbContextKey, out dbContext))
{
if (Options.IsTransactional == true)
{
dbContext = _transactionStrategy.CreateDbContext<TDbContext>(connectionString, _dbContextResolver);
}
else
{
dbContext = _dbContextResolver.Resolve<TDbContext>(connectionString, null);
} if (Options.Timeout.HasValue &&
dbContext.Database.IsRelational() &&
!dbContext.Database.GetCommandTimeout().HasValue)
{
dbContext.Database.SetCommandTimeout(Options.Timeout.Value.TotalSeconds.To<int>());
}
ActiveDbContexts[dbContextKey] = dbContext;
} return (TDbContext)dbContext;
} protected override void DisposeUow()
{
if (Options.IsTransactional == true)
{
_transactionStrategy.Dispose(IocResolver);
}
else
{
foreach (var context in GetAllActiveDbContexts())
{
Release(context);
}
} ActiveDbContexts.Clear();
} protected virtual void SaveChangesInDbContext(DbContext dbContext)
{
dbContext.SaveChanges();
} protected virtual async Task SaveChangesInDbContextAsync(DbContext dbContext)
{
await dbContext.SaveChangesAsync();
} protected virtual void Release(DbContext dbContext)
{
dbContext.Dispose();
IocResolver.Release(dbContext);
}
}

  在这个类中,有两个方法是非常重要的,一个是BeginUow(),这个在执行UnitOfWorkManager的Begin方法的时候会使用到,另外一个就是 CompleteUow(),这个方法会在执行完拦截方法后UnitOfWorkManager执行Complete方法时候最终调用这个CompleteUow方法,在这个方法内部会依次执行SaveChanges()和CommitTransaction()方法,这两个方法也是非常好理解的,在SaveChanges方法内部会获取所有处于活动状态的DBContext,然后执行里面的每一个SaveChanges()从而完成更新到数据库的操作,CommitTransaction()顾名思义就是提交数据库事物的操作,有了这些操作一个带事物的并且能够管理数据库打开和关闭操作的UnitOfWork过程就整个都结束了。

  在BeginUow中我们首先判断Options.IsTransactional == true,这个Options是我们在执行IUnitOfWork.Begin方法中传递过来的UnitOfWorkOptions,这个主要用于配置UnitOfWork的一些参数,那么这个参数有什么意义呢?我们发现,在执行CommitTransaction()也是用的这个判断,所以这个参数可以控制当前的UnitOfWork是否执行事务,其实这个在ABP官方文档中也有这个方面的论述,我们可以看一看。

  Non-Transactional Unit Of Work

  By its nature, a unit of work is transactional. ASP.NET Boilerplate starts, commits or rolls back an explicit database-level transaction. In some special cases, the transaction may cause problems since it may lock some rows or tables in the database. In these situations, you may want to disable the database-level transaction. The UnitOfWork attribute can get a boolean value in its constructor to work as non-transactional.

  那么我们在ABP中如果想在UnitOfWork中禁用事务那么我们应该怎么做呢?同样我们可以直接在自定义UnitOfWork属性中添加IsTransactional:false来进行禁用操作。

[UnitOfWork(isTransactional: false)]
public GetTasksOutput GetTasks(GetTasksInput input)
{
var tasks = _taskRepository.GetAllWithPeople(input.AssignedPersonId, input.State);
return new GetTasksOutput
{
Tasks = Mapper.Map<List<TaskDto>>(tasks)
};
}  

  最后,点击这里返回整个ABP系列的主目录。

ABP拦截器之UnitOfWorkRegistrar(二)的更多相关文章

  1. ABP拦截器之UnitOfWorkRegistrar(一)

    ABP中UnitOfWorkRegistrar拦截器是整个ABP中非常关键的一个部分,这个部分在整个业务系统中也是用的最多的一个部分,这篇文章的主要思路并不是写如何使用ABP中的UnitOfWork, ...

  2. ABP拦截器之AuthorizationInterceptor

    在整体介绍这个部分之前,如果对ABP中的权限控制还没有一个很明确的认知,请先阅读这篇文章,然后在读下面的内容. AuthorizationInterceptor看这个名字我们就知道这个拦截器拦截用户一 ...

  3. ABP中的拦截器之ValidationInterceptor(上)

    从今天这一节起就要深入到ABP中的每一个重要的知识点来一步步进行分析,在进行介绍ABP中的拦截器之前我们先要有个概念,到底什么是拦截器,在介绍这些之前,我们必须要了解AOP编程思想,这个一般翻译是面向 ...

  4. (实用篇)浅谈PHP拦截器之__set()与__get()的理解与使用方法

    "一般来说,总是把类的属性定义为private,这更符合现实的逻辑. 但是,对属性的读取和赋值操作是非常频繁的,因此在PHP5中,预定义了两个函数"__get()"和&q ...

  5. Java过滤器与SpringMVC拦截器之间的关系与区别

    今天学习和认识了一下,过滤器和SpringMVC的拦截器的区别,学到了不少的东西,以前一直以为拦截器就是过滤器实现的,现在想想还真是一种错误啊,而且看的比较粗浅,没有一个全局而又细致的认识,由于已至深 ...

  6. PHP拦截器之__set()与__get()的理解与使用

    “一般来说,总是把类的属性定义为private,这更符合现实的逻辑.但是,对属性的读取和赋值操作是非常频繁的,因此在PHP5中,预定义了两个函数“__get()”和“__set()”来获取和赋值其属性 ...

  7. PHP拦截器之__set()与__get()的理解与使用方法

    “一般来说,总是把类的属性定义为private,这更符合现实的逻辑.   但是,对属性的读取和赋值操作是非常频繁的,因此在PHP5中,预定义了两个函数“__get()”和“__set()”来获取和赋值 ...

  8. ABP中的拦截器之EntityHistoryInterceptor

    今天我们接着之前的系列接着来写另外一种拦截器EntityHistoryInterceptor,这个拦截器到底是做什么的呢?这个从字面上理解是实体历史?这个到底是什么意思?带着这个问题我们来一步步去分析 ...

  9. ABP中的拦截器之AuditingInterceptor

    在上面两篇介绍了ABP中的ValidationInterceptor之后,我们今天来看看ABP中定义的另外一种Interceptor即为AuditingInterceptor,顾名思义就是一种审计相关 ...

随机推荐

  1. Python:黑板课爬虫闯关第三关

    第三关开始才算是进入正题了. 输入网址 http://www.heibanke.com/lesson/crawler_ex02/,直接跳转到了 http://www.heibanke.com/acco ...

  2. 【我们一起写框架】MVVM的WPF框架(四)—DataGrid

    前言 这个框架写到这里,应该有很多同学发现,框架很多地方的细节,其实是违背了MVVM的设计逻辑的. 没错,它的确是违背了. 但为什么明知道违背设计逻辑,还要这样编写框架呢? 那是因为,我们编写的是框架 ...

  3. WinForm客户端限速下载(C#限速下载)

    最近由于工作需要,需要开发一个能把服务器上的文件批量下载下来本地保存,关键是要实现限速下载,如果全速下载会影响服务器上的带宽流量.本来我最开始的想法是在服务器端开发一个可以从源头就限速下载的Api端口 ...

  4. Java开发笔记(八十二)注解的基本单元——元注解

    Java的注解非但是一种标记,还是一种特殊的类型,并且拥有专门的类型定义.前面介绍的五种内置注解,都可以找到对应的类型定义代码,例如查看注解@Override的源码,发现它的代码定义是下面这样的: @ ...

  5. Vue.js实现注册功能

    编写html,通过vue-resource.js库向后台提交数据 <!DOCTYPE html> <html lang="en"> <head> ...

  6. IDEA 安装配置可视化 MongDB 插件

    IDEA 安装配置可视化 MongDB 插件 1.安装MongoDB插件 打开 IDEA ,file --> settings --> plugins,在右边搜索栏中输入Mongo,点击 ...

  7. 自定义编译gdal库

    作者:朱金灿 来源:http://blog.csdn.net/clever101 使用下载下来的gdal库的makefile来编译gdal库,生成的gdal库的名字debug版本和release版本都 ...

  8. 七、Android动画

    Android的动画可以分为三种:View动画.帧动画和属性动画,帧动画也属于View动画的一种,只不过它和平移.旋转等常见的View动画在表现形式上略有不同而已. 1.View动画 平移动画:Tra ...

  9. 再议Java中的static关键字

    再议Java中的static关键字 java中的static关键字在很久之前的一篇博文中已经讲到过了,感兴趣的朋友可以参考:<Java中的static关键字解析>. 今天我们再来谈一谈st ...

  10. [20190416]查看shared latch gets的变化.txt

    [20190416]查看shared latch gets的变化.txt 1.环境:SYS@book> @ ver1PORT_STRING                    VERSION  ...