Entity Framework:LINQ to Entities 仅支持将实体数据模型基本类型转换

26
我编写了一个方法,允许传递表达式作为orderby子句,但我遇到了这个问题。
无法将类型'System.DateTime'转换为类型'System.IComparable'。LINQ to Entities仅支持将Entity Data Model原始类型转换。
基本上,表达式是这样的:
Expression<Func<K, IComparable>> orderBy

这是它的使用方法:

SomeEntities.SomeTable
.Where
(
   whereClause
)
.Select
(
   selectClause
)
.OrderBy(orderBy)

这个想法是我可以使用一个字典来保存与表达式匹配的字符串,例如:

_possibleSortForForumItem.Add("CreateDate", item => item.CreateDate);

我有一个方法,它接受排序字符串并返回表达式,如果它与字典中的键匹配,则返回表达式,否则返回一些默认值。(这个想法是控制可以按什么排序的方式)现在,这对于字符串属性有效,但到目前为止,对于日期时间或整数,我得到了上面的错误消息。
就我(粗略地)理解的问题而言,Entity Framework 需要它成为主/EDM 类型,因为它必须将 C# DateTime 转换为数据库可以处理的内容。
有没有办法将日期时间转换为原始类型,以便仍然可以工作?
解决方案:
获取排序方法的方法:(接受查询并以“有序形式”返回)
private static Func<IQueryable<ForumViewItem>, IOrderedQueryable<ForumViewItem>> GetMethodForSort(String sortBy)
{
  if (_methodForSort == null)
  {
    _methodForSort = new Dictionary<String, Func<IQueryable<ForumViewItem>, IOrderedQueryable<ForumViewItem>>>();
    _methodForSort.Add(SortForumViewItemCreatedOn, item => item.OrderBy(innerItem => innerItem.CreatedOn));
    ...
  }

  Func<IQueryable<ForumViewItem>, IOrderedQueryable<ForumViewItem>> orderMethod;

  if(String.IsNullOrEmpty(sortBy) || !_methodForSort.ContainsKey(sortBy))
  {
    orderMethod = _methodForSort["ForumName"];
  }
  else
  {
    orderMethod = _methodForSort[sortBy];
  }

  return orderMethod;
}

通用查询方法的方法签名为:
IList<K> GetListForGrid<T, K>(this ObjectQuery<T> query, ... Func<IQueryable<K>, IOrderedQueryable<K>> orderBy, ...)

使用传入的方法:

initialQuery = query
  .Where
  (
    somethingEqualsSomething
  )
  .Select
  (
    selectClause
  );

var orderedQuery = orderBy(initialQuery);

returnValue = orderedQuery
  .Skip(numberToShow * realPage)
  .Take(numberToShow)
  .ToList();
5个回答

25
我知道这篇文章比较老,但我正在寻找与 OP 完全相同的解决方案,不想在我的字典中使用 Func<IQueryable<T>, IOrderedQueryable<T>>。主要是因为我必须实现 OrderByOrderByDescending 委托。
最终,我为 IQueryable 创建了一个名为 ObjectSort 的扩展方法,它会简单地检查表达式的返回类型,然后创建一个新的 Lambda 表达式,以使 LINQ to Entities 不会出错。
我不确定这是否是一个好的解决方案,但下面的示例可以处理 DateTimeint,所以如果您正在寻找类似的东西,希望它能给您一些思路!
public static IOrderedQueryable<T> ObjectSort<T>(this IQueryable<T> entities, Expression<Func<T, object>> expression, SortOrder order = SortOrder.Ascending)
{
    var unaryExpression = expression.Body as UnaryExpression;
    if (unaryExpression != null)
    {
        var propertyExpression = (MemberExpression)unaryExpression.Operand;
        var parameters = expression.Parameters;

        if (propertyExpression.Type == typeof(DateTime))
        {
            var newExpression = Expression.Lambda<Func<T, DateTime>>(propertyExpression, parameters);
            return order == SortOrder.Ascending ? entities.OrderBy(newExpression) : entities.OrderByDescending(newExpression);
        }

        if (propertyExpression.Type == typeof(int))
        {
            var newExpression = Expression.Lambda<Func<T, int>>(propertyExpression, parameters);
            return order == SortOrder.Ascending ? entities.OrderBy(newExpression) : entities.OrderByDescending(newExpression);
        }

        throw new NotSupportedException("Object type resolution not implemented for this type");
    }
    return entities.OrderBy(expression);
}

5
感谢这个很棒的扩展!但是它仍然不能正确地排序字符串列。我通过将最后一行更改为: return order == SortOrder.Ascending ? entities.OrderBy(expression) : entities.OrderByDescending(expression); 来解决了这个问题。 - SoftwareFactor

13

Entity Framework使这变得困难,我不确定是否有一种方法可以使用单个返回值类型(如IComparable、object等)来实现您想要做的事情。您可能考虑将设计改为名称到Func<IQueryable<K>, IOrderedQueryable<K>>值的字典:

_possibleSortForForumItem.Add("CreateDate", 
    query => query.OrderBy(item.CreateDate));

然后这样应用:

var orderedQuery = query.OrderBy(item => item.DefaultOrderColumn);

Func<IQueryable<K>, IOrderedQueryable<K>> assignOrderBy = null;

if (_possibleSortForForumItem.TryGetValue(orderColumnName, out assignOrderBy))
{
    orderedQuery = assignOrderBy(query);
}

我建议将Func的结果设置为IOrderedQueryable<K>,这将在编译时强制使用OrderBy,因为例如分页不喜欢无序查询。 - Davy Landman
花了我几分钟时间才弄明白如何操作它,但最终它非常可行。 - Programmin Tool
请注意,如果我没有弄错的话,这个签名只适用于IEnumerables而不是IQueryables。再次强调,如果我没有弄错,这意味着它将在结果集上运行,因此LINQ引擎不会将其转换为SQL,而是将其视为查询后的内存排序。 - Spooles
哦,对了,这个应该应用于一个覆盖两个签名的ObjectQuery。我的错误。 - Spooles

5
遇到了与原帖相似的问题,其中“Order By”表达式被写成Expression<Func<T, object>>类型的lambda表达式。这些表达式在NHibernate linq提供程序中被正确解释,但迁移到EF 5后出现“无法将类型'System.DateTime'强制转换为类型'System.IComparable'。LINQ to Entities仅支持对实体数据模型基元类型进行转换。”
以下方法在调用各种“OrderBy”方法时提供了一种转换为Expression<Func<T, TKey>>的方式(使用反射 - 抱歉...)。请注意,它们最初包含在一个通用类OrderBy<T>中。
    private static readonly Type QueryableType = typeof(Queryable);

    // HACK: Use reflection to call strongly-typed methods instead of object-based methods
    // This is to work around "Unable to cast the type 'System.DateTime' to type 'System.Object'. LINQ to Entities only supports casting Entity Data Model primitive types."
    private IOrderedQueryable<T> ApplyOrderByTo(
        IQueryable<T> query,
        Expression<Func<T, object>> keySelector,
        bool sortAscending,
        bool useReflection)
    {
        if (useReflection)
        {
            var body = keySelector.Body as UnaryExpression;
            var keyExpr = body.Operand as MemberExpression;

            return (IOrderedQueryable<T>)query.Provider.CreateQuery(
                Expression.Call(
                QueryableType,
                sortAscending ? "OrderBy" : "OrderByDescending",
                new Type[] { typeof(T), keyExpr.Type },
                query.Expression,
                Expression.Lambda(keyExpr, keySelector.Parameters)));
        }
        else
        {
            if (sortAscending)
                return query.OrderBy(keySelector);
            else
                return query.OrderByDescending(keySelector);
        }
    }

    // HACK: Use reflection to call strongly-typed methods instead of object-based methods
    // This is to work around "Unable to cast the type 'System.DateTime' to type 'System.Object'. LINQ to Entities only supports casting Entity Data Model primitive types."
    private IOrderedQueryable<T> ApplyOrderByTo(
        IOrderedQueryable<T> query,
        Expression<Func<T, object>> keySelector,
        bool sortAscending,
        bool useReflection)
    {
        if (useReflection)
        {
            var body = keySelector.Body as UnaryExpression;
            var keyExpr = body.Operand as MemberExpression;

            return (IOrderedQueryable<T>)query.Provider.CreateQuery(
                Expression.Call(
                QueryableType,
                sortAscending ? "ThenBy" : "ThenByDescending",
                new Type[] { typeof(T), keyExpr.Type },
                query.Expression,
                Expression.Lambda(keyExpr, keySelector.Parameters)));
        }
        else
        {
            if (sortAscending)
                return query.ThenBy(keySelector);
            else
                return query.ThenByDescending(keySelector);
        }
    }

4

我找到了一个非常简单的解决方案,可以解决你的问题(也是我的问题)。当您创建搜索表达式时,应传递属性类型(您知道它),但将表达式存储在动态变量中:

Expression<Func<TObject, DateTime>> Expr = obj=>obj.StartDate;
dynamic dynExpr=Expr;

现在您可以将 dynExpr 与 int 表达式、字符串表达式等一起存储在表格或任何其他地方,当需要使用它时,您可以在 OrderBy 方法中使用它。但不是以标准方式(扩展方法):

query=query.OrderBy(dynExpr);

只有这种方式:

query=Queryable.OrderBy(query, dynExpr);

通过这种方式,您可以在所有排序函数(OrderBy、OrderByDescending、ThenBy、ThenByDescending)中使用一个表达式。


这完美地解决了我的问题,因为我想使用许多不同的数据类型来动态构建搜索谓词和列排序。 - sovemp

1

受OldNic启发,我创建了一些按成员排序的扩展方法。这对我非常有用。我还使用System.Data.SqlClient.SortOrder枚举来定义排序顺序。

        /// <summary>
    ///     Supports sorting of a given member as an expression when type is not known. It solves problem with LINQ to Entities unable to
    ///     cast different types as 'System.DateTime', 'System.DateTime?' to type 'System.Object'.
    ///     LINQ to Entities only supports casting Entity Data Model primitive types.
    /// </summary>
    /// <typeparam name="T">entity type</typeparam>
    /// <param name="query">query to apply sorting on.</param>
    /// <param name="expression">the member expression to apply</param>
    /// <param name="sortOrder">the sort order to apply</param>
    /// <returns>Query with sorting applied as IOrderedQueryable of type T</returns>
    public static IOrderedQueryable<T> OrderByMember<T>(
        this IQueryable<T> query, 
        Expression<Func<T, object>> expression, 
        SortOrder sortOrder)
    {
        var body = expression.Body as UnaryExpression;

        if (body != null)
        {
            var memberExpression = body.Operand as MemberExpression;

            if (memberExpression != null)
            {
                return
                    (IOrderedQueryable<T>)
                    query.Provider.CreateQuery(
                        Expression.Call(
                            typeof(Queryable), 
                            sortOrder == SortOrder.Ascending ? "OrderBy" : "OrderByDescending",
                            new[] { typeof(T), memberExpression.Type }, 
                            query.Expression,
                            Expression.Lambda(memberExpression, expression.Parameters)));
            }
        }

        return sortOrder == SortOrder.Ascending ? query.OrderBy(expression) : query.OrderByDescending(expression);
    }

    /// <summary>
    ///     Supports sorting of a given member as an expression when type is not known. It solves problem with LINQ to Entities unable to
    ///     cast different types as 'System.DateTime', 'System.DateTime?' to type 'System.Object'.
    ///     LINQ to Entities only supports casting Entity Data Model primitive types.
    /// </summary>
    /// <typeparam name="T">entity type</typeparam>
    /// <param name="query">query to apply sorting on.</param>
    /// <param name="expression">the member expression to apply</param>
    /// <param name="sortOrder">the sort order to apply</param>
    /// <returns>Query with sorting applied as IOrderedQueryable of type T</returns>
    public static IOrderedQueryable<T> ThenByMember<T>(
        this IQueryable<T> query, 
        Expression<Func<T, object>> expression, 
        SortOrder sortOrder)
    {
        return ((IOrderedQueryable<T>)query).ThenByMember(expression, sortOrder);
    }

    /// <summary>
    ///     Supports sorting of a given member as an expression when type is not known. It solves problem with LINQ to Entities unable to
    ///     cast different types as 'System.DateTime', 'System.DateTime?' to type 'System.Object'.
    ///     LINQ to Entities only supports casting Entity Data Model primitive types.
    /// </summary>
    /// <typeparam name="T">entity type</typeparam>
    /// <param name="query">query to apply sorting on.</param>
    /// <param name="expression">the member expression to apply</param>
    /// <param name="sortOrder">the sort order to apply</param>
    /// <returns>Query with sorting applied as IOrderedQueryable of type T</returns>
    public static IOrderedQueryable<T> ThenByMember<T>(
        this IOrderedQueryable<T> query, 
        Expression<Func<T, object>> expression, 
        SortOrder sortOrder)
    {
        var body = expression.Body as UnaryExpression;

        if (body != null)
        {
            var memberExpression = body.Operand as MemberExpression;

            if (memberExpression != null)
            {
                return
                    (IOrderedQueryable<T>)
                    query.Provider.CreateQuery(
                        Expression.Call(
                            typeof(Queryable), 
                            sortOrder == SortOrder.Ascending ? "ThenBy" : "ThenByDescending",
                            new[] { typeof(T), memberExpression.Type }, 
                            query.Expression,
                            Expression.Lambda(memberExpression, expression.Parameters)));
            }
        }

        return sortOrder == SortOrder.Ascending ? query.ThenBy(expression) : query.ThenByDescending(expression);
    }

谢谢,这是唯一完美运作的解决方案! - AliReza Sabouri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接