安装
添加 包到你的项目. 如果当前文件夹是你的项目的根目录()时,你可以在命令行终端中使用 ABP CLI add package 命令:
假设你定义了如下的顾客实体:
using System;
using Volo.Abp.Domain.Entities;
namespace MyProject
{
public class Customer : AggregateRoot<Guid>
{
public string Name { get; set; }
public byte Age { get; set; }
public long Balance { get; set; }
public string Location { get; set; }
}
}
你可以创建一个由 Specification<Customer>
派生的新规约类.
例如:规定选择一个18岁以上的顾客
using System;
using System.Linq.Expressions;
using Volo.Abp.Specifications;
namespace MyProject
{
public class Age18PlusCustomerSpecification : Specification<Customer>
{
public override Expression<Func<Customer, bool>> ToExpression()
{
return c => c.Age >= 18;
}
}
}
你只需通过定义一个lambda来定义规约.
使用规约
这里有两种常见的规约用例.
IsSatisfiedBy
方法可以用于检查单个对象是否满足规约.
例如:如果顾客不满足年龄规定,则抛出异常
ToExpression()
方法可用于将规约转化为表达式.通过这种方式,你可以使用规约在数据库查询时过滤实体.
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
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);
}
}
}
实际上,没有必要使用 ToExpression()
方法,因为规约会自动转换为表达式.这也会起作用:
var queryable = await _customerRepository.GetQueryableAsync();
var query = queryable.Where(
new Age18PlusCustomerSpecification()
);
规约有一个强大的功能是,它们可以与And
、Or
、Not
以及AndNot
扩展方法组合使用.
你可以将 PremiumCustomerSpecification
和 Age18PlusCustomerSpecification
结合起来,查询优质成人顾客的数量,如下所示:
using System;
using System.Threading.Tasks;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Domain.Services;
using Volo.Abp.Specifications;
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
:
using Volo.Abp.Specifications;
namespace MyProject
{
public class AdultPremiumCustomerSpecification : AndSpecification<Customer>
{
public AdultPremiumCustomerSpecification()
: base(new Age18PlusCustomerSpecification(),
new PremiumCustomerSpecification())
{
}
}
}
现在,你就可以向下面一样重新编写 GetAdultPremiumCustomerCountAsync
方法:
讨论
虽然规约模式通常与C#的lambda表达式相比较,算是一种更老的方式.一些开发人员可能认为不再需要它,我们可以直接将表达式传入到仓储或领域服务中,如下所示:
var count = await _customerRepository.CountAsync(c => c.Balance > 100000 && c.Age => 18);
自从ABP的仓储支持表达式,这是一个完全有效的用法.你不必在应用程序中定义或使用任何规约,可以直接使用表达式.
所以,规约的意义是什么?为什么或者应该在什么时候考虑去使用它?
- 可复用:假设你在代码库的许多地方都需要用到优质顾客过滤器.如果使用表达式而不创建规约,那么如果以后更改“优质顾客”的定义会发生什么?假设你想将最低余额从100000美元更改为250000美元,并添加另一个条件,成为顾客超过3年.如果使用了规约,只需修改一个类.如果在任何其他地方重复(复制/粘贴)相同的表达式,则需要更改所有的表达式.
- 可组合:可以组合多个规约来创建新规约.这是另一种可复用性.
- 命名:
PremiumCustomerSpecification
更好地解释了为什么使用规约,而不是复杂的表达式.因此,如果在你的业务中使用了一个有意义的表达式,请考虑使用规约. - 可测试:规约是一个单独(且易于)测试的对象.
- 没有业务含义的表达式:不要对与业务无关的表达式和操作使用规约.