2011-08-15 26 views
8

Cho phép nói rằng tôi có một cách đặc biệt của quyết định có một số chuỗi "trận đấu", như thế này:Làm thế nào để giữ DRY trong khi sử dụng LINQ to Entities và phương pháp trợ giúp?

public bool stringsMatch(string searchFor, string searchIn) 
{ 
    if (string.IsNullOrEmpty(searchFor)) 
    { 
    return true; 
    } 

    return searchIn != null && 
    (searchIn.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) || 
    searchIn.Contains(" " + searchFor)); 
} 

Tôi muốn kéo trận đấu ra khỏi một cơ sở dữ liệu sử dụng LINQ to Entities và helper này. Tuy nhiên, khi tôi cố gắng này:

IQueryable<Blah> blahs = query.Where(b => stringsMatch(searchText, b.Name); 

tôi nhận được "LINQ to Entities không nhận ra phương pháp ..."

Nếu tôi viết lại các mã như:

IQueryable<Blah> blahs = query.Where(b => 
     string.IsNullOrEmpty(searchText) || 
     (b.Name != null && 
     (b.Name.Trim().ToLower().StartsWith(searchText.Trim().ToLower()) || 
     b.Name.Contains(" " + searchText))); 

nào tương đương về mặt logic, sau đó mọi thứ hoạt động tốt. Vấn đề là mã không thể đọc được và tôi phải viết lại nó cho từng thực thể khác nhau mà tôi muốn khớp.

Theo như tôi có thể kể từ các câu hỏi như this one, những gì tôi muốn làm là không thể vào lúc này, nhưng tôi hy vọng rằng tôi đang thiếu một cái gì đó, phải không?

+0

thử [Predicate Builder] (http://www.albahari.com/nutshell/predicatebuilder.aspx) – Eranga

Trả lời

4

Sử dụng thư viện tự do có sẵn được gọi là LINQKit (như đã đề cập bởi @Eranga) tác vụ này trở nên hợp lý. Sử dụng LINQKit đoạn code tôi có bây giờ trông giống như:

protected Expression<Func<T, bool>> stringsMatch(string searchFor, Expression<Func<T, string>> searchIn) 
{ 
    if (string.IsNullOrEmpty(searchFor)) 
    { 
    return e => true; 
    } 

    return 
    e => 
    (searchIn.Invoke(e) != null && 
     (searchIn.Invoke(e).Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) || 
     searchIn.Invoke(e).Contains(" " + searchFor))); 
} 

Và cần phải được gọi như thế này (lưu ý gọi AsExpandable())

IQueryable<Blah> blahs = query().AsExpandable().Where(StringsMatch(searchText, b => b.Name)); 

Các bộ phận kỳ diệu là searchin.Gọi (e) các cuộc gọi và sử dụng AsExpandable() để thêm lớp bao bọc cho phép chúng hoạt động.

bit AsExpandable() được giải thích chi tiết bởi tác giả gốc here.

Lưu ý rằng tôi vẫn còn hơi mơ hồ về một số chi tiết của biểu thức, vì vậy hãy thêm nhận xét/chỉnh sửa câu trả lời này nếu câu trả lời có thể được làm tốt hơn/ngắn hơn/rõ ràng hơn.

5

Nếu tất cả 'blahs' (lớp) mà bạn sẽ lọc có cấu trúc giống nhau, bạn có thể sử dụng một phương pháp đơn giản như thế này. Sự khác biệt chính là nó trả về một biểu thức mà LINQ có thể phân tích cú pháp và nó mang lại toàn bộ cá thể và bộ lọc trên tên thay vì chỉ mang tên chuỗi.

public static Expression<Func<T, bool>> BuildStringMatch<T>(string searchFor) where T : IHasName 
    { 
     return b => 
       string.IsNullOrEmpty(searchFor) || 
       (b.Name != null && 
       (b.Name.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) || 
       b.Name.Contains(" " + searchFor))); 
    } 

Bạn có thể sử dụng phương pháp như thế này:

IQueryable<Blah> blahs = query.Where(BuildStringMatch<Blah>(searchText)); 

Đó là giả định tất cả các lớp học của bạn mà bạn muốn lọc vào thực hiện một số giao diện như:

public interface IHasName 
    { 
     string Name { get; } 
    } 

Nếu bạn muốn lọc trên các thuộc tính khác nhau, tôi không nghĩ đó là điều bạn có thể làm với mã đơn giản như thế này. Tôi tin rằng bạn sẽ cần phải xây dựng biểu thức cho mình với sự phản chiếu (hoặc với sự giúp đỡ của một thư viện sử dụng sự phản chiếu) - nó vẫn có thể nhưng khó khăn hơn nhiều.

Edit: Có vẻ như bạn cần hành vi năng động, vì vậy tôi mượn một số logic từ dtb 's câu trả lời cho this question và đến với điều này:

public static Expression<Func<T, bool>> BuildStringMatch<T>(Expression<Func<T, string>> property, string searchFor) 
{ 
    var searchForExpression = Expression.Constant(searchFor, typeof(string)); 
    return 
     Expression.Lambda<Func<T, bool>>(
      Expression.OrElse(
       Expression.Call(typeof(string), "IsNullOrEmpty", null, searchForExpression), 
       Expression.AndAlso(
        Expression.NotEqual(property.Body, Expression.Constant(null, typeof(string))), 
        Expression.OrElse(
         Expression.Call(Expression.Call(Expression.Call(property.Body, "Trim", null), "ToLower", null), "StartsWith", null, 
          Expression.Call(Expression.Call(searchForExpression, "Trim", null), "ToLower", null)), 
         Expression.Call(property.Body, "Contains", null, Expression.Call(typeof(string), "Concat", null, Expression.Constant(" "), searchForExpression)) 
        ) 
       ) 
      ), 
      property.Parameters 
     ); 
} 

Bạn sẽ sử dụng nó như:

 IQueryable<Blah> blahs2 = query.Where(BuildStringMatch<Blah>(b => b.Name, searchText)); 

Dài và dài nhưng bạn có thể thấy nó giống với phương thức ban đầu được viết bằng mã C# thẳng. Lưu ý: Tôi đã không kiểm tra mã này, vì vậy có thể có một vài vấn đề nhỏ - nhưng đó là ý tưởng chung.

+0

Cảm ơn bạn đã trả lời, tôi đã cân nhắc một cái gì đó như thế này, nhưng sự thiếu linh hoạt là đặt ra (không phải mọi thứ tôi muốn khớp được gọi là "Tên"). Bất kỳ gợi ý về nơi để bắt đầu với cách phức tạp hơn? – Dan

+0

Tôi đã chỉnh sửa câu trả lời của mình để bao gồm một số mã mẫu. Nó chắc chắn trông phức tạp hơn nhưng nếu bạn chỉ phải viết nó một lần ..... có lẽ nó sẽ không quá tệ. –

+0

Cảm ơn, đó là thực sự hữu ích (xấu hổ tôi chỉ có thể upvote một lần). Tuy nhiên, tôi đã tìm thấy một số mã khác trong codebase của chúng tôi có sử dụng một thư viện gọi là LINQKit để làm một cái gì đó rất giống trong (tôi nghĩ) một cách neater. Tôi sẽ thêm một câu trả lời mới với đầy đủ chi tiết. – Dan

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