创建其中参数等于对象LINQ表达式(Creating a LINQ Expression where

2019-08-22 11:21发布

鉴于原始值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语句,但不是当它是一种表达?

Answer 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. 确保propertyTypevalueType是兼容的(无论它们是同一类型或者是敞篷车)
  3. 几个假设这里做(比如,你做分配KeyAttribute
  4. 此代码没有经过测试,所以它是不完全复制/粘贴准备。

希望帮助。



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


Answer 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表达式编译器可以摆脱更多的光线进入这个话,我可以。

编辑:这可能在lambda表达式与关闭的事情 - 它包含关闭了变量在幕后,创建一类。 然后,位置可能是类的成员。 我不知道这一点,但它是我怀疑。

这篇文章可能对局势摆脱额外的光。



文章来源: Creating a LINQ Expression where parameter equals object