2009-02-08 12 views
9

Tengo una tabla de base de datos con una lista de productos (ropa). Los productos pertenecen a categorías y son de diferentes tiendas.¿qué patrón de diseño usar para filtrar la consulta? C#

categorías de la muestra: tops, pantalones, zapatos

Tiendas de ejemplo: gap.com, macys.com, target.com

Mis clientes pueden solicitar para filtrar los productos de las siguientes maneras:

  • todos los productos (sin filtro)
  • por categoría
  • por la tienda
  • por categoría y tienda

Ahora tengo un método en mi clase "Productos" que devuelve los productos según el tipo de filtro solicitado por el usuario. Utilizo una enumeración FilterBy para determinar qué productos deben devolverse.

Por ejemplo, si el usuario desea ver todos los productos en la categoría de "tapas" que llama a esta función:

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

tengo el último parámetro vacío porque es la cadena que contiene la "tienda" de filtrar por, pero en este caso no hay tienda. Sin embargo, si el usuario desea filtrar por categoría y tienda que yo llamaría el método de esta manera:

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

Mi pregunta es, ¿cuál es la mejor manera de hacer esto? Acabo de aprender sobre el patrón de diseño de la estrategia. ¿Podría usar eso para hacer esto de una manera mejor (más fácil de extender y más fácil de mantener)?

La razón por la que estoy haciendo esta pregunta es porque me imagino que esto debe ser un problema bastante común que las personas están resolviendo de forma repetida (productos de filtrado de diferentes maneras)

+0

[Filtro Diseño Modelo Con Ejemplo] (http://www.singhajit.com/filter-design-pattern/) –

Respuesta

13

De acuerdo con "Domain Drive Design" de Eric Evan, necesita el patrón de especificación. Algo como esto

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

su repositorio puede ser llamado ahora con las especificaciones

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

var products = ProductRepository.GetBySpecifications(specifications); 

También podría crear una clase CompositeSpecification genérica que contendría especificaciones sub y un indicador de qué operador lógico aplicar a ellos Y/O

Sin embargo, estaría más inclinado a combinar expresiones LINQ.

Actualización - Ejemplo de LINQ en tiempo de ejecución

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

Se puede añadir una "y" al igual que

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

Por último se puede convertir esta expresión en un predicado y luego ejecutarlo. ..

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

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

Probablemente no compile porque lo he escrito directamente en el navegador, pero creo que generalmente es correcto.

Otra actualización :-)

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

En este caso se evaluarán en la memoria porque la fuente es una lista, pero si su fuente era un contexto de datos que apoyó Linq2SQL por ejemplo, me parece esto evaluaría usando SQL.

Aún puede utilizar el patrón de Especificación para hacer explícitos sus conceptos.

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

La principal diferencia entre los dos enfoques es que el último se basa una consulta conocido basado en las propiedades explícitas, mientras que el primero podría ser utilizado para construir una consulta de cualquier estructura (como la construcción de una consulta en su totalidad a partir de XML por ejemplo.)

Esto debería ser suficiente para empezar :-)

+0

¿podría mostrarme un ejemplo de combinación de expresiones LINQ? –

+0

Agregó un ejemplo para usted, eche un vistazo y vea cómo avanza. –

2

El patrón de estrategia no necesariamente tejer bien con el enfoque de repositorio basado en interfaz común. En lo personal, probablemente me vaya una de dos maneras: aquí

  • método Una búsqueda que soporta combinaciones de opciones:

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

(a continuación, aplicar selectivamente las combinaciones de filtros (es decir, null significa "cualquiera"), ya sea al construir un comando o pasar a un SPROC que hace algo similar.

  • ¿Con LINQ, quizás una expresión de predicado?

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

Por supuesto, con LINQ también se puede utilizar la composición de la persona que llama, pero que es más difícil escribir un depósito cerrado/probado completamente para:

`IQueryable<Product> Products {get;}` 

(y tiene la persona que llama uso .Donde (x => x.Category == "foo")) - No estoy tan seguro de este último a largo plazo ...

+0

La forma de predicados es exactamente lo que yo habrían publicado Es muy flexible y con expresiones lambda es tan conciso como cualquier otro. –

+0

De hecho; y al ser Expression <...>, se puede compilar para usar con repositorios basados ​​en objetos. La desventaja es que aún existe el riesgo de que LOLA sea la operación de consulta no admitida ... –

+0

En * teoría * Me gusta la opción IQueryable (devolución), pero hace que sea difícil aislar DAL de la IU (es decir, garantizar el acceso a los datos comienza y termina) - pero sigue siendo una opción viable para algunos secnarios. –

0

Estoy respondiendo esto basado en mi poco conocimiento de patrones.

patrón

decorador podría ayudar aquí (teniendo en cuenta que puede agregar un filtro & obtener resultados. Aplicar los nuevos filtros sobre ella & obtener nuevos resultados)

1

Creo que me gustaría hacer una clase y una clase Categoría de la tienda, en lugar de sólo cadenas:

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

y luego tal vez:

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

El Category yLas clasespueden estar relacionadas (ambas pueden ser subclases de una clase Filter).

0

Me gustaría ir con algo así como una estrategia para los propios filtros y escribir CategoryFilter y StoreFilter clases. Entonces usaría un compuesto o un decorador para combinar los filtros.

0

¿no puedes añadir Donde las cosas a medida que avanza aquí?

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 

o algo por el estilo ...

Cuestiones relacionadas