C# 创建参数等于对象的LINQ表达式

C# 创建参数等于对象的LINQ表达式,c#,linq,entity-framework-5,C#,Linq,Entity Framework 5,给定一个基本值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, "ent

给定一个基本值
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);
我得到一个错误,上面写着:

无法创建“Location”类型的常量值。在此上下文中仅支持基元类型或枚举类型。

我怀疑
Expression.Constant()
只需要基元类型,所以我需要使用不同的表达式工厂方法。(maype
Expression.Object
?-我知道它不存在)


有没有办法创建一个比较对象的表达式?为什么如果它是一个编译的LINQ语句,EF能够正确地解释它,而当它是一个表达式时,EF却不能正确地解释它?

您不能这样做,因为EF不知道如何将
位置上的等式比较转换为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);

运行下面的代码。我想测试您的假设,即e=>e.Location==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 { }
类程序{
静态void Main(字符串[]参数){
var location=新位置();
表达式=e=>e.Location==Location;
var untypedBody=expression.Body;
//未类型化的主体是二进制表达式
断言(
typeof(BinaryExpression).IsAssignableFrom(untypedBody.GetType()),
“非表达式。相等”);
var body=(二进制表达式)untypedBody;
var untypedLeft=body.Left;
var untypedRight=body.Right;
//未类型化的左表达式是MemberExpression
断言(
typeof(MemberExpression).IsAssignableFrom(untypedLeft.GetType()),
“非表达财产”);
////未键入的右表达式是恒常表达式
//断言(
//typeof(ConstantExpression).IsAssignableFrom(untypedRight.GetType()),
//“非表达式常数”);
//未类型化的right表达式是MemberExpression吗?
断言(
typeof(MemberExpression).IsAssignableFrom(untypedRight.GetType());
}
}
公营雇员
{
公共位置位置{get;set;}
}
公共类位置{}
似乎不是,这是因为正确的表达式不是常数。要查看这一点,请取消注释注释掉的代码

我不明白的是为什么正确的表达式是MemberExpression。也许了解linq表达式编译器的人可以比我更清楚地了解这一点

编辑:这可能与lambdas中的闭包有关-在幕后创建一个类,其中包含闭包变量。然后,该位置可能是该类的成员。我不确定,但我怀疑这是真的


除了前面的回答中提到的内容外,还可以进一步了解情况。

。更具体的解决方案如下:

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
            )
        );
    }
}
公共静态表达式CreateExpression(string propertyName,object valueToCompare)
{
//获取实体的类型
var entityType=typeof(T);
//获取值对象的类型
var valueType=valueToCompare.GetType();
var entityProperty=entityType.GetProperty(propertyName);
var propertyType=entityProperty.propertyType;
//表述:“实体”
var参数=Expression.parameter(entityType,“实体”);
//检查属性类型是否为值类型
//只有值类型起作用
if(propertyType.IsValueType | | propertyType.Equals(typeof(string)))
{
//表达式:entity.Property==value
返回表达式。相等(
Expression.Property(参数、entityProperty),
表达式.常量(valueToCompare)
);
}
//如果没有,则使用该键
其他的
{
//获取密钥属性
var keyProperty=propertyType.GetProperties().FirstOrDefault(p=>p.GetCustomAttributes(typeof(KeyAttribute),false)。长度>0);
//表达式:entity.Property.Key==value.Key
返回表达式。相等(
表达式.属性(
Expression.Property(参数、entityProperty),
键属性
),
表达式.常数(
keyProperty.GetValue(valueToCompare),
keyProperty.PropertyType
)
);
}
}
要点:

  • 确保检查空值
  • 确保
    propertyType
    valueType
    兼容(它们要么是相同的类型,要么是可转换的)
  • 这里有几个假设(例如,您确实分配了
    KeyAttribute
  • 这段代码没有经过测试,因此没有完全准备好复制/粘贴

  • 希望能有所帮助。

    让我困惑的是
    employees=DataContext.employees.Where(e=>e.Location==Location)会起作用,但是如果我试图创建一个相同的表达式,它就不起作用了。编译器是否做了运行时无法完成的额外工作?编译器所做的工作比运行时少得多。只是检查一下,对于这个表达式,这些类型在C#中是否兼容。在运行时,EF实际上必须将表达式转换为SQL查询,这要困难得多。您在运行时看到错误,而不是在编译时看到错误