【问题标题】:which design pattern to use for filtering query? c#使用哪种设计模式来过滤查询? C#
【发布时间】:2010-10-06 05:43:15
【问题描述】:

我有一个包含产品(服装)列表的数据库表。产品属于类别,来自不同的商店。

示例类别:上衣、下装、鞋子

示例商店:gap.com、macys.com、target.com

我的客户可以通过以下方式请求过滤产品:

  • 所有产品(无过滤器)
  • 按类别
  • 按商店
  • 按类别和商店

现在我的“产品”类中有一个方法,它根据用户请求的过滤器类型返回产品。我使用 FilterBy 枚举来确定需要退回哪些产品。

例如,如果用户想要查看“tops”类别中的所有产品,我调用此函数:

Products.GetProducts(FilterBy.Category, "tops", ""); 

我的最后一个参数是空的,因为它是包含要过滤的“存储”的字符串,但在这种情况下没有存储。但是,如果用户想按类别过滤并存储,我会这样调用该方法:

Product.GetProducts(FilterBy.CategoryAndStore, "tops", "macys.com");

我的问题是,有什么更好的方法来做到这一点?我刚刚了解了策略设计模式。我可以用它以更好(更容易扩展和更容易维护)的方式来做到这一点吗?

我问这个问题的原因是因为我认为这一定是人们反复解决的一个非常普遍的问题(以各种方式过滤产品)

【问题讨论】:

    标签: c# asp.net design-patterns


    【解决方案1】:

    根据 Eric Evan 的“域驱动设计”,您需要规范模式。像这样的

    public interface ISpecification<T>
    {
      bool Matches(T instance);
      string GetSql();
    }
    
    public class ProductCategoryNameSpecification : ISpecification<Product>
    {
      readonly string CategoryName;
      public ProductCategoryNameSpecification(string categoryName)
      {
        CategoryName = categoryName;
      }
    
      public bool Matches(Product instance)
      {
        return instance.Category.Name == CategoryName;
      }
    
      public string GetSql()
      {
        return "CategoryName like '" + { escaped CategoryName } + "'";
      }
    }
    

    现在可以使用规范调用您的存储库

    var specifications = new List<ISpecification<Product>>();
    specifications.Add(
     new ProductCategoryNameSpecification("Tops"));
    specifications.Add(
     new ProductColorSpecification("Blue"));
    
    var products = ProductRepository.GetBySpecifications(specifications);
    

    您还可以创建一个通用 CompositeSpecification 类,该类将包含子规范和一个关于将哪个逻辑运算符应用于它们的指示符 AND/OR

    不过,我更倾向于组合 LINQ 表达式。

    更新 - 运行时的 LINQ 示例

    var product = Expression.Parameter(typeof(Product), "product");
    var categoryNameExpression = Expression.Equal(
      Expression.Property(product, "CategoryName"),
      Expression.Constant("Tops"));
    

    你可以像这样添加一个“和”

    var colorExpression = Expression.Equal(
      Expression.Property(product, "Color"),
      Expression.Constant("Red"));
    var andExpression = Expression.And(categoryNameExpression, colorExpression);
    

    终于可以把这个表达式转换成谓词然后执行了……

    var predicate = 
      (Func<Product, bool>)Expression.Lambda(andExpression, product).Compile();
    var query = Enumerable.Where(YourDataContext.Products, predicate);
    
    foreach(Product currentProduct in query)
      meh(currentProduct);
    

    可能无法编译,因为我直接在浏览器中输入了它,但我相信它通常是正确的。

    另一个更新 :-)

    List<Product> products = new List<Product>();
    products.Add(new Product { CategoryName = "Tops", Color = "Red" });
    products.Add(new Product { CategoryName = "Tops", Color = "Gree" });
    products.Add(new Product { CategoryName = "Trousers", Color = "Red" });
    var query = (IEnumerable<Product>)products;
    query = query.Where(p => p.CategoryName == "Tops");
    query = query.Where(p => p.Color == "Red");
    foreach (Product p in query)
        Console.WriteLine(p.CategoryName + " / " + p.Color);
    Console.ReadLine();
    

    在这种情况下,您将在内存中进行评估,因为源是一个列表,但如果您的源是支持 Linq2SQL 的数据上下文,例如我认为这将使用 SQL 进行评估。

    您仍然可以使用规范模式来明确您的概念。

    public class Specification<T>
    {
      IEnumerable<T> AppendToQuery(IEnumerable<T> query);
    }
    

    这两种方法的主要区别在于,后者基于显式属性构建已知查询,而第一种方法可用于构建任何结构的查询(例如完全从 XML 构建查询。)

    这应该足以让你开始:-)

    【讨论】:

    • 给你加了一个例子,看看你怎么搞的。
    【解决方案2】:

    策略模式不一定与基于接口的通用存储库方法结合得很好。就个人而言,我可能会选择以下两种方式之一:

    • 一种支持选项组合的搜索方法:

      IList&lt;Product&gt; GetProducts(string category, string store, ...);

    (然后有选择地应用过滤器的组合(即null 表示“任何”) - 无论是在构建命令时,还是传递给执行类似操作的 SPROC。

    • 使用 LINQ,也许是谓词表达式?

      IList&lt;Product&gt; GetProducts(Expression&lt;Func&lt;Product,bool&gt;&gt; predicate);

    当然,使用 LINQ,您还可以使用调用者的组合,但是要为以下对象编写一个封闭/经过全面测试的存储库会更困难:

     `IQueryable<Product> Products {get;}`
    

    (并让调用者使用 .Where(x=>x.Category == "foo")) - 我不太确定最后一个长期...

    【讨论】:

    • 谓词形式正是我所发布的。它非常灵活,使用 lambda 表达式与其他任何方式一样简洁。
    • 确实;并且通过成为 Expression<...> 它可以被编译为与基于对象的存储库一起使用。不利的一面是 LOLA 仍然存在不支持的查询操作的风险...
    • theory 我喜欢 IQueryable(返回)选项,但它很难将 DAL 与 UI 隔离(即保证数据访问何时开始和结束) -但对于某些情况,它仍然是一个可行的选择。
    • 有什么理由选择 IList 作为返回类型而不是 IEnumerable
    • 为了说明,我只是希望存储库在传回数据之前清楚地完成执行。不过,预取和流式传输方法都是有效的。
    【解决方案3】:

    我想我会创建一个 Category 类和一个 Store 类,而不仅仅是字符串:

    class Category
    {
      public Category(string s)
      {
        ...
      }
      ...
    }
    

    然后也许:

    Product.GetProducts(
      Category category, //if this is null then don't filter on category
      Store store //if this is null then don't filter on store
      )
    {
      ...
    }
    

    CategoryStore 类可能是相关的(它们可能都是 Filter 类的子类)。

    【讨论】:

      【解决方案4】:

      我是根据我对模式的了解来回答这个问题的。

      装饰器模式在这里可能会有所帮助(考虑到您可以添加过滤器并获得结果。应用新过滤器并获得新结果)

      【讨论】:

        【解决方案5】:

        我会采用类似于过滤器本身的策略并编写 CategoryFilterStoreFilter 类。然后我会使用复合或装饰器来组合过滤器。

        【讨论】:

          【解决方案6】:

          你不能在这里添加 Where 的东西吗?

          var products = datacontext.Products;
          
          if(!String.IsNullOrEmpty(type))
            products = products.Where(p => p.Type == type);
          
          if(!String.IsNullOrEmpty(store))
            products = products.Where(p => p.Store == store);
          
          foreach(var p in products)
            // Do whatever
          

          或类似的东西......

          【讨论】:

            猜你喜欢
            • 2013-07-30
            • 2014-04-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多