2012-05-07 8 views
6

Tôi đang viết một truy vấn tìm kiếm đơn giản cho ứng dụng Khung thực thể của tôi. Tôi cần phải kiểm tra xem một loạt các trường là null, và nếu không, hãy gọi ToLower() trên chúng và so sánh với truy vấn tìm kiếm. Truy vấn LINQ trông giống như sau:Có cách nào đơn giản để viết một hàm tùy chỉnh trong LINQ to Entities?

public IQueryable<Store> SearchStores(string q, IQueryable<Store> source) 
{ 
    q = q.ToLower(); 

    return (
     from s in source 
     where (
      (s.Name != null && s.Name.ToLower().Contains(q)) || 
      (s.Description != null && s.Description.ToLower().Contains(q)) || 
      ... 
} 

Có rất nhiều dòng như thế này, vì vậy tôi đã bị cám dỗ để viết một phương thức helper để làm sạch nó lên một chút:

public static bool SafeSearch(this string s, string q) 
{ 
    return s == null ? false : s.ToLower().Contains(q); 
} 

Điều này tất nhiên không hoạt động, tuy nhiên, vì LINQ cho các thực thể không hiểu chức năng Tìm kiếm an toàn là gì:

LINQ to Entities không nhận ra phương pháp 'Boolean SafeSearch (System.String, System.String)', và phương pháp này không thể chuyển đổi ted vào một biểu hiện cửa hàng.

Có cách nào dễ dàng để viết một hàm tùy chỉnh đơn giản như thế này?

Cảm ơn!

+0

Loại đối chiếu trên cơ sở dữ liệu của bạn là gì? – Brannon

Trả lời

2

Vì linq sử dụng biểu thức không được thực hiện cho đến khi bạn thực sự gọi cơ sở dữ liệu, bạn sẽ cần bọc hàm của bạn bên trong vị từ.

private static Func<Country, bool> Predicate(string q) 
{ 
    return x => (
     q.SafeSearch(x.Name) || 
     q.SafeSearch(x.Description) 
     ); 
} 

Cũng đảo ngược phương pháp khuyến nông SafeSearch bằng cách gọi nó vào truy vấn, sẽ chăm sóc các trường hợp x.Name là null.

public static class SearchExt 
{ 
    public static bool SafeSearch(this string q, string param) 
    { 
     return param == null ? false : param.ToLower().Contains(q); 
    } 
} 

và sau đó bạn có thể sử dụng nó với các phương pháp extesion

return source.Where(Predicate(q)); 

hoặc bằng cách sử dụng biểu LINQ

return from p in source 
     where Predicate(q).Invoke(p) 
     select p; 
+0

Điều này đang làm việc với phương pháp mở rộng, nhưng khi tôi thử sử dụng phương thức Invoke trong LINQ, tôi nhận được: LINQ to Entities không nhận ra phương thức 'Boolean Invoke (Localsip.Models.Wine)', và phương thức này không thể được dịch sang biểu thức cửa hàng. Bất kỳ ý tưởng về điều đó? – ManicBlowfish

+0

Ồ, đủ tốt. Vẫn muốn biết cách đưa Func "Predicate" vào truy vấn LINQ. Cám ơn sự giúp đở cuả bạn. – ManicBlowfish

+0

@ManicBlowfish bạn có thể chỉ cần quay trở lại từ p trong source.Where (Predicate (q)) chọn p –

1

Có một cách để chuẩn bị các truy vấn năng động và điều kiện, và cũng có thể sử dụng chức năng để xây dựng các bộ phận của chúng. Cú pháp cũng có thể đọc được, điều này sẽ làm cho phần "đơn giản" của câu hỏi. Có thể thông qua kết hợp biểu thức LINQ. Có một số bài viết về cách thức này có thể được thực hiện, nhưng tôi nghĩ rằng tôi đã đưa ra một cách tiếp cận mới. Ít nhất tôi không tìm thấy nó trên web.

Để tiếp tục, bạn cần thư viện gồm 3 hàm đơn giản. Họ sử dụng System.Linq.Expressions.ExpressionVisitor để tự động sửa đổi các biểu thức. Tính năng chính là hợp nhất các tham số bên trong biểu thức, sao cho 2 tham số có cùng tên được tạo giống hệt nhau (UnifyParametersByName). Phần còn lại sẽ thay thế một tham số có tên với biểu thức đã cho (ReplacePar) và phương thức trợ giúp (NewExpr). Thư viện có sẵn với giấy phép MIT trên github: LinqExprHelper, nhưng bạn có thể nhanh chóng viết một cái gì đó của riêng bạn.

Trước tiên, bạn xác định một số phương pháp, sau này có thể được sử dụng trong việc tạo truy vấn động.

public class Store 
{ 
    ... 

    public static Expression<Func<Store, bool>> 
     SafeSearchName(string sWhat) 
    { 
     return LinqExprHelper.NewExpr(
      (Store s) => s.Name != null && s.Name.ToLower().Contains(sWhat) 
     ); 
    } 

    public static Expression<Func<Store, bool>> 
     SafeSearchDesc(string sWhat) 
    { 
     return LinqExprHelper.NewExpr(
      (Store s) => s.Description != null && s.Description.ToLower().Contains(sWhat) 
     ); 
    } 
} 

Sau đó, bạn truy vấn theo cách này:

// Define a master condition, using named parameters. 
    var masterExpr = LinqExprHelper.NewExpr(
     (Store s, bool bSearchName, bool bSearchDesc) 
     => (bSearchName && bSearchDesc)); 

    // Replace stub parameters with some real conditions. 
    var combExpr = masterExpr 
     .ReplacePar("bSearchName", Store.SafeSearchName("b").Body) 
     .ReplacePar("bSearchDesc", Store.SafeSearchDesc("p").Body); 
     // Sometimes you may skip a condition using this syntax: 
     //.ReplacePar("bSearchDesc", Expression.Constant(true)); 

    // It's interesting to see how the final expression looks like. 
    Console.WriteLine("expr: " + combExpr); 

    // Execute the query using combined expression. 
    db.Stores 
     .Where((Expression<Func<Store, bool>>)combExpr) 
     .ToList().ForEach(i => { Console.WriteLine(i.Name + ", " + i.Description); }); 

Tôi không sử dụng này trong sản xuất, nhưng một số xét nghiệm đơn giản được thông qua. Tôi không thấy bất kỳ giới hạn nào trong việc kết hợp các truy vấn theo cách này.Nếu chúng ta cần thêm thông số, chúng ta có thể nối thêm mức kết hợp. Ưu điểm của phương thức này là bạn có thể sử dụng các biểu thức lambda nội tuyến, rất hay để đọc, cùng với việc tạo và tạo thành biểu thức năng động, rất có khả năng.

Sau khi tất cả có "đơn giản" không? Nếu bạn xem xét cú pháp phương pháp của Linq đơn giản, thì điều này gần như là đơn giản. Nó không cho phép bạn tạo các hàm LINQ tùy chỉnh, nhưng cho bạn khả năng so sánh.

+0

Biểu thức đã làm các trick cho tôi –