2009-12-17 33 views
18

Tôi có một biểu mẫu có nhiều trường trên đó (tên công ty, mã bưu điện, v.v.) cho phép người dùng tìm kiếm các công ty trong cơ sở dữ liệu. Nếu người dùng nhập các giá trị vào nhiều trường thì tôi cần phải tìm kiếm trên tất cả các trường đó. Tôi đang sử dụng LINQ để truy vấn cơ sở dữ liệu.Làm cách nào để kết hợp các biểu thức LINQ thành một?

Cho đến nay tôi đã quản lý để viết một hàm sẽ xem xét đầu vào của chúng và biến nó thành Danh sách biểu thức. Bây giờ tôi muốn biến List đó thành một biểu thức duy nhất mà sau đó tôi có thể thực thi thông qua nhà cung cấp LINQ.

nỗ lực ban đầu của tôi là như sau

private Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions) 
    { 
     if (expressions.Count == 0) 
     { 
      return null; 
     } 
     if (expressions.Count == 1) 
     { 
      return expressions[0]; 
     } 
     Expression<Func<Company, bool>> combined = expressions[0]; 
     expressions.Skip(1).ToList().ForEach(expr => combined = Expression.And(combined, expr)); 
     return combined; 
    } 

Tuy nhiên điều này không thành công với một thông điệp ngoại lệ dọc theo dòng của "Nhà điều hành nhị phân Và không được định nghĩa cho ...". Có ai có bất kỳ ý tưởng những gì tôi cần phải làm để kết hợp các biểu thức?

CHỈNH SỬA: Sửa dòng mà tôi đã quên gán kết quả và sắp xếp các biểu thức với nhau thành một biến. Cảm ơn bạn đã chỉ ra rằng mọi người.

Trả lời

9

CHỈNH SỬA: Câu trả lời của Jason hiện đã đầy đủ hơn tôi về mặt công cụ biểu hiện, vì vậy tôi đã xóa bit đó. Tuy nhiên, tôi muốn để lại điều này:

Tôi giả sử bạn đang sử dụng các điều khoản này cho một mệnh đề Where ... tại sao không chỉ gọi đến đâu với từng biểu thức lần lượt? Điều đó sẽ có tác dụng tương tự:

var query = ...; 
foreach (var condition in conditions) 
{ 
    query = query.Where(condition); 
} 
+1

@Jon Skeet: 'combined' sẽ được nhập là 'Expression'; bạn cần thực hiện một số công việc để trả về nó như là một công thức 'Expression >'. – jason

+0

Tôi đồng ý rằng mã đầu tiên của bạn dễ hiểu hơn, vì vậy tôi sẽ đưa ra câu trả lời đúng. Tuy nhiên tôi thực sự sẽ sử dụng đoạn thứ hai vì đây là chính xác những gì tôi cần - Tôi đã làm mọi thứ quá phức tạp, cảm ơn Jon. – gilles27

+1

Trớ trêu thay tôi đã chỉnh sửa trong khi cả hai nhận xét này đều được viết - nhưng vì đoạn trích thứ hai này đã được sử dụng, tôi nghĩ tôi sẽ để nó như cũ :) –

22

Bạn có thể sử dụng kết hợp với Enumerable.AggregateExpression.AndAlso. Dưới đây là một phiên bản generic:

Expression<Func<T, bool>> AndAll<T>(
    IEnumerable<Expression<Func<T, bool>>> expressions) { 

    if(expressions == null) { 
     throw new ArgumentNullException("expressions"); 
    } 
    if(expressions.Count() == 0) { 
     return t => true; 
    } 
    Type delegateType = typeof(Func<,>) 
          .GetGenericTypeDefinition() 
          .MakeGenericType(new[] { 
           typeof(T), 
           typeof(bool) 
          } 
         ); 
    var combined = expressions 
         .Cast<Expression>() 
         .Aggregate((e1, e2) => Expression.AndAlso(e1, e2)); 
    return (Expression<Func<T,bool>>)Expression.Lambda(delegateType, combined); 
} 

mã hiện tại của bạn không bao giờ được gán cho combined:

expr => Expression.And(combined, expr); 

trả về một mới Expression đó là kết quả của phép toán anding combinedexpr nhưng nó không đột biến combined.

+0

+1 cho câu trả lời tuyệt vời về mặt kỹ thuật. Tôi đã chấp nhận Jon's vì nó xuất hiện đơn giản hơn và việc anh ta sử dụng Where là thực sự những gì tôi nên làm. – gilles27

+1

@ gilles27: Vâng, nếu bạn chỉ sử dụng nó cho vị từ trong mệnh đề 'Where', thì câu trả lời của Jon là cách để đi. Nếu bạn cần một phiên bản tổng quát hơn, phiên bản của tôi sẽ giúp bạn. :-) – jason

0

Ở đây chúng tôi có câu hỏi chung về kết hợp các biểu thức LINQ. Tôi có một giải pháp chung cho vấn đề này. Tôi sẽ cung cấp một câu trả lời về vấn đề cụ thể được đăng, mặc dù nó chắc chắn không phải là cách để đi trong trường hợp như vậy. Nhưng khi các giải pháp đơn giản thất bại trong trường hợp của bạn, bạn có thể thử sử dụng phương pháp này.

Trước tiên, bạn cần thư viện bao gồm 2 chức năng đơ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). 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.

Thư viện cho phép cú pháp khá đơn giản để kết hợp các biểu thức phức tạp. Bạn có thể kết hợp 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.

private static Expression<Func<Company, bool>> Combine(IList<Expression<Func<Company, bool>>> expressions) 
    { 
     if (expressions.Count == 0) 
     { 
      return null; 
     } 

     // Prepare a master expression, used to combine other 
     // expressions. It needs more input parameters, they will 
     // be reduced later. 
     // There is a small inconvenience here: you have to use 
     // the same name "c" for the parameter in your input 
     // expressions. But it may be all done in a smarter way. 
     Expression <Func<Company, bool, bool, bool>> combiningExpr = 
      (c, expr1, expr2) => expr1 && expr2; 

     LambdaExpression combined = expressions[0]; 
     foreach (var expr in expressions.Skip(1)) 
     { 
      // ReplacePar comes from the library, it's an extension 
      // requiring `using LinqExprHelper`. 
      combined = combiningExpr 
       .ReplacePar("expr1", combined.Body) 
       .ReplacePar("expr2", expr.Body); 
     } 
     return (Expression<Func<Company, bool>>)combined; 
    } 
Các vấn đề liên quan