2009-02-08 30 views
9

Tôi có một bảng cơ sở dữ liệu với một danh sách các sản phẩm (quần áo). Các sản phẩm thuộc về các loại và đến từ các cửa hàng khác nhau.mẫu thiết kế nào để sử dụng để lọc truy vấn? C#

loại mẫu: ngọn, đáy, giày

cửa hàng mẫu: gap.com, macys.com, target.com

khách hàng của tôi có thể yêu cầu để lọc sản phẩm theo các cách sau:

  • tất cả các sản phẩm (không có bộ lọc)
  • theo thể loại
  • bởi cửa hàng
  • theo thể loại và lưu trữ

Ngay bây giờ tôi có MỘT phương thức trong lớp "Sản phẩm" của tôi trả về sản phẩm tùy thuộc vào loại bộ lọc mà người dùng yêu cầu. Tôi sử dụng một FilterBy enum để xác định sản phẩm nào cần được trả lại.

Ví dụ, nếu người dùng muốn xem tất cả các sản phẩm trong thể loại "đỉnh" Tôi gọi hàm này:

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

Tôi có tham số cuối cùng trống rỗng, vì đó là chuỗi chứa các "cửa hàng" để lọc theo nhưng trong trường hợp này không có cửa hàng. Tuy nhiên, nếu người dùng muốn lọc theo danh mục VÀ lưu trữ, tôi sẽ gọi phương thức theo cách này:

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

Câu hỏi của tôi là cách tốt hơn để làm điều này là gì? Tôi vừa học về mẫu thiết kế chiến lược. Tôi có thể sử dụng điều đó để thực hiện điều này theo cách tốt hơn (dễ mở rộng và dễ bảo trì hơn) không?

Lý do tôi đang hỏi câu hỏi này là vì tôi con số này phải là một vấn đề khá phổ biến mà mọi người đang liên tục giải quyết (các sản phẩm lọc theo nhiều cách khác nhau)

+0

[Lọc Design Pattern Với Ví dụ] (http://www.singhajit.com/filter-design-pattern/) –

Trả lời

13

Theo "Thiết kế ổ đĩa tên miền" của Eric Evan, bạn cần mẫu đặc điểm kỹ thuật. Một cái gì đó như thế này

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

kho của bạn bây giờ có thể được gọi với thông số kỹ thuật

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

var products = ProductRepository.GetBySpecifications(specifications); 

Bạn cũng có thể tạo ra một lớp CompositeSpecification chung chung mà sẽ chứa thông số kỹ thuật phụ và một chỉ số như mà logic điều hành để áp dụng cho họ VÀ/HOẶC

Tôi muốn có nhiều khuynh hướng kết hợp các biểu thức LINQ hơn.

Update - Ví dụ về LINQ trong thời gian chạy

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

Bạn có thể thêm một "và" như vậy

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

Cuối cùng, bạn có thể chuyển đổi biểu thức này vào một vị và sau đó thực hiện nó. ..

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

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

Có lẽ sẽ không biên dịch vì tôi đã gõ nó trực tiếp vào trình duyệt, nhưng tôi tin nó nói chung là đúng.

cập nhật Một :-)

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

Trong trường hợp này, bạn sẽ được đánh giá trong bộ nhớ vì nguồn là một danh sách, nhưng nếu nguồn của bạn là một bối cảnh dữ liệu mà hỗ trợ Linq2SQL ví dụ tôi nghĩ điều này sẽ đánh giá bằng cách sử dụng SQL.

Bạn vẫn có thể sử dụng mẫu Đặc điểm kỹ thuật để làm cho các khái niệm của bạn rõ ràng.

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

Sự khác biệt chính giữa hai cách tiếp cận là sau này xây dựng một truy vấn được biết dựa trên đặc tính rõ ràng, trong khi người đầu tiên có thể được sử dụng để xây dựng một truy vấn của bất kỳ cấu trúc (ví dụ như xây dựng một truy vấn hoàn toàn từ XML ví dụ.)

Điều này là đủ để bạn bắt đầu :-)

+0

bạn có thể chỉ cho tôi một ví dụ về kết hợp các biểu thức LINQ không? –

+0

Đã thêm ví dụ cho bạn, hãy xem và xem cách bạn tiếp tục. –

2

Các mô hình chiến lược không nhất thiết đan tốt với các cách tiếp cận kho lưu trữ dựa trên giao diện chung. Cá nhân, tôi có thể đi một trong hai cách sau đây:

  • phương pháp Một tìm kiếm có hỗ trợ kết hợp các tùy chọn:

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

(sau đó chọn lọc áp dụng các kết hợp của bộ lọc (ví dụ: null có nghĩa là "bất kỳ") - hoặc khi xây dựng một lệnh hoặc chuyển xuống SPROC thực hiện điều gì đó tương tự.

  • Với LINQ, có thể là biểu thức vị ngữ?

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

Tất nhiên, với LINQ bạn cũng có thể sử dụng phần bởi người gọi, nhưng đó là khó khăn hơn để viết một khép kín/đầy đủ kiểm tra kho chứa:

`IQueryable<Product> Products {get;}` 

(và có sử dụng người gọi .Where (x => x.Category == "foo")) - Tôi không chắc chắn về điều này lâu dài nhất ...

+0

Dạng vị là chính xác những gì tôi đã đăng. Nó rất linh hoạt, và với các biểu thức lambda nó chỉ là ngắn gọn như bất kỳ cách nào khác. –

+0

Thật vậy; và bằng cách biểu thị <...>, nó có thể được biên dịch để sử dụng với kho dựa trên đối tượng. Nhược điểm là vẫn có nguy cơ LOLA lại các hoạt động truy vấn không được hỗ trợ ... –

+0

Trong * lý thuyết * Tôi thích tùy chọn IQueryable (trả lại), nhưng nó làm cho khó phân tách DAL khỏi giao diện người dùng (tức là đảm bảo khi truy cập dữ liệu bắt đầu và kết thúc) - nhưng nó vẫn là một lựa chọn khả thi đối với một số secnarios. –

0

Tôi trả lời điều này dựa trên kiến ​​thức nhỏ của tôi các mẫu.

Decorator pattern có thể giúp đây (xem xét bạn có thể thêm một bộ lọc & có được kết quả. Áp dụng bộ lọc mới vào nó & có được kết quả mới)

1

tôi nghĩ rằng tôi muốn tạo ra một lớp Category và một lớp Store, thay vì chỉ là chuỗi:

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

và sau đó có lẽ:

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

Các CategoryStore lớp học có thể liên quan (cả hai có thể là lớp con của lớp Filter).

0

Tôi sẽ đi với một cái gì đó giống như một chiến lược cho các bộ lọc chính mình và viết CategoryFilterStoreFilter lớp học. Sau đó, tôi sẽ sử dụng một hỗn hợp hoặc trang trí để kết hợp các bộ lọc.

0

Bạn không thể chỉ thêm công cụ khi bạn đến đây?

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 

hoặc một cái gì đó như thế ...

Các vấn đề liên quan