Sie müssen den Ausdrucksbaum für den maximalen Datumswert erstellen

.net c# entity-framework expression-trees linq

Frage

Ich versuche, eine Ausdrucksbaumstruktur für diese Linq-Abfrage zu erstellen: so kann ich eine generische Entität übergeben:

this.EntityCollection.Select((ent) => ent.TimeStamp).Max()

Ich möchte eine Klasse erstellen, die eine generische Entität verwendet und das Maximum ihrer TimeStamp-Eigenschaft findet.

Ich habe etwas wie unten versucht, aber es beschwert sich:

ParameterExpression param = Expression.Parameter(typeof(TE), "ent");

MemberExpression prop = Expression.
    Property(param, typeof(TE).GetProperty("TimeStamp").GetGetMethod());

Expression<Func<TE, DateTime>> lambda = Expression.Lambda<Func<TE, DateTime>>(
    prop, new ParameterExpression[] { param });

DateTime maxdate = this.EntityCollection.Select(lambda).Max();

Wenn ich kompiliere, erhalte ich den folgenden Fehler in der letzten Codezeile:

Die Überladungsauflösung ist fehlgeschlagen, weil mit diesen Argumenten kein zugreifbares 'Select' aufgerufen werden kann:

Was mache ich falsch?

Akzeptierte Antwort

(Laut Kommentare ...)

Das Problem besteht darin, dass Sie versuchen, eine Mischung aus LINQ to Objects (die IEnumerable<T> und Delegaten verwendet) und Queryable-based LINQ (die IQueryable<T> und Ausdrucksbäume verwendet) zu verwenden. Sie können Enumerable<T> eine Ausdrucksbaumstruktur nicht übergeben.

Drei Optionen:

  • Konvertieren Sie die Sammlung zuerst in ein IQueryable<T> :

    DateTime maxdate = this.EntityCollection.AsQueryable().Select(lambda).Max();
    
  • Wandeln Sie die Ausdrucksbaumstruktur zuerst in einen Delegaten um:

    DateTime maxdate = this.EntityCollection.Select(lambda.Compile()).Max();
    
  • Ändern Sie Ihre Methode, um ein IQueryable<T> anstelle eines IEnumerable<T> zu akzeptieren


Beliebte Antwort

Persönlich bevorzuge ich die Überladung von Expression.Property die eine PropertyInfo Instanz übernimmt .

Wenn Sie das tun, können Sie Folgendes tun:

ParameterExpression param = Expression.Parameter(typeof(TE), "ent");
MemberExpression prop = Expression.
    Property(param, typeof(TE).GetProperty("TimeStamp"));
Expression<Func<TE, DateTime>> lambda = Expression.Lambda<Func<TE, DateTime>>(
    prop, new ParameterExpression[] { param });
DateTime maxdate = this.EntityCollection.Select(lambda).Max();

Es ist einfach viel sauberer.

Es ist möglich, dass der Aufruf von Type.GetProperty nichts Type.GetProperty , und das gibt Ihnen den Fehler. Denken Sie daran, der Eigenschaftsname als Parameter übergeben hat öffentlich sein, andernfalls müssen Sie die verwenden Überlastung von GetProperty , die Sie Werte aus dem angeben können Binding Aufzählung um anzuzeigen , dass Sie nicht öffentliche Eigenschaften enthalten sein.

Ich denke jedoch, dass es eine bessere Alternative gibt. Sie sollten eine Schnittstelle wie folgt definieren:

public interface IHaveTimestamp
{
    DateTime TimeStamp { get; set; }
}

Damit können Sie Ihre Erweiterungsmethode wie folgt definieren:

public static DateTime? MaxTimeStamp(IEnumerable<T> entities) 
    where T : IHaveTimeStamp
{
    // Return the max.
    return entities.Select(e => (DateTime?) e.TimeStamp).Max();
}

Hinweis: DateTime? DateTime anstelle von DateTime wenn Sie eine leere Sequenz haben. Sie können auch eine Überladung erstellen, die ein IQueryable<T> wenn die Ausführung auf einem Server erfolgen soll.

Der Hauptvorteil, den Sie hier haben, besteht darin, dass Sie die Kompilierungszeit überprüfen, wo die Aufrufe gültig sind. Dies ist viel besser, als wenn zur Laufzeit eine Ausnahme ausgelöst wird.

Es wäre auch nicht schwierig zu implementieren; Sie verwenden Entity Framework, das partielle Klassendateien erstellt ; Aus diesem Grund ist es einfach, eine weitere partielle Klassendatei für jeden Typ hinzuzufügen, der über Folgendes verfügt:

public partial class MyEntity : IHaveTimeStamp
{ }

Ihr ursprünglicher Code gibt an, dass Sie die TimeStamp Eigenschaft bereits auf allen Entitäten haben, für die Sie diese Erweiterungsmethode verwenden möchten. Aus diesem Grund müssen Sie nichts für die Implementierung der Schnittstelle tun, da dies bereits implizit für Sie implementiert wurde Die TimeStamp Eigenschaft sollte öffentlich sein.

Wenn es nicht öffentlich ist, können Sie Ihre Definition so ändern, dass sie einfach so lautet:

public partial class MyEntity : IHaveTimeStamp
{ 
    IHaveTimeStamp.TimeStamp
    { 
        get { return this.TimeStamp; } 
        set { this.TimeStamp = value; } 
    }
}

Wie auch immer, es ist ein einfacher Kopier- und Einfügejob, bei dem jedes Mal der Klassenname geändert wird.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow