Подтвердить что ты не робот

Создание выражения LINQ, где параметр равен объекту

Учитывая примитивный age ценности, я знаю, как создать такое выражение:

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

Делая это:

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

Это работает отлично, за исключением сценариев, в которых свойство и константа не являются примитивными типами.

Как построить аналогичное выражение, если сравнение между объектами?

С EF я могу просто написать:

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

Это также работает, но если я попытаюсь создать одно и то же выражение:

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

Я получаю сообщение об ошибке:

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

Мое подозрение в том, что Expression.Constant() только ожидает примитивных типов, поэтому мне нужно использовать другой метод фабрики выражений. (maype Expression.Object? - Я знаю, что этого не существует)

Есть ли способ создать выражение, которое сравнивает объекты? Почему EF может правильно интерпретировать его, если его скомпилированный оператор LINQ, но не когда он является выражением?

4b9b3361

Ответ 1

В дополнение к тому, что было упомянуто в предыдущих ответах. Более конкретное решение было бы таким:

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

ВАЖНЫЕ МОМЕНТЫ :

  1. Не забудьте проверить нули
  2. Убедитесь, что propertyType и valueType совместимы (либо они одного типа, либо конвертируемые)
  3. Здесь делается несколько предположений (например, что вы назначаете KeyAttribute)
  4. Этот код не проверен, поэтому он не совсем готов к копированию/вставке.

Надеюсь, это поможет.

Ответ 2

Вы не можете этого сделать, потому что EF не знает, как перевести сравнения сравнений в Location в выражение SQL.

Однако, если вы знаете, какие свойства Location вы хотите сравнить, вы можете сделать это с помощью анонимных типов:

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

Конечно, это эквивалентно:

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

Ответ 3

Дайте код ниже пробега. Я хотел бы проверить ваше предположение, что e => e.Location == местоположение компилируется во что-то, что может быть создано с помощью Expression.Equal, Expression.Property и 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 { }

Похоже, что это не так, и потому, что правильное выражение не является константой. Чтобы увидеть это, раскомментируйте прокомментированный код.

Я не понимаю, почему правильным выражением является выражение MemberExpression. Возможно, кто-то, кто знает компилятор выражения linq, может пролить свет на это, тогда я могу.

Изменение: это может иметь отношение к закрытию в lambdas - класс создается за кулисами, который содержит закрытые переменные. Тогда местоположение может быть членом этого класса. Я не уверен в этом, но это то, что я подозреваю.

Это сообщение может проливать дополнительный свет на ситуацию.