【问题标题】:Boxed Values Using Linq to Entities使用 Linq 到实体的装箱值
【发布时间】:2017-02-04 22:50:45
【问题描述】:

这是对我的问题Dynamic Expression Generation Issues with ValueTypes 的后续处理,添加了一个新变量:实体框架。现在我能够在处理 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);
        }
    }
}

【问题讨论】:

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


【解决方案1】:

实际上这个问题与之前生成Expression&lt;Func&lt;T, object&gt;&gt; 时需要Expression.Convert 的问题相反。考虑一个具有以下签名的方法

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

它必须从传递的选择器创建OrderBy / ThenBy 链。在这里,您需要删除 Expression.Convert,以便将值类型 VExpression&lt;Func&lt;T, V&gt;&gt; 转换为 Expression&lt;Func&lt;T, object&gt;&gt; 成为可能。

让我们为这两种转换创建一个小的辅助方法:

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 方法,而不用Convert 包装值类型。

【讨论】:

  • 这正是我所需要的,完美!谢谢伊万!唯一的区别是,我没有使用扩展方法,而是让 GetExpressions 方法按照您的建议返回非通用版本。工作流畅!
猜你喜欢
  • 2012-11-22
  • 1970-01-01
  • 2011-01-01
  • 2015-01-10
  • 1970-01-01
  • 1970-01-01
  • 2017-04-11
  • 1970-01-01
  • 2013-08-02
相关资源
最近更新 更多