复杂搜索条件的良好模式?

时间:2014-12-20 07:20:35

标签: c# design-patterns

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

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

我认为Criteria / Filter Pattern可能适用于此功能。 http://en.wikipedia.org/wiki/Criteria_Pattern

我的问题是: 如何将参数添加到MeetCriteria()中? 我需要一些功能,如:

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

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

括号是一个麻烦解析。将界面设计为&#34;标准形式&#34;是一个好主意。 (仅在顶级使用OR),以便上述搜索条件可以被接受为:

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

感谢。

2 个答案:

答案 0 :(得分:3)

<强> TL; DR

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

理由:

IMO如果您尝试从头开始构建自己的Criteria或Evans specification pattern,正如您所建议的那样,您需要构建一个模型或语言来定义您的标准,能够解析此模型(包括处理优先顺序等),并且可能还将其转换为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-Injection-proof的DSL,用于表达任何Criteria / Specification模式。我建议您尽快将表示层查询转换为强类型IQueryable<T>,从而避免使用任何自定义标准语言/ DSL。

仍然需要对查询进行一些验证,例如防止用户执行任意查询,例如归还整个桌子。

如果您的物理架构跨层有一个序列化界面,请查看使用查询的OData表示 - 这解决了如何执行谓词/标准的序列化/反序列化循环的问题。

答案 1 :(得分:1)

规范模式是另一个候选人: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);
    }

参考文献: