Entity Framework: LINQ to Entities поддерживает только приведение типов примитивов Entity Data Model. - PullRequest
24 голосов
/ 18 июля 2009

Я написал метод, позволяющий передавать Expression для предложения orderby, но столкнулся с этой проблемой.

Невозможно привести тип «System.DateTime» для ввода 'System.IComparable. LINQ to Entities поддерживает только приведение Entity Data Модель примитивных типов.

В основном это выражение:

Expression<Func<K, IComparable>> orderBy

И используется так:

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

Идея в том, чтобы я мог использовать словарь для хранения совпадений строк с такими выражениями как:

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

Тогда у меня есть метод, который принимает строку сортировки и возвращает выражение, если оно соответствует ключу в словаре, если нет, возвращает некоторое значение по умолчанию. (Идея заключается в том, чтобы управлять тем, что можно упорядочить). Теперь это работает для свойств String, но пока не для datetime или integer, как я получаю сообщение об ошибке выше.

Теперь, насколько я (свободно) понимаю, проблема в том, что Entity Framework требует, чтобы он был типом Primary / 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 ]

23 голосов
/ 06 февраля 2012

Я знаю, что это старый, но я пытался выполнить то же самое, что и ОП, и не хотел использовать Func<IQueryable<T>, IOrderedQueryable<T>> в своем словаре. Главным образом потому, что мне пришлось бы реализовать делегат OrderBy и OrderByDescending.

В итоге я создал метод расширения для IQueryable с именем ObjectSort, который просто проверит, чтобы увидеть, каким должен быть возвращаемый тип выражения, а затем создаст новую лямбду, используя этот тип, чтобы LINQ to Entities не волновался. .

Я не уверен, является ли это хорошим решением или нет, но приведенный ниже пример действительно работает для DateTime и int, так что, надеюсь, он может дать вам некоторые идеи, если вы хотите сделать что-то подобное!

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);
}
13 голосов
/ 18 июля 2009

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);
}
4 голосов
/ 13 мая 2013

Обнаружена проблема, аналогичная оригинальному постеру, где выражения "Упорядочить по" записаны в виде лямбда-выражений типа Expression >. Они были правильно интерпретированы поставщиком NHibernate linq, но переход на EF 5 привел к «Невозможно привести тип System.DateTime к типу System.IComparable». LINQ to Entities поддерживает только приведение типов примитивов модели данных сущностей. "

Следующие методы обеспечивают преобразование в Expression > при вызове различных методов "OrderBy" (используя отражение - извинения ...). Обратите внимание, что они изначально были инкапсулированы в универсальный класс OrderBy .

    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);
        }
    }
3 голосов
/ 18 августа 2015

Я нашел очень простое решение вашей проблемы (и моей тоже). Когда вы создаете поисковое выражение, вы должны передать тип свойства (тогда вы его знаете), но сохранить выражение в динамической переменной:

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).

0 голосов
/ 11 ноября 2013

По вдохновению 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);
    }
...