简体   繁体   English

哪个设计模式用于过滤查询? C#

[英]which design pattern to use for filtering query? c#

I have a database table with a list of products (clothing). 我有一个数据库表,其中包含产品列表(服装)。 The products belong to categories and are from different stores. 产品属于类别,来自不同的商店。

Sample categories: tops, bottoms, shoes 样品类别:上衣,下装,鞋子

Sample stores: gap.com, macys.com, target.com 样品商店:gap.com,macys.com,target.com

My customers can request to filter products in the following ways: 我的客户可以通过以下方式请求过滤产品:

  • all products (no filter) 所有产品(无过滤器)
  • by category 按类别
  • by store 通过商店
  • by category and store 按类别和商店

Right now I have ONE method in my "Products" class that returns the products depending on the type of filter requested by the user. 现在我在“Products”类中有一个方法,它根据用户请求的过滤器类型返回产品。 I use a FilterBy enum to determine which products need to be returned. 我使用FilterBy枚举来确定需要返回哪些产品。

For example, if the user wants to view all products in the "tops" category I call this function: 例如,如果用户想要查看“tops”类别中的所有产品,我会调用此函数:

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

I have the last parameter empty because it's the string that contains the "store" to filter by but in this case there is no store. 我有最后一个参数为空,因为它是包含要过滤的“商店”的字符串,但在这种情况下没有商店。 However, if the user wants to filter by category AND store I'd call the method this way: 但是,如果用户想要按类别和商店进行过滤,我会以这种方式调用方法:

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

My question is, what's a better way to do this? 我的问题是,有什么更好的方法呢? I just learned about the strategy design pattern. 我刚学会了战略设计模式。 Could I use that to do this in a better (easier to extend and easier to maintain) way? 我可以使用它以更好(更容易扩展和更容易维护)的方式做到这一点吗?

The reason I'm asking this question is because I figure this must be a pretty common problem that people are repeatedly solving (filtering products in various ways) 我问这个问题的原因是因为我认为这一定是人们反复解决的一个非常普遍的问题(以各种方式过滤产品)

According to Eric Evan's "Domain Drive Design" you need the specification pattern. 根据Eric Evan的“域驱动设计”,您需要规范模式。 Something like this 像这样的东西

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 } + "'";
  }
}

Your repository can now be called with specifications 现在可以使用规范调用您的存储库

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

var products = ProductRepository.GetBySpecifications(specifications);

You could also create a generic CompositeSpecification class which would contain sub specifications and an indicator as to which logical operator to apply to them AND/OR 您还可以创建一个通用的CompositeSpecification类,该类包含子规范和关于应用于哪个逻辑运算符的指示符和/或

I'd be more inclined to combine LINQ expressions though. 我更倾向于结合LINQ表达式。

Update - Example of LINQ at runtime 更新 - 运行时LINQ的示例

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

You can add an "and" like so 您可以像这样添加“和”

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

Finally you can convert this expression into a predicate and then execute it... 最后,您可以将此表达式转换为谓词,然后执行它...

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

foreach(Product currentProduct in query)
  meh(currentProduct);

Probably wont compile because I have typed it directly into the browser, but I believe it is generally correct. 可能不会编译因为我直接在浏览器中键入它,但我相信它通常是正确的。

Another update :-) 另一个更新 :-)

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();

In this case you would be evaluating in memory because the source is a List, but if your source was a data context that supported Linq2SQL for example I think this would evaluate using SQL. 在这种情况下,您将在内存中进行评估,因为源是List,但如果您的源是支持Linq2SQL的数据上下文,例如我认为这将使用SQL进行评估。

You could still use the Specification pattern in order to make your concepts explicit. 您仍然可以使用规范模式以使您的概念明确。

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

The main difference between the two approaches is that the latter builds a known query based on explicit properties, whereas the first one could be used to build a query of any structure (such as building a query entirely from XML for example.) 两种方法的主要区别在于后者基于显式属性构建已知查询,而第一种方法可用于构建任何结构的查询(例如,完全从XML构建查询)。

This should be enough to get you started :-) 这应该足以让你入门:-)

The strategy pattern doesn't necessarily knit well with the common interface-based repository approach. 策略模式不一定与基于接口的通用存储库方法相吻合。 Personally, I'd probably go one of two ways here: 就个人而言,我可能会选择以下两种方式之一:

  • One search method that supports combinations of options: 一种支持选项组合的搜索方法:

    IList<Product> GetProducts(string category, string store, ...);

(then selectively apply the combinations of filters (ie null means "any") - either when building a command, or pass down to a SPROC that does something similar. (然后选择性地应用过滤器的组合 (即null表示“任何”) - 在构建命令时,或传递给执行类似操作的SPROC。

  • With LINQ, maybe a predicate expression? 使用LINQ,可能是一个谓词表达式?

    IList<Product> GetProducts(Expression<Func<Product,bool>> predicate);

Of course, with LINQ you could also use composition by the caller, but that is harder to write a closed / fully-tested repository for: 当然,使用LINQ,您也可以使用调用者的组合,但是编写一个封闭/经过全面测试的存储库更难:

 `IQueryable<Product> Products {get;}`

(and have the caller use .Where(x=>x.Category == "foo")) - I'm not so sure about this last one long-term... (让调用者使用。所以(x => x.Category ==“foo”)) - 我不太确定这最后一个长期...

I think I'd make a Category class and a Store class, instead of just strings: 我想我会创建一个Category类和一个Store类,而不仅仅是字符串:

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

And then maybe: 然后可能:

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
  )
{
  ...
}

The Category and Store classes might be related (they might both be subclasses of a Filter class). CategoryStore类可能是相关的(它们可能都是Filter类的子类)。

I am answering this based on my little knowledge of patterns. 我基于对模式的一点知识来回答这个问题。

Decorator pattern might help here (considering you can add a filter & get results. Apply new filters on it & get new results) 装饰器模式可能对此有所帮助(考虑到您可以添加过滤器并获得结果。在其上应用新过滤器并获得新结果)

I would go with something like a strategy for the filters themselves and write CategoryFilter and StoreFilter classes. 我会使用类似于过滤器本身的策略并编写CategoryFilterStoreFilter类。 Then I would use a composite or decorator to combine the filters. 然后我会使用复合或装饰器来组合滤镜。

Can't you just add Where stuff as you go here? 你不能在这里添加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

or something like that... 或类似的东西...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM