ABP框架入门学习【进阶】(六)——作者模块领域层
在前面的章节中, 我们使用 ABP 框架轻松地构建了一些服务;
- 使用 CrudAppService 基类, 而不是为标准的增删改查操作手工开发应用服务(相当于直接使用普通ORM集成在底层的基础的增删改查).
- 使用 generic repositories 自动完成数据层功能(相当于集成在ORM之外的特殊处理的自定义的方法函数).
对于 "作者" 部分;
- 我们将要展示在需要的情况下, 如何 手工做一些事情.
- 我们将要实现一些 领域驱动设计 (DDD) 最佳实践.
TestApp.BookStore.Domain项目下新建Authors文件夹:
1、新建Author实体类,脚本如下:
using JetBrains.Annotations; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.Domain.Entities.Auditing; namespace TestApp.BookStore.Authors { public class Author : FullAuditedAggregateRoot<Guid> { public string Name { get; private set; } public DateTime BirthDate { get; set; } public string ShortBio { get; set; } private Author() { /* This constructor is for deserialization / ORM purpose */ } internal Author( Guid id, [NotNull] string name, DateTime birthDate, [CanBeNull] string shortBio = null) : base(id) { SetName(name); BirthDate = birthDate; ShortBio = shortBio; } internal Author ChangeName([NotNull] string name) { SetName(name); return this; } private void SetName([NotNull] string name) { Name = Check.NotNullOrWhiteSpace( name, nameof(name), maxLength: AuthorConsts.MaxNameLength ); } } }
- 由
FullAuditedAggregateRoot<Guid>
继承使得实体支持软删除 (指实体被删除时, 它并没有从数据库中被删除, 而只是被标记删除), 实体也具有了 审计 属性. Name
属性的private set
限制从类的外部设置这个属性. 有两种方法设置名字 (两种都进行了验证):- 当新建一个作者时, 通过构造器.
- 使用
ChangeName
方法更新名字.
构造器
和ChangeName
方法的访问级别是internal
, 强制这些方法只能在领域层由AuthorManager
使用. 稍后将对此进行解释.Check
类是一个ABP框架工具类, 用于检查方法参数 (如果参数非法会抛出ArgumentException
).
其中AuthorConsts是一个简单的类,在 Acme.BookStore.Domain.Shared
项目的 Authors
命名空间 (文件夹)中,脚本如下:
using System; using System.Collections.Generic; using System.Text; namespace TestApp.BookStore.Authors { public static class AuthorConsts { public const int MaxNameLength = 64; } }
2、AuthorManager: 领域服务,脚本如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using JetBrains.Annotations; using Volo.Abp; using Volo.Abp.Domain.Services; namespace TestApp.BookStore.Authors { public class AuthorManager : DomainService { private readonly IAuthorRepository _authorRepository; public AuthorManager(IAuthorRepository authorRepository) { _authorRepository = authorRepository; } public async Task<Author> CreateAsync( [NotNull] string name, DateTime birthDate, [CanBeNull] string shortBio = null) { Check.NotNullOrWhiteSpace(name, nameof(name)); var existingAuthor = await _authorRepository.FindByNameAsync(name); if (existingAuthor != null) { throw new AuthorAlreadyExistsException(name); } return new Author( GuidGenerator.Create(), name, birthDate, shortBio ); } public async Task ChangeNameAsync( [NotNull] Author author, [NotNull] string newName) { Check.NotNull(author, nameof(author)); Check.NotNullOrWhiteSpace(newName, nameof(newName)); var existingAuthor = await _authorRepository.FindByNameAsync(newName); if (existingAuthor != null && existingAuthor.Id != author.Id) { throw new AuthorAlreadyExistsException(newName); } author.ChangeName(newName); } } }
AuthorManager
强制使用一种可控的方式创建作者和修改作者的名字.
两个方法都检查是否存在同名用户, 如果存在, 抛出业务异常 AuthorAlreadyExistsException
, 这个异常定义在 TestApp.BookStore.Domain
项目 (Authors
文件夹中):
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Volo.Abp; namespace TestApp.BookStore.Authors { public class AuthorAlreadyExistsException : BusinessException { public AuthorAlreadyExistsException(string name) : base(BookStoreDomainErrorCodes.AuthorAlreadyExists) { WithData("name", name); } } }
BusinessException
是一个特殊的异常类型. 在需要时抛出领域相关异常是一个好的实践. ABP框架会自动处理它, 并且它也容易本地化.WithData(...)
方法提供额外的数据给异常对象, 这些数据将会在本地化中或出于其它一些目的被使用.
打开 TestApp.BookStore.Domain.Shared
项目中的 BookStoreDomainErrorCodes
并修改为:
namespace TestApp.BookStore; public static class BookStoreDomainErrorCodes { /* You can add your business exception error codes here, as constants */ public const string AuthorAlreadyExists = "BookStore:00001"; }
里面有用到自定义字符串,错误码(BookStore:00001),所以需要配置下本地化TestApp.BookStore.Domain.Shared
项目中的 Localization/BookStore/en.json
, 加入以下项:
"BookStore:00001": "There is already an author with the same name: {name}"
3、定义IAuthorRepository接口并集成IRepository
AuthorManager
注入了 IAuthorRepository
, 所以我们需要定义它。脚本如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Volo.Abp.Domain.Repositories; namespace TestApp.BookStore.Authors { public interface IAuthorRepository:IRepository<Author,Guid> { Task<Author> FindByNameAsync(string name); Task<List<Author>> GetListAsync( int skipCount, int maxResultCount, string sorting, string filter = null ); } }
最终新增和更新的文件如下图高亮所示:
4、Author实体配置数据库集成
DBContext配置
TestApp.BookStore.EntityFrameworkCore项目EntityFrameworkCore文件夹下 BookStoreDbContext
加入 DbSet
属性:
public DbSet<Author> Authors { get; set; }
OnModelCreating
方法, 加入以下代码:
builder.Entity<Author>(b => { b.ToTable(BookStoreConsts.DbTablePrefix + "Authors", BookStoreConsts.DbSchema); b.ConfigureByConvention(); b.Property(x => x.Name).IsRequired().HasMaxLength(AuthorConsts.MaxNameLength); b.HasIndex(x => x.Name); });
5、创建数据库迁移
使用EF Core Code First Migrations生成数据库,在包管理控制台(PMC)中使用
Add-Migration Added_Authors -c BookStoreDbContext
和 Update-Database -Context BookStoreDbContext
命令
6、实现TestApp.BookStore.Domain项目下自定义接口IAuthorRepository
在 TestAppBookStore.EntityFrameworkCore
项目 (Authors
文件夹)中创建一个新类 EfCoreAuthorRepository
, 脚本如下:
- 继承自
EfCoreRepository
, 所以继承了标准repository的方法实现. WhereIf
是ABP 框架的快捷扩展方法. 它仅当第一个条件满足时, 执行Where
查询. (根据名字查询, 仅当 filter 不为空). 你可以不使用这个方法, 但这些快捷方法可以提高效率.sorting
可以是一个字符串, 如Name
,Name ASC
或Name DESC
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 地球OL攻略 —— 某应届生求职总结
· 提示词工程——AI应用必不可少的技术
· Open-Sora 2.0 重磅开源!
· 周边上新:园子的第一款马克杯温暖上架