使用Linq实体的盒装值

时间:2017-02-03 20:57:12

标签: c# entity-framework linq dynamic linq-expressions

这是我的问题Dynamic Expression Generation Issues with ValueTypes的后续内容,它添加了一个新变量:Entity Framework。现在我能够在处理ValueTypes时生成必要的表达式,当Linq-to-Entities尝试处理查询时,我遇到了一个新问题。我收到以下错误:

  

System.NotSupportedException:无法转换类型' System.Int32'键入' System.Object'。 LINQ to Entities仅支持转换EDM原语或枚举类型。

显然,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);
        }
    }
}

1 个答案:

答案 0 :(得分:2)

实际上,此问题与之前生成Expression.Convert时需要Expression<Func<T, object>>的问题相反。考虑具有以下签名的方法

public static IOrderedQueryable<T> OrderBy<T>(
    this IQueryable<T> source,
    IEnumerable<Expression<Func<T, object>>> selectors)

必须从传递的选择器创建OrderBy / ThenBy链。在这里,您需要删除所需的Expression.Convert,才能将价值类型Expression<Func<T, V>>的{​​{1}}转换为Expression<Func<T, object>>

让我们为两次转换创建一个小帮助方法:

V

现在原始方法的实现可能只是

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方法而不用{{包装值类型1}}。