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的几个要点:

  1. UOW跟踪变化

  2. UOW维护了一个变更列表

  3. UOW将跟踪到的已变更的对象保存到变更列表中

  4. UOW借助事务一次性提交变更列表中的所有更改

  5. 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接口。

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即可。这个时候我们就可以借助依赖注入。

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跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/323887.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

表扬几位积极的同学!

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。”这几天太忙了&#xff0c;几天才回家一次&#xff0c;总有忙不完的活在干。4班这几天同学们表现还不错&#xff0c;布置的作业都能按时完成&#xff0c;课堂上也比较活跃&#xff0c;比…

中山纪念中学培训15天总结

比赛 2018/7/6-纪中某C组题【jzoj1192,jzoj1397,jzoj1736】 上来就集体爆零 2018/7/7-纪中某C组题【jzoj1494,jzoj1495,jzoj1496,jzoj1497】 还比较好 2018/7/8-纪中某C组题【jzoj1619,jzoj1620,jzoj1621,jzoj1622】 发现了dp方面的不足 2018/7/9-纪中某B组题【jzoj1503…

汇编语言(十五)之找出两个数组中的相同元素

找出两个数组中的相同元素&#xff0c;并且输出 程序运行&#xff1a; 代码&#xff1a; datas segmentA dw -1,-2, 3, 4, 5, 6, 7, 8, 9, 10,-11, 12, 13, 14, 15ACount dw ($-A)/2B dw -1, 2,-3, 4,-5, 6,-7, 8, 9,-10, 11,-12, 13,-14, 15,16,17,18,…

如何编写更好的SQL查询:终极指南-第一部分

结构化查询语言&#xff08;SQL&#xff09;是数据挖掘分析行业不可或缺的一项技能&#xff0c;总的来说&#xff0c;学习这个技能是比较容易的。对于SQL来说&#xff0c;编写查询语句只是第一步&#xff0c;确保查询语句高效并且适合于你的数据库操作工作&#xff0c;才是最重…

汇编语言(十六)之三数值求和

输入A、B、C三个数&#xff0c;如果存在一个数为0&#xff0c;则全部清零&#xff0c;否则求和输出 程序运行&#xff1a; 代码&#xff1a; datas segmentA dw 1B dw 0D dw 3S dw 0a_string db 0ffh, 0 ,100 dup(?)b_string db 0ffh, 0 ,100 dup(?…

回忆四班的那些事儿~

10“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。”今天&#xff0c;谈谈四班的那些事儿吧~对于四班&#xff0c;回忆满满&#xff0c;根本忘却不了。和别的班一样&#xff0c;一个班内总有那么几个调皮捣蛋的孩子&#xff0c;只是四班…

C#使用Xamarin开发可移植移动应用进阶篇(7.使用布局渲染器,修改默认布局),附源码

本篇..基本可以算是Xamarin在应用开发过程中的核心了..真的很很很重要.. 想学习的..想用的..建议仔细阅读..嗯..打酱油的 ..快速滑倒下面点个推荐 - - 哈哈哈... 今天的学习内容? 也只讲一个,关于Xamarin.Forms针对各个平台如何进行可定制化的布局操作. 也就是针对某个平台…

汇编语言(十七)之判断三个数是否相等

输入三个数&#xff0c;计算相等数的个数 程序运行&#xff1a; 代码&#xff1a; datas segmenta db 0ffh, 0 ,100 dup(?)b db 0ffh, 0 ,100 dup(?)d db 0ffh, 0 ,100 dup(?) ARRAY dw 1,2,2inputA db input a$inputB db 0dh,0ah,i…

你想象中的实习是什么样的?

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;????雄雄的小课堂????。”今天感谢学长小李同学投稿的文章&#xff0c;在校外实习一年&#xff0c;可谓收获满满&#xff0c;下面就是李心焱同学的实习感悟&#xff0c;请欣赏&#xff01;实习…

汇编语言(十八)之求两个数的最大公约数

输入两个数&#xff0c;使用辗转相除法求最大公约数 程序运行&#xff1a; 代码&#xff1a; datas segmentM_max_len db 0ffhM_len db 0M_string db 100 dup(?)M dw 0 N_max_len db 0ffhN_len db 0N_string db…

Orleans—一些概念

这个文章聊一聊Orleans的概念.以下文章大部分翻译自官方教程,还有一些结合实际的应用经验,并对以前文章留下的坑进行填平.如果有哪个坑没有填,还请告诉我. Grain的生命周期: 一个Grain在逻辑上是永远存在的,并在逻辑上拥有一个不变的标识.程序的代码永远不会去创造或者销毁一个…

使用jdbc连接mysql数据库代码示例

“大家好&#xff0c;我是雄雄&#xff0c;欢迎关注微信公众号&#xff1a;雄雄的小课堂。”最近&#xff0c;正好在看jdbc&#xff0c;于是就整理了一份增删改查的案例&#xff0c;记录记录&#xff0c;以备后用&#xff01;&#xff01;java编辑器&#xff1a;myeclipse![10]…

POJ1958-Strange Towers of Hanoi【递推】

正题 题目链接&#xff1a;http://poj.org/problem?id1958 大意 有4根柱子的汉诺塔&#xff0c;有n个盘&#xff0c;求移动次数 解题思路 首先我们定义只有三根柱子时有n个盘的话移动次数是dndn&#xff0c;然后dndn−1∗21dndn−1∗21定义有四根柱子时有n个盘移动次数为fn…

[上海站] 微软Azure AspNetCore微服务实战

活动介绍 2017官方发布了EshopOnContainers的微服务项目&#xff0c;其结合了.Net Core、Azure、Docker等众多优秀的技术&#xff0c;为开发者们抛砖引玉&#xff0c;走进微服务的大门。 本次分享&#xff0c;我们将结合实际毫无保留的从代码上解析微服务架构&#xff0c;以实…

汇编语言(十九)之删除数组中的元素

在数组中删除所有-1元素&#xff0c;然后输出删除后的长度 程序运行&#xff1a; 代码&#xff1a; datas segmentLink dw 15 ,1,2,3,4,5,6,-9,-1,3,4,5,3,-1,-2,3ouput db Link length:$ datas endsstacks segment stackdb 100h dup(?)stacks endscodes segmentassume cs…

Linux+.NetCore+Nginx搭建集群

本篇和大家分享的是LinuxNetCoreNginx搭建负载集群&#xff0c;对于netcore2.0发布后&#xff0c;我一直在看官网的文档并学习&#xff0c;关注有哪些新增的东西&#xff0c;我&#xff0c;一个从1.0到2.0的跟随者这里只总结一句话&#xff1a;2.0版本&#xff0c;api更多&…

老师 累了,我们 泪了!

“以下文章来自一位不愿意透露姓名的同学所写&#xff1a;”呀呼 “老穆头” 没错是我们的穆老师悄悄往我们班探了探头瞧了瞧我们&#xff0c; 我们依旧对他那么热情&#xff0c;紧随其后老佟往门口一站 教室里立马严肃起来 她是我们级部出了名没人敢惹的老佟&#xff08;佟老…

汇编语言(二十)之分类统计字符个数

输入一串字符串&#xff0c;分别统计英文字符&#xff0c;数字字符和其他字符的个数 程序运行&#xff1a; 代码&#xff1a; datas segmentline_max_length db 0ffhline db 0, 100h dup(?)letter_count dw 0digit_count dw 0other_count …

.NET Core+Selenium+Github+Travis CI =amp;gt; SiteHistory

前言 总是三分钟热度的我折腾了一个可以每天自动截取指定网站页面并保存到Github的项目SiteHistory&#xff0c;感觉挺好(每次都这样 frameborder"0" scrolling"no" style"border-width: initial; border-style: none; width: 25px; height: 26px;&qu…

汇编语言(二十一)之数值交换与自增

输入两个数&#xff0c;如果第一个数为偶数第二个数为奇数&#xff0c;互换位置&#xff1b;如果两个数都是奇数&#xff0c;则两个数都加一 程序运行&#xff1a; 代码&#xff1a; datas segmentA dw 0 B dw 0a_string db 0ffh, 0 ,100h dup(?)b_string …