2009-07-17 18 views
23

Ich schrieb eine Methode, um einen Ausdruck für die Orderby-Klausel übergeben zu können, aber ich lief auf dieses Problem.Entity Framework: LINQ to Entities unterstützt nur Casting Entity Data Model primitive Typen

nicht Art 'System.DateTime' gießen 'System.IComparable' einzugeben. LINQ to Entities unterstützt nur Casting Entity Data Modell primitive Typen.

Grundsätzlich ist der Ausdruck, ist dies:

Expression<Func<K, IComparable>> orderBy 

Und wie diese genutzt werden:

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

Die Idee ist so, dass ich ein Wörterbuch verwenden kann, wie Zeichenfolge entspricht, um Ausdrücke zu halten:

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

Dann habe ich eine Methode, die dauert in der Sortierzeichenfolge und gibt den Ausdruck zurück, wenn er mit einem Schlüssel im Wörterbuch übereinstimmt, wenn nicht ein Standardwert zurückgegeben wird. (Die Idee ist eine Möglichkeit, zu kontrollieren, was es zu ordnen ist) Nun funktioniert dies für String-Eigenschaften, aber bisher nicht für Datetime oder Integer, da ich die obige Fehlermeldung bekomme.

Soweit ich (lose) verstehe das Problem ist, dass Entity Framework es ein primärer/EDM-Typ sein muss, weil es die C# DateTime in etwas konvertieren muss, die die Datenbank verarbeiten kann.

Gibt es eine Möglichkeit, die Datetime in einen primitiven Typ zu konvertieren, so dass dies noch funktioniert?

Lösung

Verfahren für den Auftrag durch Verfahren erhalten: (Nehmen Sie in einer Abfrage und senden Sie es in „geordneter Form“)

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

Die Methodensignatur für die generische Abfrage-Methode:

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

Und die Verwendung der in Methode übergeben:

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

var orderedQuery = orderBy(initialQuery); 

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

Antwort

12

Das Entity Framework macht dies schwierig und ich bin mir nicht sicher, ob es eine Möglichkeit gibt, das zu tun, was Sie mit einem einzelnen Rückgabewerttyp machen möchten (IComparable, Objekt, etc). Sie sollten erwägen Ihr Design in einem Wörterbuch des Namens-to Func<IQueryable<K>, IOrderedQueryable<K>> Werte Nacharbeiten:

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

Und dann ist es wie so anwenden:

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

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

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

Ich würde vorschlagen, ein IOrderedQueryable als Ergebnis der Func, dies wird zur Kompilierzeit eine OrderBy wird verwendet, weil zum Beispiel Paging nicht geordnete Abfragen nicht mögen. –

+0

Davy, Einverstanden. Ich habe den Code geändert, um dies zu berücksichtigen. –

+0

Ich brauchte ein paar Minuten, um herauszufinden, wie man das macht, aber am Ende ist es sehr praktikabel. –

21

Ich weiß, das ist alt, aber ich war auf der Suche zu erreichen, das gleiche wie das OP und wollte nicht die Func<IQueryable<T>, IOrderedQueryable<T>> in meinem Wörterbuch verwenden. Meistens weil ich sowohl einen OrderBy als auch einen OrderByDescending Delegierten implementieren müsste.

Ich habe am Ende erstellt eine Erweiterungsmethode für IQueryable ObjectSort, die einfach überprüfen, was der Rückgabetyp des Ausdrucks sein sollte und dann erstellen Sie ein neues Lambda mit diesem Typ, so dass LINQ to Entities nicht ausflippen.

Ich bin mir nicht sicher, ob dies eine gute Lösung ist oder nicht, aber das folgende Beispiel funktioniert für DateTime und int so hoffentlich kann es Ihnen ein paar Ideen, wenn Sie schauen ähnlich, etwas zu erreichen!

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

Danke für diese großartige Erweiterung! Dennoch sortiert es String-Spalten nicht korrekt. Ich habe das behoben, indem ich die letzte Zeile geändert habe in: 'return order == SortOrder.Ascending?entities.OrderBy (Ausdruck): entities.OrderByDescending (Ausdruck); ' – SoftwareFactor

4

ein ähnliches Problem wie das ursprüngliche Plakat auf, wo "Order By" Ausdrücke, wo als Lambda-Ausdrücke vom Typ Expression < Func < T, Objekt > > geschrieben. Diese wurden vom NHibernate-Linq-Provider korrekt interpretiert, die Migration nach EF 5 führte jedoch dazu, dass der Typ System.DateTime nicht in den Typ System.IComparable umgewandelt werden konnte. LINQ to Entities unterstützt nur die Konvertierung primitiver Entity Data Model-Typen.

Die folgenden Methoden bieten eine Umstellung auf Expression < Func < T, TKey > > wenn die verschiedenen „OrderBy“ Methoden aufrufen (mit Reflexion - Entschuldigungen ...) Hinweis sie ursprünglich in einer allgemeinen Klasse gekapselt wurden SortiertNach <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

Mit Inspiration von OldNic habe ich ein paar Erweiterungsmethoden für die Sortierung nach Mitgliedern erstellt. Es funktioniert großartig für mich. Ich verwende auch System.Data.SqlClient.SortOrder enum, um die Sortierreihenfolge zu definieren.

 /// <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

Ich fand eine sehr einfache Lösung für Ihr Problem (und meine auch). Wenn Sie Ihren Suchausdruck erstellen, sollten Sie Art von Eigentum übergehen (Sie wissen es dann) aber speichern Ausdruck in dynamische Variable:

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

Jetzt können Sie dynExpr speichern in einer Tabelle oder wo immer Sie wollen, zusammen mit int Ausdrücke, Zeichenfolgenausdrücke, ... und wenn die Zeit gekommen ist, können Sie sie in der OrderBy-Methode verwenden. Aber nicht in Standard-Weg (Extension-Methode):

query=query.OrderBy(dynExpr); 

, nur auf diese Weise:

query=Queryable.OrderBy(query, dynExpr); 

Auf diese Weise können Sie einen Ausdruck in allen Sortierfunktionen verwenden können (SortiertNach, OrderByDescending, ThenBy, ThenByDescending) .

+0

Dies löste genau mein Problem, da ich Suchprädikate und Spaltenreihenfolgen mit vielen verschiedenen Datentypen dynamisch erstellen möchte. – sovemp

Verwandte Themen