2009-07-17 18 views
23

Escribí un método para permitir que se pasara una expresión para la cláusula orderby, pero me encontré con este problema.Entity Framework: LINQ to Entities only supports casting Entity Data Model tipos primitivos

No se puede convertir el tipo 'System.DateTime' para escribir 'System.IComparable'. LINQ a Entidades solo admite el fundido de Datos de entidad Tipos primitivos de modelo.

Básicamente la expresión es la siguiente:

Expression<Func<K, IComparable>> orderBy 

y se utiliza de esta manera:

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

La idea es para que pueda utilizar un diccionario para sostener cadena coincide con expresiones como:

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

Luego tengo un método que toma en la cadena de ordenamiento y devuelve la expresión si coincide con una clave en el diccionario, si no devuelve algún valor predeterminado. (La idea es una forma de controlar por lo que se puede ordenar) Ahora esto funciona para las propiedades de cadena, pero hasta ahora no para fecha o entero ya que recibo el mensaje de error anterior.

Ahora que yo (vagamente) entiendo el problema es que Entity Framework necesita que sea un tipo Principal/EDM porque tiene que convertir el C# DateTime en algo que la base de datos puede manejar.

¿Hay alguna manera de convertir el datetime a un tipo primitivo para que esto funcione?

Solución

El método para conseguir la orden por el método: (Tome en una consulta y devolverlo en "forma ordenada")

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; 
} 

La firma del método para el método de consulta genérica:

IList<K> GetListForGrid<T, K>(this ObjectQuery<T> query, ... Func<IQueryable<K>, IOrderedQueryable<K>> orderBy, ...) 

Y el uso del pasado en el método:

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

var orderedQuery = orderBy(initialQuery); 

returnValue = orderedQuery 
    .Skip(numberToShow * realPage) 
    .Take(numberToShow) 
    .ToList(); 

Respuesta

12

Entity Framework lo hace difícil y no estoy seguro de que haya una manera de hacer lo que quiera hacer con un único tipo de valor devuelto (IComparable, objeto, etc.). Usted podría considerar volver a trabajar en el diseño de un diccionario de Func<IQueryable<K>, IOrderedQueryable<K>> valores de nombre-a-:

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

Y luego aplicarlo así:

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

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

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

Sugeriría un IOrderedQueryable como resultado del Func, esto forzará en tiempo de compilación un OrderBy, porque por ejemplo paginación no le gusta las consultas no ordenadas. –

+0

Davy, de acuerdo. Cambié el código para reflejar esto. –

+0

Me tomó un par de minutos descubrir cómo funciona esto, pero al final es muy factible. –

21

Sé que esto es viejo, pero yo estaba buscando para llevar a cabo exactamente lo mismo que el OP y no quería usar el Func<IQueryable<T>, IOrderedQueryable<T>> en mi diccionario. Sobre todo porque tendría que implementar un delegado OrderBy y OrderByDescending.

Terminé creando un método de extensión para IQueryable llamado ObjectSort que simplemente verificará cuál debe ser el tipo de devolución de la expresión y luego creará una nueva lambda utilizando ese tipo para que LINQ to Entities no se vuelva loco.

no estoy seguro si esto es una buena solución o no, pero el siguiente ejemplo funciona para DateTime y int así que espero que se le puede dar algunas ideas si usted está buscando para lograr algo similar!

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); 
} 
+3

¡Gracias por esta gran extensión! Aún así, no ordena las columnas de cadena correctamente. Lo arreglé cambiando la última línea a: 'orden de devolución == SortOrder.Ascending?entities.OrderBy (expresión): entities.OrderByDescending (expresión); ' – SoftwareFactor

4

encontramos con un problema similar como el cartel original, donde "ordenar por" expresiones donde escriben como lambdas de la expresión de tipo < Func < T, objeto > >. Estos fueron interpretados correctamente por el proveedor NHibernate linq, pero la migración a EF 5 dio como resultado "No se pudo lanzar el tipo 'System.DateTime' para escribir 'System.IComparable'. LINQ to Entities solo admite la fundición de tipos primitivos de Entity Data Model."

Los siguientes métodos proporcionan una conversión de Expresión < Func < T, TKey > > al llamar a los diversos métodos "OrdenarPor" (usando la reflexión - disculpas ...) Nota que fueron encapsulados originalmente en una clase genérica OrdenarPor <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); 
     } 
    } 
0

Inspirado en OldNic, creé un par de métodos de extensión para clasificar por miembros. Funciona muy bien para mí. También estoy usando la enumeración System.Data.SqlClient.SortOrder para definir el orden de clasificación.

 /// <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); 
    } 
2

Encontré una solución muy simple a su problema (y el mío también). Cuando se crea la expresión de búsqueda, usted debe pasar por tipo de propiedad (ya se sabe que a continuación), pero la expresión tienda en variable dinámica:

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

Ahora puede almacenar dynExpr en una tabla o en cualquier lugar que desee, junto con int expresiones, expresiones de cadena, ... y cuando llegue el momento puedes usarlo en el método OrderBy. Pero no en la forma estándar (método de extensión):

query=query.OrderBy(dynExpr); 

, solamente de esta manera:

query=Queryable.OrderBy(query, dynExpr); 

De esta manera puede utilizar una expresión en todas las funciones de clasificación (OrdenarPor, OrderByDescending, ThenBy, ThenByDescending) .

+0

Esto resolvió exactamente mi problema, ya que quiero construir dinámicamente predicados de búsqueda y ordenamientos de columna con muchos tipos de datos diferentes. – sovemp

Cuestiones relacionadas