从零开始进行ABP项目开发(六)——继续应用层的开发

现在项目的架构已经基本搭好了,需要完善应用层,将所需要的功能补充完整。由于我们的数据库中已经保存了诗和诗人的数据,这部分数据不需要通过我们的应用维护,我们需要的功能是针对诗和诗人的查询以及对诗的分类的维护。主要功能如下:

  • 诗人查询:按姓名进行模糊查询
  • 根据id获取诗人数据。
  • 诗查询: 按诗人进行查询;按关键字在标题进行模糊查询;按分类进行查询,如果分类是多个,就查询属于所有分类的诗。比如,如果查询条件是“唐诗三百首”和“五言诗”,那么结果应该是唐诗三百首中的五言诗。
  • 根据id获取诗数据。
  • 分类列表:列出所有分类。
  • 分类的增、删:可以增加和删除分类。
  • 查询某一首诗的所有分类。

上面的功能能够基本完成诗和诗人查询分类的功能。上述功能的定义在接口IPoemAppService中定义,在PoemAppService中实现。相关测测试用例在单元测试项目中实现。这些功能的输入输出采用DTO,DTO与实体通过AutoMap进行映射。下面先看一下DTO的定义和映射,然后对重点功能的实现做下说明。

DTO的定义

DTO与实体的映射

以诗为例,说明Dto与实体的映射,诗的实体中包括作者的定义,如下:

    /// <summary>
    /// 诗
    /// Volumn和Num是全唐诗中的卷和序号,
    /// </summary>
    public class Poem : Entity
    {
        ...
        /// <summary>
        /// 作者
        /// </summary>
        public virtual Poet Author { get; set; }
        ... 
    }

这里诗的作者是Poet对象,而我们在输出的时候,希望PoemDto中是诗人的名字,PoemDto的定义如下:

    [AutoMapFrom(typeof(Core.Poems.Poem))]
    public class PoemDto : EntityDto
    {
        public string Title { get; set; }

        public string Content { get; set; }

        public int PoetID { get; set; }

        public string AuthorName { get; set; }

        public string Comments { get; set; }

        public string Volumn { get; set; }

        public string Num { get; set; }
    }

这时,我们需要将实体中Author的Name映射到DTO的AuthorName字段。这需要在PoemApplicationModule模块启动前,加载映射的规则:

namespace ZL.Poem.Application
{
    [DependsOn(typeof(PoemCoreModule), typeof(AbpAutoMapperModule))]
    public class PoemApplicationModule : AbpModule
    {
        ...
        //这里加载Dto到实体的映射规则
        public override void PreInitialize()
        {
            Configuration.Modules.AbpAutoMapper().Configurators.Add(config =>
            {
                config.CreateMap<Core.Poems.Poem, PoemDto>()
                      .ForMember(u => u.AuthorName, options => options.MapFrom(o => o.Author.Name));
            });
        }
        ...
    }
}

在PreInitialize中定义了映射规则,将Author.Name映射到Dto的AuthorName字段。
除了PoemDto,还有PoetDto、CategoryDto、CategoryPoemDto与相应的实体映射,结构比较简单,这里不再描述。

作为输入的DTO

作为输入的DTO有三个:

  • PagedResultRequestDto: 用于定义分页查询输入的Dto。
  • SearchPoetDto: 用于定义诗人查询输入的Dto,从PagedResultRequestDto派生,并包括查询关键字。
  • SearchPoemDto: 用于定义诗查询输入的Dto,从PagedResultRequestDto派生,包括诗人姓名、查询关键字和分类数组。

代码如下:

using Abp.Application.Services.Dto;

namespace ZL.Poem.Application.Poems
{
    /// <summary>
    /// 分页查询传入参数
    /// </summary>
    public class PagedResultRequestDto : IPagedResultRequest
    {
        /// <summary>
        /// 跳过的记录数
        /// </summary>
        public int SkipCount { get; set; }

        /// <summary>
        /// 返回的最大记录数
        /// </summary>
        public int MaxResultCount { get; set; }
    }
}

namespace ZL.Poem.Application.Poems
{
    public class SearchPoetDto : PagedResultRequestDto
    {
        public string Keyword { get; set; }
    }
}
namespace ZL.Poem.Application.Poems
{
    public class SearchPoemDto : PagedResultRequestDto
    {
        public string Keyword { get; set; }

        public string AuthorName { get; set; }

        public string[] Categories { get; set; }
    }
}

功能实现说明

功能的定义在接口IPoemAppService中定义,在PoemAppService中实现。相关测测试用例在单元测试项目中实现。这里只对重点功能的实现进行说明,按照定义、实现、测试的顺序列出相关代码。

获取诗人分页

定义:

        /// <summary>
        /// 获取诗人分页
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        PagedResultDto<PoetDto> GetPagedPoets(PagedResultRequestDto dto);

输入PagedResultRequestDto。

实现:

        public PagedResultDto<PoetDto> GetPagedPoets(PagedResultRequestDto dto)
        {
            var count = _poetRepository.Count();
            var lst = _poetRepository.GetAll().OrderBy(o => o.Id).PageBy(dto).ToList();

            return new PagedResultDto<PoetDto>
            {
                TotalCount = count,
                Items = lst.MapTo<List<PoetDto>>()
            };
        }

输出是Abp定义的PagedResultDto<T>结构,包括两部分:记录总数和查询出的结果。

相关测试:

        [Fact]
        public void GetPoets_Test()
        {
            // Act
            var output = _appService.GetPagedPoets(new PagedResultRequestDto { MaxResultCount = 20, SkipCount = 0 });

            // Assert
            output.Items.Count.ShouldBe(2);
            output.Items[0].Name.ShouldBe("李白");
        }

测试的数据在TestDataBuilder中创建。

查询诗:

定义:


        /// <summary>
        /// 按条件查询诗,条件是关键字(模糊查询),作者(精确查询),分类(属于所有分类)
        /// </summary>
        /// <param name="dto"></param>
        /// <returns></returns>
        PagedResultDto<PoemDto> SearchPoems(SearchPoemDto dto);

输入的条件为三部分:

  • 关键字:模糊查询
  • 作者姓名:精确查询
  • 分类数组: 属于输入的所有分类

如果所有查询条件为空,查询结果为所有记录。如果查询条件不为空,查询结果是各查询条件的交集。

实现:

        public PagedResultDto<PoemDto> SearchPoems(SearchPoemDto dto)
        {
            var res = _poemRepository.GetAllIncluding(c => c.Author);
            //
            if (!string.IsNullOrEmpty(dto.Keyword))
            {
                res = res.Where(o => o.Title.Contains(dto.Keyword));
            }
            if (!string.IsNullOrEmpty(dto.AuthorName))
            {
                res = res.Where(o => o.Author.Name == dto.AuthorName);
            }
            if (dto.Categories != null)
            {
                foreach (var category in dto.Categories)
                {
                    res = res.Where(o => o.PoemCategories.Any(q => q.Category.CategoryName == category));
                }
            }

            var count = res.Count();
            var lst = res.OrderBy(o => o.Id).PageBy(dto).ToList();

            return new PagedResultDto<PoemDto>
            {
                TotalCount = count,
                Items = lst.MapTo<List<PoemDto>>()
            };
        }

测试:

        [Fact]
        public void SearchPomts_Test()
        {
            // Act
            var output = _appService.SearchPoems(new SearchPoemDto { AuthorName = "李白", MaxResultCount = 20, SkipCount = 0 });

            // Assert
            output.Items.Count.ShouldBe(2);

            output.Items[0].Title.ShouldBe("静夜思");
        }

        [Fact]
        public void SearchPomtWithKeys_Test()
        {
            // Act
            var output = _appService.SearchPoems(new SearchPoemDto { Keyword = "静", MaxResultCount = 20, SkipCount = 0 });

            // Assert
            output.Items.Count.ShouldBe(1);

            output.Items[0].Title.ShouldBe("静夜思");
        }
        [Fact]
        public void SearchPomtWithCategories_Test()
        {
            var output = _appService.SearchPoems(new SearchPoemDto { Categories = new string[] { "小学古诗" }, MaxResultCount = 20, SkipCount = 0 });
            output.Items.Count.ShouldBe(3);
        }

这个功能的组合比较多,因此需要相应的测试用例,这里没有列全,只列出了部分测试用例。

小结

通过本讲,我们了解到:

  • 如何实现DTO与实体间的映射
  • 使用PagedResultRequestDto定义分页查询条件
  • 使用PagedResultDto<T> 定义分页查询结果
  • 用单元测试检验功能

下一步工作

下一步我们要定义Web Api模块,将应用层的功能通过Web API 发布。然后,我们就可以开发客户端程序。

本文同步发布在我的个人网站 http://www.jiagoushi.cn

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容