.net core2.x - 關於工作單元(UnitOfWork) 模式


概要:在搭建框架,順手說下寫下,關於unitofwork,可能你理解了,可能你還不理解,可能與不可能不是重點,重點是感興趣就看看吧。

1.工作單元(unitofowork)是什么(后面簡寫uow)?

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

  Unit of Work --Martin Fowler

  uow是這個老兄(馬丁)提出的,上面是他說的話,這里要注意的就是分兩個時間點,a)業務操作過程中,對操作的CUD操作的對象的狀態進行跟蹤操作; b) CUD操作完必經的一步驟當然是提交到數據庫,uow保證業務提交使用同意上下文對象,從而保證了事務的一致性,假設提交失敗直接回滾。

  簡單說:uow可以說是一個開關,開:用於上下文的獲取,供所有的倉儲對象使用;關:提交操作,提交的過程包含事務控制(是否會滾)。

  所以他這一句話我們可以明確這幾個東西:

  ①:一個對象用於存放 業務操作的對象(我們結合倉儲使用就是倉儲了) repository的臨時存儲對象;

  ②:同一業務操作的上下文必須保持一致(同一上下文對象)

  3  :維護當前業務的變更操作(微軟自帶輸入法打不出來圓圈三,,,,)

  ④:事務控制

  依據這幾點,所以我們可以和容易寫出我們想要的代碼,(臨時先刨除 第三點 ,后面會有補充),先聲明:這里未考慮多上下文的情況,因為我這邊用不到,但是實現也比較簡單,可以將涉及到的hashtable對象換成dictionary等,鍵為上下位對象類型或者名稱。

public class UnitOfWork : IUnitOfWork
    {
        #region fields
        /// <summary>
        ///     服務提供器,主要用於查找 框架配置對象,以及DbContextOptionBuilder對象
        /// </summary>
        private readonly IServiceProvider _provider;
        /// <summary>
        ///     當前請求涉及的scope生命的倉儲對象
        /// </summary>
        private Hashtable repositorys;

        private IDbContextTransaction _dbTransaction { get; set; }
        /// <summary>
        ///     上下文對象,UOW內部初始化上下文對象,供當前scope內的操作使用,保證同一上下文
        /// </summary>
        public DbContext DbContext => GetDbContext();
        #endregion

        #region ctor
        public UnitOfWork(IServiceProvider provider)
        {
            _provider = provider;
        }
        #endregion

        #region public

        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);
        }

        #endregion

        #region private
        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);//TODO null可以換成緩存中獲取connection對象,以便性能的提升

            if (!(ActivatorUtilities.CreateInstance(_provider, options.ESoftorDbOption.DbContextType, optionsBuilder.Options) is DbContext 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;
            }
        }

        #endregion

        #region override
        public void Dispose()
        {
            _dbTransaction.Dispose();
            DbContext.Dispose();
            GC.SuppressFinalize(this);
        }
        #endregion
    }
View Code

  接口定義:

/// <summary>
    ///     工作單元接口
    /// </summary>
    public interface IUnitOfWork : IDisposable
    {
        IRepository<TEntity, TKey> Repository<TEntity, TKey>() where TEntity : class, IEntity<TKey>;

        void BeginTransaction();

        int Commit();
        Task<int> CommitAsync();
    }
View Code

 

2.怎么用?

  就目前而言,博客園中可見到的大部分的 實現都是將uow注入到 repository,通過 uow獲取上下文對象,然后在 service中只是直接注入所需的 repository對象,是的,這的確滿足我們的開發需求了,也能正常運行。

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

  如果有多個倉儲對象,依次如上面的方式注入即可。但是,這樣做的話,當以后我們有表刪除或者新增的時候,我們不得不維護這樣的列表。這完全不符合OO設計原則;如果我們有新表的創建或者刪除,改動就比較多了。

  如果你有細細觀察的話,我們這里的 UOW實現稍有不同,也就涉及到當前請求的 倉儲對象(repository),我們在這零時存儲到了一個 hashable對象中,那么這時候我們在 service中使用uow和倉儲的時候,就不用像上面那樣,有多少個需要注冊多少次,而只需要注入我們的一個uow對象即可。然后通過uow獲取 倉儲(repository)對象,因為我們零時將涉及到當前請求(事務)的 倉儲已經存儲到私有變量的 hashtable中,

public class TestService
{
      private readonly IUnitOfWork _uow;
      public TestService(IUnitOfWork uow){
      _uow = uow;
      }  
      //......其他方法實現
} 

  然后我們在使用倉儲(repository)的時候,只需要如下方式使用即可:

  var userRepository = _uow.Repository<User,Guid>();

  var roleRepository = _uow.Repository<Role,Guid>();

  ...

  而在我們用到事務的地方,直接使用uow中的commit提交即可:

  _uow.BeginTransaction();

  var userRepository = _uow.Repository<User,Guid>();

  var roleRepository = _uow.Repository<Role,Guid>();

  ...//一些列其他操作,(CRUD)

  _uow.Commit();

  就像上面說到的,這樣保證了當前業務操作涉及的 倉儲對象(repository),會保證在 hashtable對象中,同時使用同一個上線問對象(DbContext),Commit提交的時候保證了事務(上下文)的一致性。而且如上面提到的,我們只需要在service層中注入一個uow即可,不論表如何變動,刪除或者新增表,我們這里不會收到任何影響。比較理想的一種方式。

 

3.注意點?

  uow模式注意點,也就是uow的說明 即:

  1.由uow初始化上下文對象,也就是我們代碼中的DbContext,;

  2.由uow提供事務的控制方法,以及控制事務回滾,保證最終一致性

  3.這里我們還使用了uow進行倉儲對象的 獲取。

  4.其他

  

4.補充:對象操作狀態的控制?

  上面有說到,uow還需要對操作狀態的控制?啥意思?簡單說就是,一系列的 增、刪、改的 命令操作 的狀態控制,這里的實現,園子已經在很早之前就有比較完善的實現了:

  http://www.cnblogs.com/zxj159/p/3505457.html

  基本原理就是 類似我們定義的 hashtable對象,定義三個 Dictionary 變量,用於存儲當前 業務操作涉及的 增、刪、改、三種操作的 存儲變量。

 

完。

 


免責聲明!

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



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