使用 Linq to Entities 的盒装值
Boxed Values Using Linq to Entities
这是对我的问题 的跟进,添加了一个新变量:Entity Framework。现在我能够在处理 ValueType 时生成必要的表达式,当 Linq-to-Entities 尝试处理查询时,我 运行 遇到了一个新问题。我收到以下错误:
System.NotSupportedException: Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
显然,Linq-to-Entities 不喜欢盒装值。这在我强制处理查询(通过 ToList()
或其他类型方法)时起作用,但在由数据库完成时不起作用,这将是理想的。
有没有办法让这个方法更通用,让 Linq-to-Entities 满意?请记住,直到运行时我才知道 属性 的类型。
public IEnumerable<Expression<Func<T, object>>> GetExpressions<T>(string sortedColumn) where T : IReportRecord
{
var columns = GetFullSortOrder(sortedColumn);
var typeParameter = Expression.Parameter(typeof(T));
foreach (var c in columns)
{
var propParameter = Expression.Property(typeParameter, c);
if (propParameter.Type.IsValueType)
{
var boxedPropParameter = Expression.Convert(propParameter, typeof(object));
yield return Expression.Lambda<Func<T, object>>(boxedPropParameter, typeParameter);
}
else
{
yield return Expression.Lambda<Func<T, object>>(propParameter, typeParameter);
}
}
}
实际上这个问题与之前生成Expression<Func<T, object>>
时需要Expression.Convert
的问题相反。考虑具有以下签名的方法
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
IEnumerable<Expression<Func<T, object>>> selectors)
必须根据传递的选择器创建 OrderBy
/ ThenBy
链。在这里,您需要 删除 Expression.Convert
只需要将值类型 V
.[=25 的 Expression<Func<T, V>>
转换为 Expression<Func<T, object>>
=]
让我们为这两种转换创建一个小的辅助方法:
public static Expression Wrap(this Expression source)
{
if (source.Type.IsValueType)
return Expression.Convert(source, typeof(object));
return source;
}
public static LambdaExpression Unwrap<T>(this Expression<Func<T, object>> source)
{
var body = source.Body;
if (body.NodeType == ExpressionType.Convert)
body = ((UnaryExpression)body).Operand;
return Expression.Lambda(body, source.Parameters);
}
现在原方法的实现可以很简单
public static IEnumerable<Expression<Func<T, object>>> GetExpressions<T>(string sortedColumn) where T : IReportRecord
{
var typeParameter = Expression.Parameter(typeof(T));
return from c in GetFullSortOrder(sortedColumn)
select Expression.Lambda<Func<T, object>>(
Expression.Property(typeParameter, c).Wrap(), typeParameter);
}
而且申请方法可能是这样的
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, object>>> keySelectors)
{
var result = source.Expression;
var method = "OrderBy";
foreach (var item in keySelectors)
{
var keySelector = item.Unwrap();
result = Expression.Call(
typeof(Queryable), method, new[] { typeof(T), keySelector.Body.Type },
result, Expression.Quote(keySelector));
method = "ThenBy";
}
return (IOrderedQueryable<T>)source.Provider.CreateQuery(result);
}
当然不必如此。在您的情况下,您可以将这两种方法合二为一(类似于第二种方法,但接收 string sortedColumn
),在这种情况下,您将简单地使用非通用 Expression.Lambda
方法而不用 [= 包装值类型24=]。
这是对我的问题
System.NotSupportedException: Unable to cast the type 'System.Int32' to type 'System.Object'. LINQ to Entities only supports casting EDM primitive or enumeration types.
显然,Linq-to-Entities 不喜欢盒装值。这在我强制处理查询(通过 ToList()
或其他类型方法)时起作用,但在由数据库完成时不起作用,这将是理想的。
有没有办法让这个方法更通用,让 Linq-to-Entities 满意?请记住,直到运行时我才知道 属性 的类型。
public IEnumerable<Expression<Func<T, object>>> GetExpressions<T>(string sortedColumn) where T : IReportRecord
{
var columns = GetFullSortOrder(sortedColumn);
var typeParameter = Expression.Parameter(typeof(T));
foreach (var c in columns)
{
var propParameter = Expression.Property(typeParameter, c);
if (propParameter.Type.IsValueType)
{
var boxedPropParameter = Expression.Convert(propParameter, typeof(object));
yield return Expression.Lambda<Func<T, object>>(boxedPropParameter, typeParameter);
}
else
{
yield return Expression.Lambda<Func<T, object>>(propParameter, typeParameter);
}
}
}
实际上这个问题与之前生成Expression<Func<T, object>>
时需要Expression.Convert
的问题相反。考虑具有以下签名的方法
public static IOrderedQueryable<T> OrderBy<T>(
this IQueryable<T> source,
IEnumerable<Expression<Func<T, object>>> selectors)
必须根据传递的选择器创建 OrderBy
/ ThenBy
链。在这里,您需要 删除 Expression.Convert
只需要将值类型 V
.[=25 的 Expression<Func<T, V>>
转换为 Expression<Func<T, object>>
=]
让我们为这两种转换创建一个小的辅助方法:
public static Expression Wrap(this Expression source)
{
if (source.Type.IsValueType)
return Expression.Convert(source, typeof(object));
return source;
}
public static LambdaExpression Unwrap<T>(this Expression<Func<T, object>> source)
{
var body = source.Body;
if (body.NodeType == ExpressionType.Convert)
body = ((UnaryExpression)body).Operand;
return Expression.Lambda(body, source.Parameters);
}
现在原方法的实现可以很简单
public static IEnumerable<Expression<Func<T, object>>> GetExpressions<T>(string sortedColumn) where T : IReportRecord
{
var typeParameter = Expression.Parameter(typeof(T));
return from c in GetFullSortOrder(sortedColumn)
select Expression.Lambda<Func<T, object>>(
Expression.Property(typeParameter, c).Wrap(), typeParameter);
}
而且申请方法可能是这样的
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, IEnumerable<Expression<Func<T, object>>> keySelectors)
{
var result = source.Expression;
var method = "OrderBy";
foreach (var item in keySelectors)
{
var keySelector = item.Unwrap();
result = Expression.Call(
typeof(Queryable), method, new[] { typeof(T), keySelector.Body.Type },
result, Expression.Quote(keySelector));
method = "ThenBy";
}
return (IOrderedQueryable<T>)source.Provider.CreateQuery(result);
}
当然不必如此。在您的情况下,您可以将这两种方法合二为一(类似于第二种方法,但接收 string sortedColumn
),在这种情况下,您将简单地使用非通用 Expression.Lambda
方法而不用 [= 包装值类型24=]。