创建一个LINQ表达式,其中参数等于对象
本文关键字:参数 于对象 对象 表达式 一个 LINQ 创建 | 更新日期: 2023-09-27 18:08:48
给定原始值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()
只期望原始类型,所以我需要使用不同的表达式工厂方法。(可能是Expression.Object
?-我知道这是不存在的)
是否有一种方法来创建一个比较对象的表达式?为什么EF能够正确地解释它,如果它是一个编译的LINQ语句,但不是当它是一个表达式?
除了在前面的答案中提到的。更具体的解决方案如下:
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
)
);
}
}
重点:
- 确保检查null
- 确保
propertyType
和valueType
是兼容的(要么他们是相同的类型或可转换) - 这里有几个假设(例如,你确实分配了一个
KeyAttribute
) - 这段代码没有经过测试,所以还不能完全复制/粘贴。
希望对你有帮助。
您不能这样做,因为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);
运行下面的代码。我想测试你的假设,即e => e.Location == location正在编译成可以用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中的闭包有关-一个类是在后台创建的,其中包含封闭的变量。位置可以是该类的成员。我不确定,但这是我的怀疑。