使用 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=]。