zoukankan      html  css  js  c++  java
  • 工作单元(UnitOfWork) 模式 (2) .NET Core

    1.工作单元(UnitOfWork)是什么?

      Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.

      UnitOfWork是Martin Fowler提出的,上面是他说的话,这里要注意的就是分两个时间点,a)业务操作过程中,对操作的CUD操作的对象状态进行跟踪操作; b) CUD操作完毕提交到数据库,UnitOfWork保证业务提交使用同意上下文对象,从而保证了事务的一致性,假设提交失败直接回滚。

      简单说:UnitOfWork可以说是一个开关,开:用于上下文的获取,供所有的仓储对象使用;关:提交操作,提交的过程包含事务控制(是否回滚)。

      所以他这一句话我们可以明确这几个东西:

      1. 一个对象用于存放 业务操作的对象(我们结合仓储使用就是仓储了) repository的临时存储对象;
      2. 同一业务操作的上下文必须保持一致(同一上下文对象);
      3. 维护当前业务的变更操作;
      4. 事务控制;

      依据这几点,我们就可以写出想要的代码,(临时先刨除 第三点 ,后面会有补充),先声明:这里未考虑多上下文的情况,因为我这边用不到,但是实现也比较简单,可以将涉及到的hashtable对象换成dictionary等,键为上下位对象类型或者名称。

      接口定义:

    /// <summary>
    ///     工作单元接口
    /// </summary>
    public interface IUnitOfWork : IDisposable
    {
    	IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey>;
    	void BeginTransaction();
    	int Commit();
    	Task<int> CommitAsync();
    }
    
    public class UnitOfWork : IUnitOfWork
    {
    	/// <summary>
    	///     服务提供器,主要用于查找 框架配置对象,以及DbContextOptionBuilder对象
    	/// </summary>
    	private readonly IServiceProvider _provider;
    	/// <summary>
    	///     当前请求涉及的scope生命的仓储对象
    	/// </summary>
    	private Hashtable repositorys;
    
    	private IDbContextTransaction _dbTransaction { get; set; }
    	/// <summary>
    	///     上下文对象,UnitOfWork内部初始化上下文对象,供当前scope内的操作使用,保证同一上下文
    	/// </summary>
    	public DbContext DbContext => GetDbContext();
    
    	public UnitOfWork(IServiceProvider provider)
    	{
    		_provider = provider;
    	}
    
    	public IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey>
    	{
    		if (repositorys == null)
    			repositorys = new Hashtable();
    
    		var entityType = typeof(TEntity);
    		if (!repositorys.ContainsKey(entityType.Name))
    		{
    			var baseType = typeof(Repository<,>);
    			var repositoryInstance = Activator.CreateInstance(baseType.MakeGenericType(entityType), DbContext);
    			repositorys.Add(entityType.Name, repositoryInstance);
    		}
    
    		return (IRepository<TEntity, TKey>)repositorys[entityType.Name];
    	}
    
    	public void BeginTransaction()
    	{
    		//DbContext.Database.UseTransaction(_dbTransaction);//如果多上下文,我们可是在其他上下文直接使用 UserTransaction使用已存在的事务
    		_dbTransaction = DbContext.Database.BeginTransaction();
    	}
    
    	public int Commit()
    	{
    		int result = 0;
    		try
    		{
    			result = DbContext.SaveChanges();
    			if (_dbTransaction != null)
    				_dbTransaction.Commit();
    		}
    		catch (Exception ex)
    		{
    			result = -1;
    			CleanChanges(DbContext);
    			_dbTransaction.Rollback();
    			throw new Exception($"Commit 异常:{ex.InnerException}/r{ ex.Message}");
    		}
    		return result;
    	}
    
    	public async Task<int> CommitAsync()
    	{
    		int result = 0;
    		try
    		{
    			result = await DbContext.SaveChangesAsync();
    			if (_dbTransaction != null)
    				_dbTransaction.Commit();
    		}
    		catch (Exception ex)
    		{
    			result = -1;
    			CleanChanges(DbContext);
    			_dbTransaction.Rollback();
    			throw new Exception($"Commit 异常:{ex.InnerException}/r{ ex.Message}");
    		}
    		return await Task.FromResult(result);
    	}
    
    	private DbContext GetDbContext()
    	{
    		var options = _provider.ESoftorOption();
    		IDbContextOptionsBuilderCreator builderCreator = _provider.GetServices<IDbContextOptionsBuilderCreator>()
    			.FirstOrDefault(d => d.DatabaseType == options.ESoftorDbOption.DatabaseType);
    
    		if (builderCreator == null)
    			throw new Exception($"无法解析数据库类型为:{options.ESoftorDbOption.DatabaseType}的{typeof(IDbContextOptionsBuilderCreator).Name}实例");
    		//DbContextOptionsBuilder
    		var optionsBuilder = builderCreator.Create(options.ESoftorDbOption.ConnectString, null);//null可以换成缓存中获取connection对象,以便性能的提升
    		if (!(ActivatorUtilities.CreateInstance(_provider, options.ESoftorDbOption.DbContextType, optionsBuilder.Options) is DbContext))
    			throw new Exception($"上下文对象'{options.ESoftorDbOption.DbContextType.AssemblyQualifiedName}'实例化失败,请确认配置文件已正确配置");
    		return dbContext;
    	}
    
    	/// <summary>
    	///     操作失败,还原跟踪状态
    	/// </summary>
    	/// <param name="context"></param>
    	private static void CleanChanges(DbContext context)
    	{
    		var entries = context.ChangeTracker.Entries().ToArray();
    		for (int i = 0; i < entries.Length; i++)
    		{
    			entries[i].State = EntityState.Detached;
    		}
    	}
    
    	public void Dispose()
    	{
    		_dbTransaction.Dispose();
    		DbContext.Dispose();
    		GC.SuppressFinalize(this);
    	}
    }
    

    2.怎么用?

      就目前而言,博客园中可见到的大部分的 实现都是将 UnitOfWork 注入到 repository,通过 UnitOfWork 获取上下文对象,然后在 service 中只是直接注入所需的 repository 对象,是的,这的确满足我们的开发需求了,也能正常运行。

    public class TestService
    {
          private readonly ITestRepository _testRepository;
          public TestService(ITestRepository testRepository){
              _testRepository = testRepository;
          }  
          //......其他方法实现
    }    
    

      如果有多个仓储对象,依次如上面的方式注入即可。但是,这样做的话,当以后我们有表删除或者新增的时候,我们不得不维护这样的列表。这完全不符合OO设计原则;如果我们有新表的创建或者删除,改动就比较多了。

      如果你有细细观察的话,我们这里的 UnitOfWork实现稍有不同,也就涉及到当前请求的 仓储对象(repository),我们在这临时存储到了一个 hashable对象中,那么这时候我们在 service中使用UnitOfWork和仓储的时候,就不用像上面那样,有多少个需要注册多少次,而只需要注入一个UnitOfWork对象即可。然后通过UnitOfWork获取 仓储对象(repository),因为我们临时将涉及到当前请求(事务)的 仓储已经存储到私有变量的 hashtable中。

    public class TestService
    {
          private readonly IUnitOfWork _UnitOfWork;
          public TestService(IUnitOfWork UnitOfWork){
          _UnitOfWork = UnitOfWork;
          }  
          //......其他方法实现
    } 
    

      然后我们在使用仓储(repository)的时候,只需要如下方式使用即可:

      var userRepository = _UnitOfWork.Repository<User,Guid>();
      var roleRepository = _UnitOfWork.Repository<Role,Guid>();
      ...
    

      而在我们用到事务的地方,直接使用UnitOfWork中的commit提交即可:

      _UnitOfWork.BeginTransaction();
      var userRepository = _UnitOfWork.Repository<User,Guid>();
      var roleRepository = _UnitOfWork.Repository<Role,Guid>();
      ...//一些列其他操作,(CRUD)
      _UnitOfWork.Commit();
    

      就像上面说到的,这样保证了当前业务操作涉及的 仓储对象(repository),会保证在 hashtable对象中,同时使用同一个上下文对象(DbContext),Commit提交的时候保证了事务(上下文)的一致性。而且如上面提到的,我们只需要在service层中注入一个UnitOfWork即可,不论表如何变动,删除或者新增表,我们这里不会受到任何影响。比较理想的一种方式。

    3.注意点?

      UnitOfWork模式注意点:
      1.由UnitOfWork初始化上下文对象,也就是我们代码中的DbContext
      2.由UnitOfWork提供事务的控制方法,以及控制事务回滚,保证最终一致性
      3.这里我们还使用了UnitOfWork进行仓储对象的获取。
      4.其他

    4.补充:对象操作状态的控制

      上面有说到,UnitOfWork还需要对操作状态的控制,简单说就是,一系列的 增、删、改 命令操作的状态控制,可参考:https://www.cnblogs.com/zhaoshujie/p/12260188.html
      基本原理就是类似我们定义的 hashtable对象,定义三个 Dictionary 变量,用于存储当前业务操作涉及的 增、删、改 三种操作的存储变量。

  • 相关阅读:
    [置顶] java得到前一个月的年月日时分秒
    Windows 8.1 Preview的新功能和新API
    oracle保证读一致性原理
    10161
    Qt国际化
    使用Maven管理依赖JAR文件,自定义项目布局,利用ANT生成不同的发布包
    Haxe2.10到Haxe3,NME到OpenFL的迁移备忘
    设置RichEdit相关颜色说明
    使用MFC CImage类绘制PNG图片时遇到的问题
    把网球计分招式重构到状态模式
  • 原文地址:https://www.cnblogs.com/zhaoshujie/p/12260363.html
Copyright © 2011-2022 走看看