工作單元(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 變量,用於存儲當前業務操作涉及的 增、刪、改 三種操作的存儲變量。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM