2012-06-22 19 views
10

Tôi đang trong quá trình viết một lớp dữ liệu cho một phần của hệ thống của chúng tôi ghi nhật ký thông tin về các công việc tự động chạy mỗi ngày - tên công việc , nó kéo dài bao lâu, kết quả là gì, vvSử dụng LINQ ExpressionVisitor để thay thế các tham số nguyên thủy với tham chiếu thuộc tính trong biểu thức lambda

Tôi đang nói chuyện với cơ sở dữ liệu bằng Entity Framework, nhưng tôi đang cố giữ những chi tiết đó ẩn khỏi các mô-đun cấp cao hơn và tôi không muốn các đối tượng thực thể được phơi bày.

Tuy nhiên, tôi muốn làm cho giao diện của mình rất linh hoạt trong tiêu chí mà nó sử dụng để tra cứu thông tin công việc. Ví dụ: giao diện người dùng sẽ cho phép người dùng thực hiện các truy vấn phức tạp như "cung cấp cho tôi tất cả công việc có tên 'hello' chạy từ 10:00 sáng đến 11:00 sáng." Rõ ràng, điều này trông giống như một công việc cho các cây Expression được tạo động.

Vì vậy, những gì tôi muốn lớp dữ liệu của tôi (kho) để có thể làm là chấp nhận LINQ biểu thức kiểu Expression<Func<string, DateTime, ResultCode, long, bool>> (biểu thức lambda) và sau đó đằng sau hậu trường chuyển đổi đó lambda để một biểu thức Entity Framework ObjectContext tôi có thể sử dụng dưới dạng bộ lọc bên trong mệnh đề Where().

Tóm lại, tôi đang cố chuyển đổi biểu thức lambda loại Expression<Func<string, DateTime, ResultCode, long, bool>> thành Expression<Func<svc_JobAudit, bool>>, trong đó svc_JobAudit là đối tượng dữ liệu Khuôn khổ thực thể tương ứng với bảng nơi lưu trữ thông tin công việc. (Bốn thông số trong đại biểu đầu tiên tương ứng với tên của công việc, khi nó chạy, kết quả và khoảng thời gian đó đã lấy trong MS tương ứng)

Tôi đã đạt được tiến bộ rất tốt bằng cách sử dụng lớp ExpressionVisitor cho đến khi tôi nhấn một bức tường gạch và nhận được một InvalidOperationException với thông báo lỗi này:

Khi gọi từ 'VisitLambda', viết lại một nút kiểu 'System.Linq.Expressions.ParameterExpression' phải trả lại một giá trị null không của cùng loại. Ngoài ra, ghi đè 'VisitLambda' và thay đổi nó để không truy cập trẻ em thuộc loại này.

Tôi hoàn toàn bối rối. Tại sao heck nó sẽ không cho phép tôi chuyển đổi các nút biểu thức tham chiếu tham số đến các nút tham chiếu thuộc tính? Có cách nào khác để giải quyết vấn đề này không?

Dưới đây là một số mẫu mã:

namespace ExpressionTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      Expression<Func<string, DateTime, ResultCode, long, bool>> expression = (myString, myDateTime, myResultCode, myTimeSpan) => myResultCode == ResultCode.Failed && myString == "hello"; 
      var result = ConvertExpression(expression); 
     } 

     private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, ResultCode, long, bool>> expression) 
     { 
      var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(new ReplaceVisitor().Modify(expression), Expression.Parameter(typeof(svc_JobAudit))); 
      return newExpression; 
     } 
    } 

    class ReplaceVisitor : ExpressionVisitor 
    { 
     public Expression Modify(Expression expression) 
     { 
      return Visit(expression); 
     } 

     protected override Expression VisitParameter(ParameterExpression node) 
     { 
      if (node.Type == typeof(string)) 
      { 
       return Expression.Property(Expression.Parameter(typeof(svc_JobAudit)), "JobName"); 
      } 
      return node; 
     } 
    } 
} 
+1

[Thay tham số trong biểu thức lambda] (http://stackoverflow.com/questions/11159697/replace-parameter-in-lambda-expression) –

Trả lời

6

Vấn đề là hai lần:

  • tôi đã hiểu lầm như thế nào đến thăm các kiểu biểu thức Lambda. Tôi vẫn đang quay trở lại một lambda mà phù hợp với đại biểu cũ thay vì trả lại một lambda mới để phù hợp với đại biểu mới.

  • Tôi cần giữ tham chiếu đến phiên bản ParameterExpression mới mà tôi không làm.

Các mã mới trông như thế này (chú ý cách người truy cập bây giờ chấp nhận một tham chiếu đến một ParameterExpression phù hợp với các đối tượng dữ liệu Entity Framework):

class Program 
{ 
    const string conString = @"myDB"; 

    static void Main(string[] args) 
    { 
     Expression<Func<string, DateTime, byte, long, bool>> expression = (jobName, ranAt, resultCode, elapsed) => jobName == "Email Notifications" && resultCode == (byte)ResultCode.Failed; 
     var criteria = ConvertExpression(expression); 

     using (MyDataContext dataContext = new MyDataContext(conString)) 
     { 
      List<svc_JobAudit> jobs = dataContext.svc_JobAudit.Where(criteria).ToList(); 
     } 
    } 

    private static Expression<Func<svc_JobAudit, bool>> ConvertExpression(Expression<Func<string, DateTime, byte, long, bool>> expression) 
    { 
     var jobAuditParameter = Expression.Parameter(typeof(svc_JobAudit), "jobAudit"); 
     var newExpression = Expression.Lambda<Func<svc_JobAudit, bool>>(new ReplaceVisitor().Modify(expression.Body, jobAuditParameter), jobAuditParameter); 
     return newExpression; 
    } 
} 

class ReplaceVisitor : ExpressionVisitor 
{ 
    private ParameterExpression parameter; 

    public Expression Modify(Expression expression, ParameterExpression parameter) 
    { 
     this.parameter = parameter; 
     return Visit(expression); 
    } 

    protected override Expression VisitLambda<T>(Expression<T> node) 
    { 
     return Expression.Lambda<Func<svc_JobAudit, bool>>(Visit(node.Body), Expression.Parameter(typeof(svc_JobAudit))); 
    } 

    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     if (node.Type == typeof(string)) 
     { 
      return Expression.Property(parameter, "JobName"); 
     } 
     else if (node.Type == typeof(DateTime)) 
     { 
      return Expression.Property(parameter, "RanAt"); 
     } 
     else if (node.Type == typeof(byte)) 
     { 
      return Expression.Property(parameter, "Result"); 
     } 
     else if (node.Type == typeof(long)) 
     { 
      return Expression.Property(parameter, "Elapsed"); 
     } 
     throw new InvalidOperationException(); 
    } 
} 
+0

này là hữu ích, cảm ơn. –

+0

Ghi đè của VisitLambda trong ExpressionVisitor có vấn đề với các biểu thức lambda lồng nhau. Giả sử (ví dụ đã tạo), biểu thức đang được chuyển đổi là: (jobName, ranAt, resultCode, elapsed) => AuditLogTable.Any (a => a.JobName == jobName && a.Date> = ranAt) ; Tiểu lambda "a => a.JobName ..." sẽ được xử lý bởi VisitLambda và chuyển đổi thành: svc_JobAudit => a.JobName == jobName && a.Date> = ranAt ... mà sẽ thất bại. Trong trường hợp của tôi, tôi vừa xóa bỏ ghi đè (chỉ được gọi cho phụ lambdas và không được gọi cho lambda cấp cao nhất) –

1

Câu trả lời được chấp nhận là 'hardcoded' đối với một số cụ thể loại. Dưới đây là một biểu thức viết lại tổng quát hơn có thể thay thế một tham số cho bất kỳ biểu thức nào khác (lambda, hằng số, ...). Trong trường hợp biểu thức lambda, chữ ký của biểu thức cần thay đổi để kết hợp các thông số cần thiết bởi giá trị được thay thế.

public class ExpressionParameterSubstitute : System.Linq.Expressions.ExpressionVisitor 
{ 
    private readonly ParameterExpression from; 
    private readonly Expression to; 
    public ExpressionParameterSubstitute(ParameterExpression from, Expression to) 
    { 
     this.from = from; 
     this.to = to; 
    } 

    protected override Expression VisitLambda<T>(Expression<T> node) 
    { 
     if (node.Parameters.All(p => p != this.from)) 
      return node; 

     // We need to replace the `from` parameter, but in its place we need the `to` parameter(s) 
     // e.g. F<DateTime,Bool> subst F<Source,DateTime> => F<Source,bool> 
     // e.g. F<DateTime,Bool> subst F<Source1,Source2,DateTime> => F<Source1,Source2,bool> 

     var toLambda = to as LambdaExpression; 
     var substituteParameters = toLambda?.Parameters ?? Enumerable.Empty<ParameterExpression>(); 

     ReadOnlyCollection<ParameterExpression> substitutedParameters 
      = new ReadOnlyCollection<ParameterExpression>(node.Parameters 
       .SelectMany(p => p == this.from ? substituteParameters : Enumerable.Repeat(p, 1)) 
       .ToList()); 

     var updatedBody = this.Visit(node.Body);  // which will convert parameters to 'to' 
     return Expression.Lambda(updatedBody, substitutedParameters); 
    } 

    protected override Expression VisitParameter(ParameterExpression node) 
    { 
     var toLambda = to as LambdaExpression; 
     if (node == from) return toLambda?.Body ?? to; 
     return base.VisitParameter(node); 
    } 
} 
Các vấn đề liên quan