复杂搜索条件的良好模式

本文关键字:模式 搜索 条件 复杂 | 更新日期: 2023-09-27 18:34:43

我目前正在研究用户搜索功能。搜索条件非常复杂,例如:

Footage >= 50 AND (SizeCode == SizeType.Large OR MobileEnd == "ABC") 

我认为标准/过滤器模式可能适合此功能。http://en.wikipedia.org/wiki/Criteria_Pattern

我的问题是:如何将参数添加到满足标准((中?我需要一些功能,例如:

public List<Store> MeetCriteria(List<Store> entities, int footage)
public List<Store> MeetCriteria(List<Store> entities, string mobileEnd)

括号解析起来很麻烦。将界面设计为"标准形式"(仅在顶层使用 OR(是否是一个好主意,以便上述搜索条件可以接受为:

(Footage >= 50 AND SizeCode == SizeType.Large) OR 
(Footage >= 50 AND MobileEnd == "ABC")

谢谢。

复杂搜索条件的良好模式

TL;DR

我相信一般来说,这个问题最好使用 LINQ 在 C# 中解决。具体而言,如果要将条件转换为 SQL 并应用于数据库,则条件可以表示为 Expression 树,因为这些树可以由实体框架等查询提供程序处理。

理由:

IMO 如果您尝试从头开始构建自己的标准或 Evans 规范模式,正如您所建议的那样,您将需要构建一个模型或语言来定义您的标准,能够解析此模型(包括处理优先级顺序等(,并且可能还将其转换为 Sql 或其他数据查询语言以有效地执行标准。这将是IMO漫长而痛苦的旅程。

下面是一个简单的示例,使用 Func<Store, bool> 谓词来构建条件并针对静态内存中集合执行它们。这绝不是完整的 - 所有这些都与And完全匹配,尽管确实允许可选的过滤:

private static readonly IEnumerable<Store> _myStores = new[]
{
    new Store {Footage = 100, MobileEnd = "XYZ", SizeCode = SizeCode.Small},
    new Store {Footage = 200, MobileEnd = "XYZ", SizeCode = SizeCode.Medium},
    new Store {Footage = 300, MobileEnd = "XYZ", SizeCode = SizeCode.Large},
    new Store {Footage = 150, MobileEnd = "ABC", SizeCode = SizeCode.Small},
    new Store {Footage = 250, MobileEnd = "ABC", SizeCode = SizeCode.Medium},
    new Store {Footage = 350, MobileEnd = "ABC", SizeCode = SizeCode.Large},
};
private static IEnumerable<Store> ApplyAndPredicates(IEnumerable<Func<Store, bool>> predicates)
{
    var filteredStores = _myStores;
    foreach (var predicate in predicates)
    {
        filteredStores = filteredStores.Where(predicate);
    }
    return filteredStores;
}
public static List<Store> MeetCriteria(List<Store> entities, int? footage = null, string mobileEnd = null, SizeCode? sizeCode = null)
{
    var predicates = new List<Func<Store, bool>>();
    if (footage.HasValue)
    {
        predicates.Add(s => s.Footage == footage.Value);
    }
    if (mobileEnd != null)
    {
        predicates.Add(s => s.MobileEnd == mobileEnd);
    }
    if (sizeCode != null)
    {
        predicates.Add(s => s.SizeCode == sizeCode);
    }
    return ApplyAndPredicates(predicates).ToList();
}

编辑
我想论证的一点是,LINQ 表达式树已经提供了一个类型安全的、富有表现力的、 SQL 注入证明的 DSL,用于表达任何条件/规范模式。我建议您尽快将表示层查询转换为强类型IQueryable<T>,从而避免对任何自定义条件语言/DSL的需求。

仍然需要对查询进行一些验证,例如,防止用户执行任意查询,例如返回整个表。

如果您的物理体系结构具有跨层的序列化接口,请查看使用查询的 OData 表示形式 - 这解决了如何执行谓词/条件的序列化/反序列化循环的问题。

规范模式是另一个候选者:https://en.wikipedia.org/wiki/Specification_pattern 。它允许您在现有标准的基础上构建更多标准。

以下是您将如何使用它:

var OverDue = new OverDueSpecification();
var NoticeSent = new NoticeSentSpecification();
var InCollection = new InCollectionSpecification();
// example of specification pattern logic chaining
var SendToCollection = OverDue.And(NoticeSent).And(InCollection.Not());
var InvoiceCollection = Service.GetInvoices();
foreach (var currentInvoice in InvoiceCollection) {
    if (SendToCollection.IsSatisfiedBy(currentInvoice))  {
        currentInvoice.SendToCollection();
    }
}

以及 C# 6.0 中的泛型代码:

  public interface ISpecification<T>
    {
        bool IsSatisfiedBy(T candidate);
        ISpecification<T> And(ISpecification<T> other);
        ISpecification<T> AndNot(ISpecification<T> other);
        ISpecification<T> Or(ISpecification<T> other);
        ISpecification<T> OrNot(ISpecification<T> other);
        ISpecification<T> Not();
    }
    public abstract class LinqSpecification<T> : CompositeSpecification<T>
    {
        public abstract Expression<Func<T, bool>> AsExpression();
        public override bool IsSatisfiedBy(T candidate) => AsExpression().Compile()(candidate);
    }
    public abstract class CompositeSpecification<T> : ISpecification<T>
    {
        public abstract bool IsSatisfiedBy(T candidate);
        public ISpecification<T> And(ISpecification<T> other) => new AndSpecification<T>(this, other);
        public ISpecification<T> AndNot(ISpecification<T> other) => new AndNotSpecification<T>(this, other);
        public ISpecification<T> Or(ISpecification<T> other) => new OrSpecification<T>(this, other);
        public ISpecification<T> OrNot(ISpecification<T> other) => new OrNotSpecification<T>(this, other);
        public ISpecification<T> Not() => new NotSpecification<T>(this);
    }
    public class AndSpecification<T> : CompositeSpecification<T>
    {
        ISpecification<T> left;
        ISpecification<T> right;
        public AndSpecification(ISpecification<T> left, ISpecification<T> right)
        {
            this.left = left;
            this.right = right;
        }
        public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) && right.IsSatisfiedBy(candidate);
    }
    public class AndNotSpecification<T> : CompositeSpecification<T>
    {
        ISpecification<T> left;
        ISpecification<T> right;
        public AndNotSpecification(ISpecification<T> left, ISpecification<T> right)
        {
            this.left = left;
            this.right = right;
        }
        public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) && right.IsSatisfiedBy(candidate) != true;
    }
    public class OrSpecification<T> : CompositeSpecification<T>
    {
        ISpecification<T> left;
        ISpecification<T> right;
        public OrSpecification(ISpecification<T> left, ISpecification<T> right)
        {
            this.left = left;
            this.right = right;
        }
        public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) || right.IsSatisfiedBy(candidate);
    }
    public class OrNotSpecification<T> : CompositeSpecification<T>
    {
        ISpecification<T> left;
        ISpecification<T> right;
        public OrNotSpecification(ISpecification<T> left, ISpecification<T> right)
        {
            this.left = left;
            this.right = right;
        }
        public override bool IsSatisfiedBy(T candidate) => left.IsSatisfiedBy(candidate) || right.IsSatisfiedBy(candidate) != true;
    }
    public class NotSpecification<T> : CompositeSpecification<T>
    {
        ISpecification<T> other;
        public NotSpecification(ISpecification<T> other) => this.other = other;
        public override bool IsSatisfiedBy(T candidate) => !other.IsSatisfiedBy(candidate);
    }

引用:

  • 规格设计模式:http://www.martinfowler.com/apsupp/spec.pdf