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, 脚本如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using TestApp.BookStore.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;


namespace TestApp.BookStore.Authors
{
    public class EfCoreAuthorRepository
        : EfCoreRepository<BookStoreDbContext, Author, Guid>,
            IAuthorRepository
    {
        public EfCoreAuthorRepository(
            IDbContextProvider<BookStoreDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
        }

        public async Task<Author> FindByNameAsync(string name)
        {
            var dbSet = await GetDbSetAsync();
            return await dbSet.FirstOrDefaultAsync(author => author.Name == name);
        }

        public async Task<List<Author>> GetListAsync(
            int skipCount,
            int maxResultCount,
            string sorting,
            string filter = null)
        {
            var dbSet = await GetDbSetAsync();
            return await dbSet
                .WhereIf(
                    !filter.IsNullOrWhiteSpace(),
                    author => author.Name.Contains(filter)
                 )
                .OrderBy(sorting)
                .Skip(skipCount)
                .Take(maxResultCount)
                .ToListAsync();
        }
    }
}
  • 继承自 EfCoreRepository, 所以继承了标准repository的方法实现.
  • WhereIf 是ABP 框架的快捷扩展方法. 它仅当第一个条件满足时, 执行 Where 查询. (根据名字查询, 仅当 filter 不为空). 你可以不使用这个方法, 但这些快捷方法可以提高效率.
  • sorting 可以是一个字符串, 如 NameName ASC 或 Name DESC
posted @ 2022-03-03 16:36  HI_Hub_MI  阅读(408)  评论(0编辑  收藏  举报