现在项目的架构已经基本搭好了,需要完善应用层,将所需要的功能补充完整。由于我们的数据库中已经保存了诗和诗人的数据,这部分数据不需要通过我们的应用维护,我们需要的功能是针对诗和诗人的查询以及对诗的分类的维护。主要功能如下:
- 诗人查询:按姓名进行模糊查询
- 根据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