鼎鼎知识库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3、Specification Pattern模式解决查询问题.md 5.5KB

3 vuotta sitten
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # 从一段代码说起
  2. 通常在`Application`层的`IRequestHandler`中涉及到查询、排序、分页会有一大段代码。
  3. ```
  4. public class SomeRequest : IRequest<DDResponseWrapper<SomeRequestDto>>
  5. {
  6. //是否加载一对多关系
  7. public bool LoadChildren {get;set;}
  8. public bool IsPagingEnabled{get;set;}
  9. public int Page{get;set;}
  10. public int PageSize{get;set;}
  11. ...
  12. }
  13. public class SomeRequestDto
  14. {
  15. }
  16. public class SomeRequestHandler : IRequestHandler<SomeRequest, DDResponseWrapper<SomeRequestDto>>
  17. {
  18. private readonly IBreakerRepo _breakerRepo;
  19. public SomeRequestHandler(IBreakerRepo breakerRepo)
  20. {
  21. _breakerRepo = breakerRepo;
  22. }
  23. public asyn Task<DDResponseWrapper<SomeRequestDto>> Handle(SomeRequest request, CancellationTolen canellationTolen)
  24. {
  25. var breakers = _breakerRepo.GetAll();
  26. breakers = breakers.OrderBy(t=>t.Name);
  27. if(request.LoadChildren)
  28. {
  29. breakers = breakers.Include(t => t.BreakerData);
  30. }
  31. if(request.IsPagingEnablled)
  32. {
  33. breakers = breakers.Skip(request.Page).Take(request.PageSize);
  34. }
  35. if(!string.IsNullOrEmpty(request.Name))
  36. {
  37. breakers = breakers.Where(t=>t.Name.Contains(request.Name));
  38. }
  39. ......
  40. }
  41. }
  42. ```
  43. > 是否可以把这部分逻辑放到基础设施层、或者领域层呢?然后通过调用`var breakers =await _breakerRepo.ListAsync(request)`替换以上逻辑?在`DDD`中,有一种`Specification Patter`模式特别适合这种场景,可以把相关逻辑封装在领域层,这样根据模块化,也更方便单元测试。
  44. ```
  45. //在领域层定义Specification<T>
  46. public class CustomerSpec : Specification<Customer>
  47. {
  48. public CustomerSpec(CustomerFilter filter)
  49. {
  50. ...
  51. }
  52. }
  53. //应用
  54. var spec = new CustomerSpec();
  55. var breakers = await _breakerRepo.ListAsync(spec);
  56. ```
  57. `Ardalis.Specification`遵循了`Specification Pattern`,很好地解决了上述问题。
  58. ```
  59. <PackageReference Include="Ardalis.Specification" Version="4.2.0" />
  60. <PackageReference Include="Ardalis.Specification.EntityFrameworkCore" Version="4.2.0" />
  61. ```
  62. # 领域层
  63. 一对多关系
  64. ```
  65. public class Customer : IAggregateRoot
  66. {
  67. public int Id { get; private set; }
  68. public string Name { get; private set; }
  69. public string Email { get; private set; }
  70. public string Address { get; private set; }
  71. public IEnumerable<Store> Stores => _stores.AsEnumerable();
  72. private readonly List<Store> _stores = new List<Store>();
  73. public Customer(string name, string email, string address)
  74. {
  75. Guard.Against.NullOrEmpty(name, nameof(name));
  76. Guard.Against.NullOrEmpty(email, nameof(email));
  77. this.Name = name;
  78. this.Email = email;
  79. this.Address = address;
  80. }
  81. ......
  82. }
  83. public class Store
  84. {
  85. public int Id { get; private set; }
  86. public string Name { get; private set; }
  87. public string Address { get; private set; }
  88. public int CustomerId { get; private set; }
  89. public Store(string name, string address)
  90. {
  91. Guard.Against.NullOrEmpty(name, nameof(name));
  92. this.Name = name;
  93. this.Address = address;
  94. }
  95. }
  96. ```
  97. 查询条件
  98. ```
  99. public class BaseFilter
  100. {
  101. public bool LoadChildren { get; set; }
  102. public bool IsPagingEnabled { get; set; }
  103. public int Page { get; set; }
  104. public int PageSize { get; set; }
  105. }
  106. public class CustomerFilter : BaseFilter
  107. {
  108. public string Name { get; set; }
  109. public string Email { get; set; }
  110. public string Address { get; set; }
  111. }
  112. ```
  113. 定义`Specification`相关
  114. ```
  115. namespace Sample.Core.Specifications
  116. {
  117. public class CustomerSpec : Specification<Customer>
  118. {
  119. public CustomerSpec(CustomerFilter filter)
  120. {
  121. Query.OrderBy(t => t.Name)
  122. .ThenByDescending(t => t.Address);
  123. if (filter.LoadChildren)
  124. Query.Include(x => x.Stores);
  125. if (filter.IsPagingEnabled)
  126. Query.Skip(PaginationHelper.CalculateSkip(filter))
  127. .Take(PaginationHelper.CalculateTake(filter));
  128. if (!string.IsNullOrEmpty(filter.Name))
  129. Query.Where(t => t.Name == filter.Name);
  130. if (!string.IsNullOrEmpty(filter.Email))
  131. Query.Where(x => x.Email == filter.Email);
  132. if (!string.IsNullOrEmpty(filter.Address))
  133. Query.Search(x => x.Address, "%" + filter.Address + "%");
  134. }
  135. }
  136. }
  137. ```
  138. 实现`Ardalis.Specification`的一个接口
  139. ```
  140. using Ardalis.Specification;
  141. namespace Sample.Core.Interfaces
  142. {
  143. public interface IRepository<T> : IRepositoryBase<T> where T : class, IAggregateRoot
  144. {
  145. }
  146. }
  147. ```
  148. # 基础设施层
  149. `IRepository`的实现
  150. ```
  151. using Ardalis.Specification.EntityFrameworkCore;
  152. namespace Sample.Infra.Data
  153. {
  154. public class MyRepository<T> : RepositoryBase<T>, IRepository<T> where T : class, IAggregateRoot
  155. {
  156. private readonly SampleDbContext dbContext;
  157. public MyRepository(SampleDbContext dbContext) : base(dbContext)
  158. {
  159. this.dbContext = dbContext;
  160. }
  161. // Not required to implement anything. Add additional functionalities if required.
  162. }
  163. }
  164. ```
  165. 注册
  166. ```
  167. services.AddScoped(typeof(MyRepository<>));
  168. ```
  169. # `Application`层或`UI`层调用
  170. ```
  171. private readonly IRepository<Customer> customerRepository;
  172. var spec = new CustomerSpec();
  173. var customers = await customerRepository.ListAsync(spec);
  174. ```
  175. 源码位置:`F:\demos\CleanArchitecture\UseSpecification`