领域驱动设计DDD ABP VNext 二:使用仓储 电脑版发表于:2022/5/21 15:42 [TOC] ### 领域驱动设计仓储介绍 在领域层和数据映射层之间进行中介,使用类似集合的接口来操作领域对象." (Martin Fowler)。 实际上,仓储用于领域对象在数据库(参阅实体)中的操作, 通常每个 聚合根 或不同的实体创建对应的仓储。 换句话说,存储库还处理数据并隐藏类似于DAO的查询。但是,它处于更高的层次,更接近应用程序的业务逻辑。因为我们是领域驱动设计核心在领域而不是数据库,以领域为中心设计,而不是数据为中心。 tn4>如果在应用层直接去调用基础设置层的数据库模块操作数据和三层架构没有区别。应该领域层定义仓储,基础设置层的数据库模块比如ef core层实现它。 然后通过领域层的仓储去调用,才是以领域为核心,不是以数据库为核心,因为是通过领域层来操作的数据库,领驱动设计是站在更高的位面来思考问题,不要一来就深入到繁杂的数据库这样的细节设计中去,操作数据库的只是基础设施层中用来操作数据库,做数据持久化的,至于持久化模块用什么就是基础设施层的事情了,其实领域层不需要去关心这些细节。 领域驱动设计DDD聚合模式的理解可以参考: https://www.tnblog.net/notebook/article/details/7146 ### 使用ABP VNext封装的通用仓储 abp vnext为每个聚合根或实体提供了 默认的通用仓储,通用仓储提供了一些开箱即用的标准CRUD功能。可以直接注入使用,里边基础的一些crud操作都已经封装好了直接使用即可,还是可以帮我们减少一些工作量。 tn4>这种没有什么技术含量的工作,就应该封装一次重复使用。而且还能更标准化,不然一个团队里边每个人按自己的想法命名写一套,就很乱了,这也是为什么一个项目讲过多个人之手后,变得....难以维护的原因,缺乏良好的架构和标准化。 **使用ABP VNext封装的通用仓储非常简单,直接注入即可** ``` using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; using WY.JBLand.Application.User.Dto; using WY.JBLand.Domain.User; using WY.JBLand.EntityFrameworkCore; namespace WY.JBLand.Application.User { public class UserInfoAppService : ApplicationService, IUserInfoAppService { private readonly WyJBLandDbContext _wyJBLandDbContext; /// 使用ABP VNext封装的通用仓储 private readonly IRepository<UserInfo> _userinfoRepository; public UserInfoAppService(WyJBLandDbContext wyJBLandDbContext, IRepository<UserInfo> userinfoRepository) { _wyJBLandDbContext = wyJBLandDbContext; _userinfoRepository = userinfoRepository; } public async Task<List<UserInfoDTO>> GetListAsync() { List<UserInfo> userInfos = await _userinfoRepository.GetListAsync(); var result = ObjectMapper.Map<List<UserInfo>, List<UserInfoDTO>>(userInfos); return null; } public List<UserInfoDTO> Get() { List<UserInfo> userInfos = _wyJBLandDbContext.UserInfo.ToList(); //使用AutoMapper进行对象转换 var result = ObjectMapper.Map<List<UserInfo>, List<UserInfoDTO>>(userInfos); return result; } } } ``` 还要注意基础设置层中上下文对象注入的时候加上options.AddDefaultRepositories(true);不然不会生成默认的仓储,注入是空会报错 ![](https://img.tnblog.net/arcimg/aojiancc2/93fa6e4886c741c9a2543dd94fe9937c.png) 还有一个坑是依赖注入要使用autofac,不然操作起来也是为空的哦 ![](https://img.tnblog.net/arcimg/aojiancc2/ea9cfe4ecee24d4db0810d775adcd2e1.png) ### 只读仓储 对于想要使用只读仓储的开发者,使用IReadOnlyRepository<TEntity, TKey> 与 IReadOnlyBasicRepository<Tentity, TKey>接口即可。 ### 自定义仓储 对于大多数情况, 默认通用仓储就足够了. 但是, 你可能会需要为实体创建自定义仓储类。 ABP不会强制你实现任何接口或从存储库的任何基类继承. 它可以只是一个简单的POCO类. 但是建议继承现有的仓储接口和类, 获得开箱即用的标准方法使你的工作更轻松。 #### 自定义仓储接口 首先在领域层定义一个仓储接口: ``` public interface IPersonRepository : IRepository<Person, Guid> { Task<Person> FindByNameAsync(string name); } ``` 此接口扩展了 IRepository<Person, Guid> 以使用已有的通用仓储功能. #### 实现自定义的仓储接口 在数据库访问层中实现上面写的接口 自定义存储库依赖于你使用的数据访问工具. 在此示例中, 我们将使用Entity Framework Core: ``` public class PersonRepository : EfCoreRepository<MyDbContext, Person, Guid>, IPersonRepository { public PersonRepository(IDbContextProvider<TestAppDbContext> dbContextProvider) : base(dbContextProvider) { } public async Task<Person> FindByNameAsync(string name) { return await DbContext.Set<Person>() .Where(p => p.Name == name) .FirstOrDefaultAsync(); } } ``` 大概的结构如下: ![](https://img.tnblog.net/arcimg/aojiancc2/afc4ec09297847f2811c0fa4bb6d51f9.png) #### IQueryable & 异步操作 IRepository 继承自 IQueryable,这意味着你可以直接使用LINQ扩展方法. 如上面的泛型仓储示例. 示例: 使用 Where(...) 和 ToList() 扩展方法 ``` var people = _personRepository .Where(p => p.Name.Contains(nameFilter)) .ToList(); ``` 通常想要使用`.ToListAsync()`, `.CountAsync()`.... 来编写真正的异步代码. 但在你使用标准的应用程序启动模板时会发现无法在应用层或领域层使用这些异步扩展方法,因为: - 这里异步方法不是标准LINQ方法,它们定义在Microsoft.EntityFrameworkCoreNuget包中。 - 标准模板应用层与领域层不引用EF Core 包以实现数据库提供程序独立. 根据你的需求和开发模式,你可以根据以下选项使用异步方法。 tn4>强烈建议使用异步方法! 在执行数据库查询时不要使用同步LINQ方法,以便能够开发可伸缩的应用程序.