规约模式用于为实体和其他业务对象定义 命名、可复用、可组合和可测试的过滤器 .
规约是领域层的一部分.
定义规约
你可以创建一个由 Specification 派生的新规约类.
1 2 3 4 5 6 7 8 9 10
| namespace MyProject { public class Age18PlusCustomerSpecification : Specification<Customer> { public override Expression<Func<Customer, bool>> ToExpression() { return c => c.Age >= 18; } } }
|
你也可以直接实现ISpecification接口,但是基类Specification做了大量简化.
使用规约
这里有两种常见的规约用例.
IsSatisfiedBy
IsSatisfiedBy 方法可以用于检查单个对象是否满足规约.
例如:如果顾客不满足年龄规定,则抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| namespace MyProject { public class CustomerService : ITransientDependency { public async Task BuyAlcohol(Customer customer) { if (!new Age18PlusCustomerSpecification().IsSatisfiedBy(customer)) { throw new Exception( "这位顾客不满足年龄规定!" ); } } } }
|
ToExpression & Repositories
ToExpression() 方法可用于将规约转化为表达式.通过这种方式,你可以使用规约在数据库查询时过滤实体.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| namespace MyProject { public class CustomerManager : DomainService, ITransientDependency { private readonly IRepository<Customer, Guid> _customerRepository;
public CustomerManager(IRepository<Customer, Guid> customerRepository) { _customerRepository = customerRepository; }
public async Task<List<Customer>> GetCustomersCanBuyAlcohol() { var queryable = await _customerRepository.GetQueryableAsync(); var query = queryable.Where( new Age18PlusCustomerSpecification().ToExpression() ); return await AsyncExecuter.ToListAsync(query); } } }
|
规约被正确地转换为SQL/数据库查询语句,并且在DBMS端高效执行.虽然它与规约无关,但如果你想了解有关 AsyncExecuter 的更多信息,请参阅仓储文档.
实际上,没有必要使用 ToExpression() 方法,因为规约会自动转换为表达式.这也会起作用:
1 2 3 4
| var queryable = await _customerRepository.GetQueryableAsync(); var query = queryable.Where( new Age18PlusCustomerSpecification() );
|
编写规约
规约有一个强大的功能是,它们可以与And、Or、Not以及AndNot扩展方法组合使用.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| namespace MyProject { public class PremiumCustomerSpecification : Specification<Customer> { public override Expression<Func<Customer, bool>> ToExpression() { return (customer) => (customer.Balance >= 100000); } } } 你可以将 PremiumCustomerSpecification 和 Age18PlusCustomerSpecification 结合起来,查询优质成人顾客的数量,如下所示:
~~~C# namespace MyProject { public class CustomerManager : DomainService, ITransientDependency { private readonly IRepository<Customer, Guid> _customerRepository;
public CustomerManager(IRepository<Customer, Guid> customerRepository) { _customerRepository = customerRepository; }
public async Task<int> GetAdultPremiumCustomerCountAsync() { return await _customerRepository.CountAsync( new Age18PlusCustomerSpecification() .And(new PremiumCustomerSpecification()).ToExpression() ); } } }
|
如果你想让这个组合成为一个可复用的规约,你可以创建这样一个组合的规约类,它派生自AndSpecification:
1 2 3 4 5 6 7 8 9 10 11 12 13
| using Volo.Abp.Specifications;
namespace MyProject { public class AdultPremiumCustomerSpecification : AndSpecification<Customer> { public AdultPremiumCustomerSpecification() : base(new Age18PlusCustomerSpecification(), new PremiumCustomerSpecification()) { } } }
|
何时使用规约
使用规约的一些好处:
- 可复用:假设你在代码库的许多地方都需要用到优质顾客过滤器.如果使用表达式而不创建规约,那么如果以后更改“优质顾客”的定义会发生什么?假设你想将最低余额从100000美元更改为250000美元,并添加另一个条件,成为顾客超过3年.如果使用了规约,只需修改一个类.如果在任何其他地方重复(复制/粘贴)相同的表达式,则需要更改所有的表达式.
- 可组合:可以组合多个规约来创建新规约.这是另一种可复用性.
- 可命名:PremiumCustomerSpecification 更好地解释了为什么使用规约,而不是复杂的表达式.因此,如果在你的业务中使用了一个有意义的表达式,请考虑使用规约.
- 可测试:规约是一个单独(且易于)测试的对象.