UnitOfWork知多少
1. 引言
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
Unit Of Work模式,由馬丁大叔提出,是一種數據訪問模式。UOW模式的作用是在業務用例的操作中跟蹤對象的所有更改(增加、刪除和更新),并將所有更改的對象保存在其維護的列表中。在業務用例的終點,通過事務,一次性提交所有更改,以確保數據的完整性和有效性。總而言之,UOW協調這些對象的持久化及并發問題。
2. UOW的本質
通過以上的介紹,我們可以總結出實現UOW的幾個要點:
UOW跟蹤變化
UOW維護了一個變更列表
UOW將跟蹤到的已變更的對象保存到變更列表中
UOW借助事務一次性提交變更列表中的所有更改
UOW處理并發
而對于這些要點,EF中的DBContext已經實現了。
3. EF中的UOW
每個DbContext類型實例都有一個ChangeTracker用來跟蹤記錄實體的變化。當調用SaveChanges時,所有的更改將通過事務一次性提交到數據庫。
我們直接看個EF Core的測試用例:
public ApplicationDbContext InMemorySqliteTestDbContext { ??get{ ? ? ? ?// In-memory database only exists while the connection is openvar connection = new SqliteConnection("DataSource=:memory:");connection.Open(); ? ?
?? ?var options = new DbContextOptionsBuilder<ApplicationDbContext>().UseSqlite(connection).Options; ? ? ?
?? ??var context = new ApplicationDbContext(options);context.Database.EnsureCreated(); ? ?
?? ?? ? ?return context;} }[Fact]public void Test_Ef_Implemented_Uow(){ ? ?//新增用戶var user = new ApplicationUser(){UserName = "shengjie",Email = "ysjshengjie@qq.com"};InMemorySqliteTestDbContext.Users.Add(user); ? ?//創建用戶對應客戶var customer = new Customer(){ApplicationUser = user,NickName = "圣杰"};InMemorySqliteTestDbContext.Customers.Add(customer); ? ?//添加地址var address = new Address("廣東省", "深圳市", "福田區", "下沙街道", "圣杰", "135****9309");InMemorySqliteTestDbContext.Addresses.Add(address); ? ?//修改客戶對象的派送地址customer.AddShippingAddress(address);InMemoryTestDbContext.Entry(customer).State = EntityState.Modified; ? ?//保存var changes = InMemorySqliteTestDbContext.SaveChanges();Assert.Equal(3, changes); ? ?var savedCustomer = InMemorySqliteTestDbContext.Customers.FirstOrDefault(c => c.NickName == "圣杰");Assert.Equal("shengjie", savedCustomer.ApplicationUser.UserName);Assert.Equal(customer.ApplicationUserId, savedCustomer.ApplicationUserId);Assert.Equal(1, savedCustomer.ShippingAddresses.Count); }
首先這個用例是綠色通過的。該測試用例中我們添加了一個User,并為User創建對應的Customer,同時為Customer添加一條Address。從代碼中我們可以看出僅做了一次保存,新增加的User、Customer、Address對象都成功持久化到了內存數據庫中。從而證明EF Core是實現了Uow模式的。但很顯然應用程序與基礎設施層高度耦合,那如何解耦呢?繼續往下看。
4. DDD中的UOW
那既然EF Core已經實現了Uow模式,我們還有必要自行實現一套Uow模式嗎?這就視具體情況而定了,如果你的項目簡單的增刪改查就搞定了的,就不用折騰了。
在DDD中,我們會借助倉儲模式來實現領域對象的持久化。倉儲只關注于單一聚合的持久化,而業務用例卻常常會涉及多個聚合的更改,為了確保業務用例的一致型,我們需要引入事務管理,而事務管理是應用服務層的關注點。我們如何在應用服務層來管理事務呢?借助UOW。這樣就形成了一條鏈:Uow->倉儲-->聚合-->實體和值對象。即Uow負責管理倉儲處理事務,倉儲管理單一聚合,聚合又由實體和值對象組成。
下面我們就先來定義實體和值對象,這里我們使用層超類型。
4.1. 定義實體
? ?/// <summary>/// A shortcut of <see cref="IEntity{TPrimaryKey}"/> for most used primary key type (<see cref="int"/>)./// </summary>public interface IEntity : IEntity<int>{} ? ?? ?
? ?/// <summary>/// Defines interface for base entity type. All entities in the system must implement this interface./// </summary>/// <typeparam name="TPrimaryKey">Type of the primary key of the entity</typeparam>public interface IEntity<TPrimaryKey>{ ? ? ? ?/// <summary>/// Unique identifier for this entity./// </summary>TPrimaryKey Id { get; set; }}
4.2. 定義聚合
namespace UnitOfWork { ??public interface IAggregateRoot : IAggregateRoot<int>, IEntity{} ? ?
?
?public interface IAggregateRoot<TPrimaryKey> : IEntity<TPrimaryKey>{} }
4.3. 定義泛型倉儲
namespace UnitOfWork { ??public interface IRepository<TEntity> : IRepository<TEntity, int>where TEntity : class, IEntity, IAggregateRoot{} ?
?public interface IRepository<TEntity, TPrimaryKey>where TEntity : class, IEntity<TPrimaryKey>, IAggregateRoot<TPrimaryKey>{ ? ? ? ?
?IQueryable<TEntity> GetAll(); ? ?
? ?TEntity Get(TPrimaryKey id); ? ? ?
? ??TEntity FirstOrDefault(TPrimaryKey id); ? ?
? ?TEntity Insert(TEntity entity); ? ?
? ? ? ?TEntity Update(TEntity entity); ? ? ?
?void Delete(TEntity entity); ? ? ? ?void Delete(TPrimaryKey id);} }
因為倉儲是管理聚合的,所以我們需要限制泛型參數為實現IAggregateRoot的類。
4.4. 實現泛型倉儲
amespace UnitOfWork.Repositories { ? ?public class EfCoreRepository<TEntity>: EfCoreRepository<TEntity, int>, IRepository<TEntity>where TEntity : class, IEntity, IAggregateRoot{ ? ? ?
?public EfCoreRepository(UnitOfWorkDbContext dbDbContext) : base(dbDbContext){}} ?
?public class EfCoreRepository<TEntity, TPrimaryKey>: IRepository<TEntity, TPrimaryKey>where TEntity : class, IEntity<TPrimaryKey>, IAggregateRoot<TPrimaryKey>{ ? ?
? ?private readonly UnitOfWorkDbContext _dbContext; ? ?
? ?public virtual DbSet<TEntity> Table => _dbContext.Set<TEntity>(); ? ?
? ?public EfCoreRepository(UnitOfWorkDbContext dbDbContext){_dbContext = dbDbContext;} ? ? ?
?public IQueryable<TEntity> GetAll(){ ? ? ? ? ? ?return Table.AsQueryable();} ? ?
? ?public TEntity Insert(TEntity entity){ ? ? ? ? ? ?var newEntity = Table.Add(entity).Entity;_dbContext.SaveChanges(); ? ? ? ? ? ?return newEntity;} ? ? ?
?public TEntity Update(TEntity entity){AttachIfNot(entity);_dbContext.Entry(entity).State = EntityState.Modified;_dbContext.SaveChanges(); ? ? ? ? ?
?return entity;} ? ?
? ?public void Delete(TEntity entity){AttachIfNot(entity);Table.Remove(entity);_dbContext.SaveChanges();} ? ?
? ?public void Delete(TPrimaryKey id){ ? ? ? ?
? ?var entity = GetFromChangeTrackerOrNull(id); ? ?
? ?? ? ? ?if (entity != null){Delete(entity); ? ? ? ? ?
? ? ?return;}entity = FirstOrDefault(id); ? ?
? ? ? ?if (entity != null){Delete(entity); ? ? ? ? ?
? ? ? ?? ? ?return;}} ? ? ? ?protected virtual void AttachIfNot(TEntity entity){ ? ? ? ? ? ?var entry = _dbContext.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity); ? ? ? ? ? ?if (entry != null){ ? ? ? ? ? ? ? ?return;}Table.Attach(entity);} ? ? ?
?private TEntity GetFromChangeTrackerOrNull(TPrimaryKey id){ ? ? ? ?
?
? ? ? ??var entry = _dbContext.ChangeTracker.Entries().FirstOrDefault(ent =>ent.Entity is TEntity &&EqualityComparer<TPrimaryKey>.Default.Equals(id, ((TEntity)ent.Entity).Id)); ? ? ?
? ? ?? ? ?return entry?.Entity as TEntity;}} }
因為我們直接使用EF Core進行持久化,所以我們直接通過構造函數初始化DbContex實例。同時,我們注意到Insert、Update、Delete方法都顯式的調用了SaveChanges方法。
至此,我們完成了從實體到聚合再到倉儲的定義和實現,萬事俱備,只欠Uow。
4.5. 實現UOW
通過第3節的說明我們已經知道,EF Core已經實現了UOW模式。而為了確保領域層透明的進行持久化,我們對其進行了更高一層的抽象,實現了倉儲模式。但這似乎引入了另外一個問題,因為倉儲是管理單一聚合的,每次做增刪改時都顯式的提交了更改(調用了SaveChanges),在處理多個聚合時,就無法利用DbContext進行批量提交了。那該如何是好?一不做二不休,我們再對其進行一層抽象,抽離保存接口,這也就是Uow的核心接口方法。
我們抽離SaveChanges方法,定義IUnitOfWork接口。
?public interface IUnitOfWork{ ? ? ?
??int SaveChanges();} }
因為我們是基于EFCore實現Uow的,所以我們只需要依賴DbContex,就可以實現批量提交。實現也很簡單:
namespace UnitOfWork { ??public class UnitOfWork<TDbContext> : IUnitOfWork where TDbContext : DbContext{ ? ? ?
? ?private readonly TDbContext _dbContext; ? ?
? ?
? ? ? ?public UnitOfWork(TDbContext context) ? ? ? ?{_dbContext = context ?? throw new ArgumentNullException(nameof(context));} ? ?
? ? ? ?
? ? ? ? ?public int SaveChanges() ? ? ? ?{ ? ? ? ?
? ? ? ? ?? ?return _dbContext.SaveChanges();}} }
既然Uow接手保存操作,自然我們需要:注釋掉EfCoreRepository中Insert、Update、Delete方法中的顯式保存調用_dbContext.SaveChanges();。
那如何確保操作多個倉儲時,最終能夠一次性提交所有呢?
確保Uow和倉儲共用同一個DbContex即可。這個時候我們就可以借助依賴注入。
4.6. 依賴注入
我們直接使用.net core 提供的依賴注入,依次注入DbContext、UnitOfWork和Repository。
//注入DbContextservices.AddDbContext<UnitOfWorkDbContext>(options =>options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));//注入Uow依賴services.AddScoped<IUnitOfWork, UnitOfWork<UnitOfWorkDbContext>>();//注入泛型倉儲services.AddTransient(typeof(IRepository<>), typeof(EfCoreRepository<>)); services.AddTransient(typeof(IRepository<,>), typeof(EfCoreRepository<,>));這里我們限定了DbContext和UnitOfWork的生命周期為Scoped,從而確保每次請求共用同一個對象。如何理解呢?就是整個調用鏈上的需要注入的同類型對象,使用是同一個類型實例。
4.7. 使用UOW
下面我們就來實際看一看如何使用UOW,我們定義一個應用服務:
namespace UnitOfWork.Customer{ ? ?public class CustomerAppService : ICustomerAppService{ ? ?
? ? ?private readonly IUnitOfWork _unitOfWork; ? ? ?
? ? ?private readonly IRepository<Customer> _customerRepository; ?
? ? ?private readonly IRepository<ShoppingCart.ShoppingCart> _shoppingCartRepository; ?
? ? ?
? ? ?public CustomerAppService(IRepository<ShoppingCart> shoppingCartRepository, ? ? ? ? ? ?IRepository<Customer> customerRepository, IUnitOfWork unitOfWork) ? ? ? ?{_shoppingCartRepository = shoppingCartRepository;_customerRepository = customerRepository;_unitOfWork = unitOfWork;} ? ? ?
? ? ?
? ? ?public void CreateCustomer(Customer customer) ?
? ? ?{_customerRepository.Insert(customer);//創建客戶var cart = new ShoppingCart.ShoppingCart() {CustomerId = customer.Id};_shoppingCartRepository.Insert(cart);//創建購物車_unitOfWork.SaveChanges();} ? ? ? ?//....} }
通過以上案例,我們可以看出,我們只需要通過構造函數依賴注入需要的倉儲和Uow即可完成對多個倉儲的持久化操作。
5. 最后
對于Uow模式,有很多種實現方式,大多過于復雜抽象。EF和EF Core本身已經實現了Uow模式,所以在實現時,我們應避免不必要的抽象來降低系統的復雜度。
最后,重申一下:
Uow模式是用來管理倉儲處理事務的,倉儲用來解耦的(領域層與基礎設施層)。而基于EF實現Uow模式的關鍵:確保Uow和Reopository之間共享同一個DbContext實例。
最后附上使用.Net Core和EF Core基于DDD分層思想實現的源碼:?GitHub--UnitOfWork
相關文章
DDD理論學習系列(1)-- 通用語言
DDD領域驅動之干貨 (一)
DDD理論學習系列(2)-- 領域
DDD理論學習系列(3)-- 限界上下文
DDD理論學習系列(4)-- 領域模型
事件總線知多少(2)
DDD理論學習系列(5)-- 統一建模語言
DDD理論學習系列(6)-- 實體
DDD理論學習系列(7)-- 值對象
DDD理論學習系列(8)-- 應用服務&領域服務
DDD理論學習系列(9)-- 領域事件
DDD理論學習系列(10)-- 聚合
DDD理論學習系列(11)-- 工廠
DDD理論學習系列(12)-- 倉儲
DDD理論學習系列(13)-- 模塊
從事件和DDD入手來構建微服務
DDD領域驅動之干貨 (一)
WeText項目:一個基于.NET實現的DDD、CQRS與微服務架構的演示案例
【DDD/CQRS/微服務架構案例】在Ubuntu 14.04.4 LTS中運行WeText項目的服務端
基于.NET CORE微服務框架 -surging的介紹和簡單示例 (開源)
剝析surging的架構思想
基于.NET CORE微服務框架 -談談surging的服務容錯降級
我眼中的ASP.NET Core之微服務
.NET Core 事件總線,分布式事務解決方案:CAP
原文地址:http://www.cnblogs.com/sheng-jie/p/7416302.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的UnitOfWork知多少的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C#使用Xamarin开发可移植移动应用
- 下一篇: C#使用Xamarin开发可移植移动应用