2013-04-12 20 views
17

Cho một giá trị nguyên thủy age tôi biết làm thế nào để tạo ra một biểu hiện như thế này:Tạo một biểu thức LINQ mà thông số tương đương với đối tượng

//assuming: age is an int or some other primitive type 
employee => employee.Age == age 

Bằng cách này:

var entityType = typeof(Employee); 
var propertyName = "Age"; 
int age = 30; 
var parameter = Expression.Parameter(entityType, "entity"); 

var lambda = Expression.Lambda(
     Expression.Equal(
      Expression.Property(parameter, propertyName), 
      Expression.Constant(age) 
     )      
    , parameter); 

Đó hoạt động tốt, ngoại trừ trong các kịch bản trong đó tài sản và hằng số được đề cập không phải là các kiểu nguyên thủy.

Làm cách nào để xây dựng biểu thức tương tự nếu so sánh giữa các đối tượng?

Với EF tôi chỉ có thể viết:

Location location = GetCurrentLocation(); 
employees = DataContext.Employees.Where(e => e.Location == location); 

Đó cũng làm việc, nhưng nếu tôi cố gắng tạo ra các biểu hiện giống nhau:

var entityType = typeof(Employee); 
var propertyName = "Location"; 
var location = GetCurrentLocation(); 
var parameter = Expression.Parameter(entityType, "entity"); 

var lambda = Expression.Lambda(
     Expression.Equal(
      Expression.Property(parameter, propertyName), 
      Expression.Constant(location) 
     )      
    , parameter); 

tôi nhận được một lỗi mà nói:

Unable to create a constant value of type 'Location'. Only primitive types or enumeration types are supported in this context.

nghi ngờ là Expression.Constant() chỉ mong đợi các loại nguyên thủy, vì vậy tôi cần phải sử dụng một phương thức biểu hiện nhà máy khác nhau. (maype Expression.Object? - Tôi biết điều đó không tồn tại)

Có cách nào để tạo biểu thức so sánh các đối tượng không? Tại sao EF có thể giải thích nó một cách chính xác nếu một câu lệnh LINQ được biên dịch của nó, nhưng không phải khi nó là một biểu thức?

+1

Cũng giống như một lưu ý phụ, sử dụng Expression.Constant buộc máy chủ cơ sở dữ liệu tạo một kế hoạch thực thi SQL mới mỗi lần thay đổi liên tục. Điều này có thể có tác động hiệu suất lớn, xem https://stackoverflow.com/questions/34845097/rewriting-a-linq-expression-query-to-enable-caching-sql-execution-plan –

Trả lời

4

Ngoài những gì đã được đề cập trong các câu trả lời trước. Một giải pháp cụ thể hơn sẽ đi như vậy:

public static Expression CreateExpression<T>(string propertyName, object valueToCompare) 
{ 
    // get the type of entity 
    var entityType = typeof(T); 
    // get the type of the value object 
    var valueType = valueToCompare.GetType(); 
    var entityProperty = entityType.GetProperty(propertyName); 
    var propertyType = entityProperty.PropertyType; 


    // Expression: "entity" 
    var parameter = Expression.Parameter(entityType, "entity"); 

    // check if the property type is a value type 
    // only value types work 
    if (propertyType.IsValueType || propertyType.Equals(typeof(string))) 
    { 
     // Expression: entity.Property == value 
     return Expression.Equal(
      Expression.Property(parameter, entityProperty), 
      Expression.Constant(valueToCompare) 
     ); 
    } 
    // if not, then use the key 
    else 
    { 
     // get the key property 
     var keyProperty = propertyType.GetProperties().FirstOrDefault(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Length > 0); 

     // Expression: entity.Property.Key == value.Key 
     return Expression.Equal(
      Expression.Property(
       Expression.Property(parameter, entityProperty), 
       keyProperty 
      ), 
      Expression.Constant(
       keyProperty.GetValue(valueToCompare), 
       keyProperty.PropertyType 
      ) 
     ); 
    } 
} 

ĐIỂM QUAN TRỌNG:

  1. Hãy chắc chắn để kiểm tra null
  2. Hãy chắc chắn rằng propertyTypevalueType tương thích (hoặc là họ đang cùng loại hoặc có thể chuyển đổi)
  3. Một số giả định được thực hiện ở đây (ví dụ: bạn chỉ định KeyAttribute)
  4. Mã này không được kiểm tra, do đó, nó không phải là chính xác sao chép/dán đã sẵn sàng.

Hy vọng điều đó sẽ hữu ích.

+2

'OtherProperty' là gì – Sinaesthetic

3

Bạn không thể làm điều đó vì EF không biết cách dịch so sánh bình đẳng trên Location thành một biểu thức SQL.

Tuy nhiên, nếu bạn biết những gì thuộc tính của Location bạn muốn so sánh, bạn có thể làm điều này với các loại vô danh:

var location = GetCurrentLocation(); 
var locationObj = new { location.LocationName, location.LocationDescription }; 
employees = DataContext.Employees.Where(e => new { e.Location.LocationName, e.Location.Description } == locationObj); 

Tất nhiên đó là tương đương với:

var location = GetCurrentLocation(); 
employees = DataContext.Employees.Where(e => e.Location.LocationName == location.Name && 
              e.Location.Description == location.Description); 
+0

Điều làm tôi bối rối là 'nhân viên = DataContext.Employees.Where (e => e.Location == location); '** works ** nếu tôi viết nó ra trực tiếp như thế, nhưng nếu tôi cố tạo một biểu thức giống nhau, nó không hoạt động. Trình biên dịch có làm điều gì đó thêm mà không thể thực hiện được khi chạy không? –

+1

Trình biên dịch đang hoạt động ít hơn rất nhiều so với thời gian chạy. Nó chỉ kiểm tra xem các loại có tương thích trong C# cho biểu thức này hay không. Tại thời gian chạy EF thực sự phải chuyển đổi biểu thức thành truy vấn SQL, điều này khó hơn nhiều. Bạn đang nhìn thấy lỗi tại thời gian chạy và không phải lúc biên dịch vì trình biên dịch không biết biểu thức không thể được chuyển đổi. –

+0

Tôi cũng không chắc chắn liệu mình có làm rõ hay không, nhưng tôi đã cập nhật câu hỏi với hy vọng cung cấp thêm sự rõ ràng. –

2

Đưa vào mã bên dưới chạy. Tôi muốn kiểm tra giả định của bạn rằng e => e.Location == location được biên dịch thành một thứ có thể được xây dựng với Expression.Equal, Expression.Property và Expression.Constant.

class Program { 
     static void Main(string[] args) { 
      var location = new Location(); 
      Expression<Func<Employee, bool>> expression = e => e.Location == location; 

      var untypedBody = expression.Body; 

      //The untyped body is a BinaryExpression 
      Debug.Assert(
       typeof(BinaryExpression).IsAssignableFrom(untypedBody.GetType()), 
       "Not Expression.Equal"); 

      var body = (BinaryExpression)untypedBody; 
      var untypedLeft = body.Left; 
      var untypedRight = body.Right; 

      //The untyped left expression is a MemberExpression 
      Debug.Assert(
       typeof(MemberExpression).IsAssignableFrom(untypedLeft.GetType()), 
       "Not Expression.Property"); 

      ////The untyped right expression is a ConstantExpression 
      //Debug.Assert(
      // typeof(ConstantExpression).IsAssignableFrom(untypedRight.GetType()),     
      // "Not Expression.Constant"); 

      //The untyped right expression is a MemberExpression? 
      Debug.Assert(
       typeof(MemberExpression).IsAssignableFrom(untypedRight.GetType()))); 
    } 
} 

public class Employee 
{ 
    public Location Location { get; set; } 
} 

public class Location { } 

Có vẻ như không phải vậy, và vì biểu thức đúng không phải là Hằng số. Để thấy điều này, hãy bỏ ghi chú ra mã.

Điều tôi không hiểu là tại sao biểu thức đúng là MemberExpression. Có lẽ ai đó biết trình biên dịch biểu thức linq có thể làm sáng tỏ điều này sau đó tôi có thể.

Chỉnh sửa: Điều này có thể liên quan đến việc đóng cửa trong lambdas - một lớp được tạo phía sau hậu trường chứa các biến đóng trên các biến. Vị trí có thể là thành viên của lớp đó. Tôi không chắc về điều này, nhưng đó là điều tôi nghi ngờ.

This post có thể làm sáng tỏ thêm về tình huống.

+0

Tuyệt vời! Bạn thưa ông hiểu vấn đề của tôi! Tôi nghĩ chúng ta đang đi đúng hướng. –

+0

Rất vui khi được nghe. Liên kết tôi vừa thêm gợi ý 1) mà các biến bị bắt không thể được thêm vào các biểu thức LINQ trong thời gian chạy, và 2) LINQKit có thể giúp đỡ. Tôi đã không đọc sâu, nhưng tôi nghĩ bạn có thể đánh giá cao cuộc thảo luận. – Doug

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