{"id":13458181,"url":"https://github.com/sheng-jie/UnitOfWork","last_synced_at":"2025-03-24T15:30:57.643Z","repository":{"id":119682169,"uuid":"99389038","full_name":"sheng-jie/UnitOfWork","owner":"sheng-jie","description":"DDD中实体、聚合、仓储、UOW相关实现。","archived":false,"fork":false,"pushed_at":"2024-03-25T06:27:28.000Z","size":38,"stargazers_count":120,"open_issues_count":1,"forks_count":45,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-10-29T03:32:32.888Z","etag":null,"topics":["aggregate","ddd","repository-pattern","unitofworkpattern"],"latest_commit_sha":null,"homepage":"http://www.jianshu.com/p/6f22d7bcb87a","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sheng-jie.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-08-05T00:34:54.000Z","updated_at":"2024-09-18T20:41:31.000Z","dependencies_parsed_at":null,"dependency_job_id":"4921db23-407a-4bfb-8789-f54be72d0d0b","html_url":"https://github.com/sheng-jie/UnitOfWork","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheng-jie%2FUnitOfWork","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheng-jie%2FUnitOfWork/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheng-jie%2FUnitOfWork/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sheng-jie%2FUnitOfWork/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sheng-jie","download_url":"https://codeload.github.com/sheng-jie/UnitOfWork/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245297924,"owners_count":20592501,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aggregate","ddd","repository-pattern","unitofworkpattern"],"created_at":"2024-07-31T09:00:46.506Z","updated_at":"2025-03-24T15:30:57.232Z","avatar_url":"https://github.com/sheng-jie.png","language":"C#","funding_links":[],"categories":["C\\#"],"sub_categories":[],"readme":"# 1. 引言\n\u003e Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.\n *[Unit of Work](https://martinfowler.com/eaaCatalog/unitOfWork.html) --Martin Fowler*\n\nUnit Of Work模式，由马丁大叔提出，是一种数据访问模式。UOW模式的作用是在业务用例的操作中跟踪对象的所有更改（增加、删除和更新），并将所有更改的对象保存在其维护的列表中。在业务用例的终点，通过事务，**一次性提交所有更改**，以确保数据的完整性和有效性。总而言之，UOW协调这些对象的持久化及并发问题。\n\n# 2. UOW的本质\n通过以上的介绍，我们可以总结出实现UOW的几个要点：\n1. UOW跟踪变化\n2. UOW维护了一个变更列表\n3. UOW将跟踪到的已变更的对象保存到变更列表中\n4. UOW借助事务一次性提交变更列表中的所有更改\n5. UOW处理并发\n\n而对于这些要点，EF中的DBContext已经实现了。\n\n# 3. EF中的UOW\n\n每个`DbContext`类型实例都有一个`ChangeTracker`用来跟踪记录实体的变化。当调用`SaveChanges`时，所有的更改将通过事务一次性提交到数据库。\n\n我们直接看个EF Core的测试用例：\n```\npublic ApplicationDbContext InMemorySqliteTestDbContext\n{\n    get\n    {\n        // In-memory database only exists while the connection is open\n        var connection = new SqliteConnection(\"DataSource=:memory:\");\n        connection.Open();\n\n        var options = new DbContextOptionsBuilder\u003cApplicationDbContext\u003e()\n            .UseSqlite(connection)\n            .Options;\n\n        var context = new ApplicationDbContext(options);\n        context.Database.EnsureCreated();\n        return context;\n    }\n}\n\n[Fact]\npublic void Test_Ef_Implemented_Uow()\n{\n    //新增用户\n    var user = new ApplicationUser()\n    {\n        UserName = \"shengjie\",\n        Email = \"ysjshengjie@qq.com\"\n    };\n\n    InMemorySqliteTestDbContext.Users.Add(user);\n\n    //创建用户对应客户\n    var customer = new Customer()\n    {\n        ApplicationUser = user,\n        NickName = \"圣杰\"\n    };\n\n    InMemorySqliteTestDbContext.Customers.Add(customer);\n\n    //添加地址\n    var address = new Address(\"广东省\", \"深圳市\", \"福田区\", \"下沙街道\", \"圣杰\", \"135****9309\");\n\n    InMemorySqliteTestDbContext.Addresses.Add(address);\n\n    //修改客户对象的派送地址\n    customer.AddShippingAddress(address);\n\n    InMemoryTestDbContext.Entry(customer).State = EntityState.Modified;\n\n    //保存\n    var changes = InMemorySqliteTestDbContext.SaveChanges();\n\n    Assert.Equal(3, changes);\n\n    var savedCustomer = InMemorySqliteTestDbContext.Customers\n        .FirstOrDefault(c =\u003e c.NickName == \"圣杰\");\n\n    Assert.Equal(\"shengjie\", savedCustomer.ApplicationUser.UserName);\n\n    Assert.Equal(customer.ApplicationUserId, savedCustomer.ApplicationUserId);\n\n    Assert.Equal(1, savedCustomer.ShippingAddresses.Count);\n}\n```\n首先这个用例是绿色通过的。该测试用例中我们添加了一个User，并为User创建对应的Customer，同时为Customer添加一条Address。从代码中我们可以看出仅做了一次保存，新增加的User、Customer、Address对象都成功持久化到了内存数据库中。从而证明EF Core是实现了Uow模式的。但很显然应用程序与基础设施层高度耦合，那如何解耦呢？继续往下看。\n\n# 4. DDD中的UOW\n那既然EF Core已经实现了Uow模式，我们还有必要自行实现一套Uow模式吗？这就视具体情况而定了，如果你的项目简单的增删改查就搞定了的，就不用折腾了。\n\n在DDD中，我们会借助仓储模式来实现领域对象的持久化。仓储只关注于单一聚合的持久化，而业务用例却常常会涉及多个聚合的更改，为了确保业务用例的一致型，我们需要引入事务管理，而事务管理是应用服务层的关注点。我们如何在应用服务层来管理事务呢？借助UOW。这样就形成了一条链：Uow-\u003e仓储--\u003e聚合--\u003e实体和值对象。即Uow负责管理仓储处理事务，仓储管理单一聚合，聚合又由实体和值对象组成。\n\n下面我们就先来定义实体和值对象，这里我们使用层超类型。\n\n## 4.1. 定义实体\n```\n    /// \u003csummary\u003e\n    /// A shortcut of \u003csee cref=\"IEntity{TPrimaryKey}\"/\u003e for most used primary key type (\u003csee cref=\"int\"/\u003e).\n    /// \u003c/summary\u003e\n    public interface IEntity : IEntity\u003cint\u003e\n    {\n\n    }\n\n    /// \u003csummary\u003e\n    /// Defines interface for base entity type. All entities in the system must implement this interface.\n    /// \u003c/summary\u003e\n    /// \u003ctypeparam name=\"TPrimaryKey\"\u003eType of the primary key of the entity\u003c/typeparam\u003e\n    public interface IEntity\u003cTPrimaryKey\u003e\n    {\n        /// \u003csummary\u003e\n        /// Unique identifier for this entity.\n        /// \u003c/summary\u003e\n        TPrimaryKey Id { get; set; }\n    }\n```\n\n## 4.2. 定义聚合\n```\nnamespace UnitOfWork\n{\n    public interface IAggregateRoot : IAggregateRoot\u003cint\u003e, IEntity\n    {\n\n    }\n\n    public interface IAggregateRoot\u003cTPrimaryKey\u003e : IEntity\u003cTPrimaryKey\u003e\n    {\n\n    }\n}\n```\n\n## 4.3. 定义泛型仓储\n```\nnamespace UnitOfWork\n{\n    public interface IRepository\u003cTEntity\u003e : IRepository\u003cTEntity, int\u003e\n        where TEntity : class, IEntity, IAggregateRoot\n    {\n\n    }\n\n    public interface IRepository\u003cTEntity, TPrimaryKey\u003e\n        where TEntity : class, IEntity\u003cTPrimaryKey\u003e, IAggregateRoot\u003cTPrimaryKey\u003e\n    {        \n        IQueryable\u003cTEntity\u003e GetAll();\n\n        TEntity Get(TPrimaryKey id);\n\n        TEntity FirstOrDefault(TPrimaryKey id);\n\n        TEntity Insert(TEntity entity);\n        \n        TEntity Update(TEntity entity);\n\n        void Delete(TEntity entity);\n\n        void Delete(TPrimaryKey id);\n    }\n}\n```\n因为仓储是管理聚合的，所以我们需要限制泛型参数为实现`IAggregateRoot`的类。\n\n## 4.4. 实现泛型仓储\n```\namespace UnitOfWork.Repositories\n{\n    public class EfCoreRepository\u003cTEntity\u003e\n        : EfCoreRepository\u003cTEntity, int\u003e, IRepository\u003cTEntity\u003e\n        where TEntity : class, IEntity, IAggregateRoot\n    {\n        public EfCoreRepository(UnitOfWorkDbContext dbDbContext) : base(dbDbContext)\n        {\n        }\n    }\n\n    public class EfCoreRepository\u003cTEntity, TPrimaryKey\u003e\n        : IRepository\u003cTEntity, TPrimaryKey\u003e\n        where TEntity : class, IEntity\u003cTPrimaryKey\u003e, IAggregateRoot\u003cTPrimaryKey\u003e\n    {\n        private readonly UnitOfWorkDbContext _dbContext;\n\n        public virtual DbSet\u003cTEntity\u003e Table =\u003e _dbContext.Set\u003cTEntity\u003e();\n\n        public EfCoreRepository(UnitOfWorkDbContext dbDbContext)\n        {\n            _dbContext = dbDbContext;\n        }\n\n        public IQueryable\u003cTEntity\u003e GetAll()\n        {\n            return Table.AsQueryable();\n        }\n\n        public TEntity Insert(TEntity entity)\n        {\n            var newEntity = Table.Add(entity).Entity;\n            _dbContext.SaveChanges();\n            return newEntity;\n        }\n\n        public TEntity Update(TEntity entity)\n        {\n            AttachIfNot(entity);\n            _dbContext.Entry(entity).State = EntityState.Modified;\n\n            _dbContext.SaveChanges();\n\n            return entity;\n        }\n\n        public void Delete(TEntity entity)\n        {\n            AttachIfNot(entity);\n            Table.Remove(entity);\n\n           _dbContext.SaveChanges();\n        }\n\n        public void Delete(TPrimaryKey id)\n        {\n            var entity = GetFromChangeTrackerOrNull(id);\n            if (entity != null)\n            {\n                Delete(entity);\n                return;\n            }\n\n            entity = FirstOrDefault(id);\n            if (entity != null)\n            {\n                Delete(entity);\n                return;\n            }\n        }\n\n        protected virtual void AttachIfNot(TEntity entity)\n        {\n            var entry = _dbContext.ChangeTracker.Entries().FirstOrDefault(ent =\u003e ent.Entity == entity);\n            if (entry != null)\n            {\n                return;\n            }\n\n            Table.Attach(entity);\n        }\n\n        private TEntity GetFromChangeTrackerOrNull(TPrimaryKey id)\n        {\n            var entry = _dbContext.ChangeTracker.Entries()\n                .FirstOrDefault(\n                    ent =\u003e\n                        ent.Entity is TEntity \u0026\u0026\n                        EqualityComparer\u003cTPrimaryKey\u003e.Default.Equals(id, ((TEntity)ent.Entity).Id)\n                );\n\n            return entry?.Entity as TEntity;\n        }\n    }\n}\n```\n因为我们直接使用EF Core进行持久化，所以我们直接通过构造函数初始化DbContex实例。同时，我们注意到`Insert、Update、Delete`方法都显式的调用了`SaveChanges`方法。\n\n至此，我们完成了从实体到聚合再到仓储的定义和实现，万事俱备，只欠Uow。\n\n## 4.5. 实现UOW\n通过第3节的说明我们已经知道，EF Core已经实现了UOW模式。而为了确保领域层透明的进行持久化，我们对其进行了更高一层的抽象，实现了仓储模式。但这似乎引入了另外一个问题，因为仓储是管理单一聚合的，每次做增删改时都显式的提交了更改（调用了SaveChanges），在处理多个聚合时，就无法利用DbContext进行批量提交了。那该如何是好？一不做二不休，我们再对其进行一层抽象，抽离保存接口，这也就是Uow的核心接口方法。\n我们抽离`SaveChanges`方法，定义`IUnitOfWork`接口。\n```\nnamespace UnitOfWork\n{\n    public interface IUnitOfWork\n    {\n        int SaveChanges();\n    }\n}\n```\n因为我们是基于EFCore实现Uow的，所以我们只需要依赖DbContex，就可以实现批量提交。实现也很简单：\n```\nnamespace UnitOfWork\n{\n    public class UnitOfWork\u003cTDbContext\u003e : IUnitOfWork where TDbContext : DbContext\n    {\n        private readonly TDbContext _dbContext;\n\n        public UnitOfWork(TDbContext context)\n        {\n            _dbContext = context ?? throw new ArgumentNullException(nameof(context));\n        }\n\n        public int SaveChanges()\n        {\n            return _dbContext.SaveChanges();\n        }\n    }\n}\n```\n既然Uow接手保存操作，自然我们需要：**注释掉EfCoreRepository中Insert、Update、Delete方法中的显式保存调用`_dbContext.SaveChanges();`**。\n\n那如何确保操作多个仓储时，最终能够一次性提交所有呢？\n\n**确保Uow和仓储共用同一个DbContex即可**。这个时候我们就可以借助依赖注入。\n\n## 4.6. 依赖注入\n我们直接使用.net core 提供的依赖注入，依次注入DbContext、UnitOfWork和Repository。\n```\n//注入DbContext\nservices.AddDbContext\u003cUnitOfWorkDbContext\u003e(\n    options =\u003eoptions.UseSqlServer(\n    Configuration.GetConnectionString(\"DefaultConnection\")));\n\n//注入Uow依赖\nservices.AddScoped\u003cIUnitOfWork, UnitOfWork\u003cUnitOfWorkDbContext\u003e\u003e();\n\n//注入泛型仓储\nservices.AddTransient(typeof(IRepository\u003c\u003e), typeof(EfCoreRepository\u003c\u003e));\nservices.AddTransient(typeof(IRepository\u003c,\u003e), typeof(EfCoreRepository\u003c,\u003e));\n```\n这里我们限定了DbContext和UnitOfWork的生命周期为`Scoped`，从而确保每次请求共用同一个对象。如何理解呢？就是**整个调用链**上的需要注入的同类型对象，使用是同一个类型实例。\n## 4.7. 使用UOW\n下面我们就来实际看一看如何使用UOW，我们定义一个应用服务：\n```\nnamespace UnitOfWork.Customer\n{\n    public class CustomerAppService : ICustomerAppService\n    {\n        private readonly IUnitOfWork _unitOfWork;\n        private readonly IRepository\u003cCustomer\u003e _customerRepository;\n        private readonly IRepository\u003cShoppingCart.ShoppingCart\u003e _shoppingCartRepository;\n\n        public CustomerAppService(IRepository\u003cShoppingCart\u003e shoppingCartRepository, \n            IRepository\u003cCustomer\u003e customerRepository, IUnitOfWork unitOfWork)\n        {\n            _shoppingCartRepository = shoppingCartRepository;\n            _customerRepository = customerRepository;\n            _unitOfWork = unitOfWork;\n        }\n\n        public void CreateCustomer(Customer customer)\n        {\n            _customerRepository.Insert(customer);//创建客户\n\n            var cart = new ShoppingCart.ShoppingCart() {CustomerId = customer.Id};\n            _shoppingCartRepository.Insert(cart);//创建购物车\n            _unitOfWork.SaveChanges();\n        }\n\n        //....\n    }\n}\n```\n通过以上案例，我们可以看出，我们只需要通过构造函数依赖注入需要的仓储和Uow即可完成对多个仓储的持久化操作。\n\n# 5. 最后\n\n对于Uow模式，有很多种实现方式，大多过于复杂抽象。EF和EF Core本身已经实现了Uow模式，所以在实现时，我们应避免不必要的抽象来降低系统的复杂度。\n\n最后，重申一下：\n**Uow模式是用来管理仓储处理事务的，仓储用来解耦的（领域层与基础设施层）。而基于EF实现Uow模式的关键：确保Uow和Reopository之间共享同一个DbContext实例。**\n\n最后附上基于.Net Core和EF Core实现的源码： [GitHub--UnitOfWork](https://github.com/yanshengjie/UnitOfWork)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheng-jie%2FUnitOfWork","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsheng-jie%2FUnitOfWork","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsheng-jie%2FUnitOfWork/lists"}