2013-04-12 13 views
17

Biorąc pod uwagę wartość prymitywny age wiem jak utworzyć wyrażenie takiego:Tworzenie wyrażeń LINQ gdzie parametr równy obiekt

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

Robiąc to:

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); 

To działa dobrze z wyjątkiem scenariuszy gdzie właściwość i stała nie są typami pierwotnymi.

Jak skonstruować podobną ekspresję jeśli porównanie jest między obiektami?

EF mogę tylko napisać:

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

który działa również, ale gdy próbuję utworzyć ten sam wyraz:

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); 

pojawia się błąd, który mówi:

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

Mój podejrzenie jest takie, że Expression.Constant() oczekuje tylko typów pierwotnych, więc muszę użyć innej metody fabryki ekspresji. (maype Expression.Object? - Wiem, że nie istnieje)

Czy istnieje sposób na stworzenie wyrażenia porównującego obiekty? Dlaczego EF potrafi poprawnie zinterpretować, jeśli jest to skompilowane wyrażenie LINQ, ale nie kiedy jest wyrażeniem?

+1

Tak samo na marginesie, użycie Expression.Constant zmusza serwer bazy danych do generowania nowego planu wykonania SQL za każdym razem, gdy wprowadzane są stałe zmiany. Może to mieć duży wpływ na wydajność, zobacz https://stackoverflow.com/questions/34845097/rewriting-a-linq-expression-query-to-enable-caching-sql-execution-plan –

Odpowiedz

4

Oprócz tego, o czym wspomniano w poprzednich odpowiedziach. Bardziej konkretne rozwiązanie pójdzie jako takie:

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 
      ) 
     ); 
    } 
} 

WAŻNE:

  1. upewnij się, aby sprawdzić, null
  2. Upewnij propertyType i valueType są zgodne (czy są one tego samego typu lub są zamienne)
  3. Podano tutaj kilka założeń (np. że przypisujesz KeyAttribute)
  4. Ten kod nie jest testowany, więc nie jest dokładnie gotowy do kopiowania/wklejania.

Nadzieję, że pomaga.

+2

czym jest "otherProperty" – Sinaesthetic

3

Nie można tego zrobić, ponieważ EF nie wie jak przetłumaczyć na Location porównań równości w wyrażeniu SQL.

Jednakże, jeśli wiesz, co właściwości Location chcesz porównać, można to zrobić za pomocą anonimowych typów:

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

Oczywiście to odpowiednik:

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

Co mnie myli, że 'pracownicy = DataContext.Employees.Where (e => e.Location == location); '** działa ** jeśli zapiszę to bezpośrednio, ale jeśli spróbuję stworzyć identyczne wyrażenie, to nie działa. Czy kompilator robi coś ekstra, czego nie można zrobić w środowisku wykonawczym? –

+1

Kompilator robi dużo mniej niż czas działania. Sprawdza tylko, czy typy są zgodne w C# dla tego wyrażenia. W czasie wykonywania EF faktycznie musi przekonwertować wyrażenie na zapytanie SQL, co jest znacznie trudniejsze. Widzisz błąd podczas wykonywania, a nie podczas kompilacji, ponieważ kompilator nie wie, że wyrażenie nie może zostać przekonwertowane. –

+0

Nie jestem również pewien, czy jasno się wyraziłem, ale zaktualizowałem to pytanie w nadziei, że dodam jasność. –

2

Podaj poniższy kod bieg. Chciałem przetestować twoje założenie, że e => e.Location == lokalizacja kompiluje się w coś, co można skonstruować za pomocą Expression.Equal, Expression.Property i 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 { } 

Wygląda na to, że nie jest, i dlatego, że właściwe wyrażenie nie jest stałą. Aby to zobaczyć, odkomentuj skomentowany kod.

Co ja nie rozumiem, dlaczego prawo jest wyrazem jest MemberExpression. Być może ktoś, kto zna kompilator ekspresji linq, może rzucić więcej światła na to, co mogę.

Edit: To może mieć do czynienia z zamknięciem w lambdas - klasa jest tworzona za kulisami, który zawiera zamknięty nad zmiennymi. Lokalizacja może wówczas należeć do tej klasy. Nie jestem tego pewien, ale podejrzewam.

This post mogą rzucić dodatkowe światło na sytuację.

+0

Świetnie! Pan zrozumiał mój problem! Myślę, że zmierzamy we właściwym kierunku. –

+0

Miło to słyszeć. Dodany przeze mnie link zasugerował 1), że przechwyconych zmiennych nie można dodawać do wyrażeń linq w środowisku wykonawczym, a 2) LinqKit może być w stanie pomóc. Nie przeczytałem dogłębnie, ale myślę, że docenicie tę dyskusję. – Doug