规约Specification模式


  先从下面的一个示例引入我们要介绍的内容。User没有什么特殊,一个普通的领域对象,它提供了一些查询方法。

public class User 
{  
    public string ID { get; set; }  
    public string FirstName { get; set; }  
    public string LastName { get; set; }  
    public string Department { get; set; }

public int Age { get; set; }
  public List GetUserName(string name)    {}   

publicList
GetUserDepartment(string department)    {}
}

  在上面的例子中,有什么问题呢,如果需要添加新的过滤条件,按不同的属性做条件进行过滤。会想到怎样处理呢?

怎加一些新的查询方法?或是修改当前的方法?

下面介绍下规约模式

    將一些领域知识封装到一个同一单元 --规约--,提升 可重用,可维护,和松耦合的能力。通过bool逻辑將业务规则链接在一起,实现重组业务规则。

 定义规约 

 public interface ISpecification 
    {
        public bool IsSatisfiedBy(T u);
    }

 

public class UserDepartmentSpecification : ISpecification
    {
        private readonly string _department;

        public EmployeeDepartmentSpecification(string depatrment)
        {
            _department = depatrment;
        }

        public bool IsSatisfiedBy(User employee)
        {
            return employee.Department.Equals(_department);
        }
    }

    public class UserAgeSpecification : ISpecification
    {
        private readonly int _age;

        public EmployeeYearSpecification(int age)
        {
            _age= age;
        }
        public bool IsSatisfiedBy(User employee)
        {
            return employee.Aga == _age;
        }
    }

  Service:

public class UserService
    {
        public static List GetUserBy(ISpecification specification, List user)
        {
            List NeededUsers= new List();


            foreach (var u in user)
            {
                if (specification.IsSatisfiedBy(u))
                {
                    NeededUsers.Add(u);
                }
            }
            return NeededUsers ;
 } }

  通过规约模式,我们移除了大量最对性的查询方法。如果还需要针对其他属性的过滤条件,也只需要新增新的规约。保持了模型的稳定。

使用 LINQ类别(Orm)的仓储查询

  这里我们拿ESHOPWeb 的规约实现学习,核心接口的定义如下图。

ISpecification

public interface ISpecification : ISpecification
    {
        Expression>? Selector { get; }
    }

    public interface ISpecification
    {
        IEnumerablebool>>> WhereExpressions { get; }
        IEnumerable<(Expressionobject>> KeySelector, OrderTypeEnum OrderType)> OrderExpressions { get; }
        IEnumerable IncludeAggregators { get; }
        IEnumerable<string> IncludeStrings { get; }
        IEnumerable<(Expressionstring>> Selector, string SearchTerm, int SearchGroup)> SearchCriterias { get; }

        int? Take { get; }
        int? Skip { get; }
        [Obsolete]
        bool IsPagingEnabled { get; }

        bool CacheEnabled { get; }
        string? CacheKey { get; }
    }

 public abstract class Specification : Specification, ISpecification
    {
        protected new virtual ISpecificationBuilder Query { get; }

        protected Specification() : base()
        {
            this.Query = new SpecificationBuilder(this);
        }

        public Expression>? Selector { get; internal set; }
    }

    public abstract class Specification : ISpecification
    {
        protected virtual ISpecificationBuilder Query { get; }

        protected Specification()
        {
            this.Query = new SpecificationBuilder(this);
        }

        public IEnumerablebool>>> WhereExpressions { get; } = new Listbool>>>();

        public IEnumerable<(Expressionobject>> KeySelector, OrderTypeEnum OrderType)> OrderExpressions { get; } = 
            new List<(Expressionobject>> KeySelector, OrderTypeEnum OrderType)>();

        public IEnumerable IncludeAggregators { get; } = new List();

        public IEnumerable<string> IncludeStrings { get; } = new List<string>();

        public IEnumerable<(Expressionstring>> Selector, string SearchTerm, int SearchGroup)> SearchCriterias { get; } =
            new List<(Expressionstring>> Selector, string SearchTerm, int SearchGroup)>();


        public int? Take { get; internal set; } = null;

        public int? Skip { get; internal set; } = null;

        public bool IsPagingEnabled { get; internal set; } = false;

        public string? CacheKey { get; internal set; }

        public bool CacheEnabled { get; internal set; }
    }

SpecificationBuilder

public interface ISpecificationBuilder : ISpecificationBuilder
{
new Specification Specification { get; }
}

public interface ISpecificationBuilder
{
Specification Specification { get; }
}
  

 public class SpecificationBuilder : SpecificationBuilder, ISpecificationBuilder
    {
        public new Specification Specification { get; }

        public SpecificationBuilder(Specification specification) 
            : base(specification)
        {
            this.Specification = specification;
        }
    }

    public class SpecificationBuilder : ISpecificationBuilder
    {
        public Specification Specification { get; }

        public SpecificationBuilder(Specification specification)
        {
            this.Specification = specification;
        }
    } 

ISpecificationEvaluator 会根据规约生成对应的查询表达式

实现与调用

   自定义规约的时候实现ISpecification,通过 Query属性( ISpecificationBuilder),构建查询。

  public class BlogWithPostsAndAuthorSpec : Specification
    {
        public BlogWithPostsAndAuthorSpec(int id)
        {
            Query.Where(b => b.Id == id)
                .Include(b => b.Posts)
                .ThenInclude(p => p.Author);
        }
    }

 在仓储里,通过 ISpecificationEvaluator  处理查询表达式,返回IQueryable

 public abstract class RepositoryBase : IRepositoryBase where T : class
    {
        ......
     ......

private readonly ISpecificationEvaluator specificationEvaluator;

public async Task> ListAsync(ISpecification specification) { return await ApplySpecification(specification).ToListAsync(); } public async Task> ListAsync(ISpecification specification) { return await ApplySpecification(specification).ToListAsync(); } public async Task<int> CountAsync(ISpecification specification) { return await ApplySpecification(specification).CountAsync(); } protected IQueryable ApplySpecification(ISpecification specification) { return specificationEvaluator.GetQuery(dbContext.Set().AsQueryable(), specification); } protected IQueryable ApplySpecification(ISpecification specification) { if (specification is null) throw new ArgumentNullException("Specification is required"); if (specification.Selector is null) throw new SelectorNotFoundException(); return specificationEvaluator.GetQuery(dbContext.Set().AsQueryable(), specification); } }