在上面一篇中我们主要是了解了在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. Devexpress常见问题

    1.DevExpress控件组中的GridControl控件不能使横向滚动条有效. 现象:控件中的好多列都挤在一起,列宽都变的很小,根本无法正常浏览控件单元格中的内容. 解决:gridView1.Op ...

  2. Bootstrap 前端框架 遇到的问题 解决方案

    bootstrap实现导航栏的响应式布局,当在小屏幕.手机屏幕浏览时自动折叠隐藏 直接放代码,更容易理解.下次可以套这个代码 <!DOCTYPE html> <html> &l ...

  3. 深入学习ThreadLocal原理

    上文我们学习了ThreadLocal的基本用法以及基本原理,ThreadLocal中的方法并不多,基本用到的也就get.set.remove等方法,但是其核心逻辑还是在定义在ThreadLocal内部 ...

  4. Java基础系列-Collector和Collectors

    原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/10748925.html 一.概述 Collector是专门用来作为Stream的coll ...

  5. 【带着canvas去流浪(6)】绘制雷达图

    目录 一. 任务说明 二. 重点提示 三. 示例代码 示例代码托管在:http://www.github.com/dashnowords/blogs 博客园地址:<大史住在大前端>原创博文 ...

  6. InnoSetup 脚本打包及管理员权限设置

    InnoSetup使用教程:InnoSetup打包安装 脚本详细 1. 定义变量 #define MyAppName "TranslationTool" #define MyApp ...

  7. Basic Linux Privilege Escalation

    (Linux) privilege escalation is all about: Collect - Enumeration, more enumeration and some more enu ...

  8. Android为TV端助力:自定义view之太阳

    先看效果图 package com.hhzt.iptv.lvb_w8.view; import android.content.Context;import android.graphics.Canv ...

  9. Android 报错:error: too many padding sections on bottom border

    一.发生错误 [我以为我做了一张完美的.9图片,没想到.9图片还需要画左边和上边,尴尬···] 二.解决方法 .9图片造成错误 [具体内容] 最后修改.9图为

  10. windows2012R2安装SQL2005详情!

    用友T3软件报错单据的时候提示1105数据库错误 原因分析:客户使用的是sql2005 express的数据库,账套的物理文件达到了4G. 只能重装SQL的版本,but.... 在window2012 ...