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 Fowlerhtml
Unit Of Work模式,由馬丁大叔提出,是一種數據訪問模式。UOW模式的做用是在業務用例的操做中跟蹤對象的全部更改(增長、刪除和更新),並將全部更改的對象保存在其維護的列表中。在業務用例的終點,經過事務,一次性提交全部更改,以確保數據的完整性和有效性。總而言之,UOW協調這些對象的持久化及併發問題。git
經過以上的介紹,咱們能夠總結出實現UOW的幾個要點:github
而對於這些要點,EF中的DBContext已經實現了。數據庫
每一個DbContext
類型實例都有一個ChangeTracker
用來跟蹤記錄實體的變化。當調用SaveChanges
時,全部的更改將經過事務一次性提交到數據庫。併發
咱們直接看個EF Core的測試用例:ide
public ApplicationDbContext InMemorySqliteTestDbContext { get { // In-memory database only exists while the connection is open var 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模式的。但很顯然應用程序與基礎設施層高度耦合,那如何解耦呢?繼續往下看。函數
那既然EF Core已經實現了Uow模式,咱們還有必要自行實現一套Uow模式嗎?這就視具體狀況而定了,若是你的項目簡單的增刪改查就搞定了的,就不用折騰了。測試
在DDD中,咱們會藉助倉儲模式來實現領域對象的持久化。倉儲只關注於單一聚合的持久化,而業務用例卻經常會涉及多個聚合的更改,爲了確保業務用例的一致型,咱們須要引入事務管理,而事務管理是應用服務層的關注點。咱們如何在應用服務層來管理事務呢?藉助UOW。這樣就造成了一條鏈:Uow->倉儲-->聚合-->實體和值對象。即Uow負責管理倉儲處理事務,倉儲管理單一聚合,聚合又由實體和值對象組成。ui
下面咱們就先來定義實體和值對象,這裏咱們使用層超類型。this
/// <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; } }
namespace UnitOfWork { public interface IAggregateRoot : IAggregateRoot<int>, IEntity { } public interface IAggregateRoot<TPrimaryKey> : IEntity<TPrimaryKey> { } }
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
的類。
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。
經過第3節的說明咱們已經知道,EF Core已經實現了UOW模式。而爲了確保領域層透明的進行持久化,咱們對其進行了更高一層的抽象,實現了倉儲模式。但這彷佛引入了另一個問題,由於倉儲是管理單一聚合的,每次作增刪改時都顯式的提交了更改(調用了SaveChanges),在處理多個聚合時,就沒法利用DbContext進行批量提交了。那該如何是好?一不作二不休,咱們再對其進行一層抽象,抽離保存接口,這也就是Uow的核心接口方法。
咱們抽離SaveChanges
方法,定義IUnitOfWork
接口。
namespace UnitOfWork { 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便可。這個時候咱們就能夠藉助依賴注入。
咱們直接使用.net core 提供的依賴注入,依次注入DbContext、UnitOfWork和Repository。
//注入DbContext services.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
,從而確保每次請求共用同一個對象。如何理解呢?就是整個調用鏈上的須要注入的同類型對象,使用是同一個類型實例。
下面咱們就來實際看一看如何使用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便可完成對多個倉儲的持久化操做。
對於Uow模式,有不少種實現方式,大多過於複雜抽象。EF和EF Core自己已經實現了Uow模式,因此在實現時,咱們應避免沒必要要的抽象來下降系統的複雜度。
最後,重申一下:
Uow模式是用來管理倉儲處理事務的,倉儲用來解耦的(領域層與基礎設施層)。而基於EF實現Uow模式的關鍵:確保Uow和Reopository之間共享同一個DbContext實例。
最後附上使用.Net Core和EF Core基於DDD分層思想實現的源碼: GitHub--UnitOfWork