È necessario creare un albero di espressioni per il valore di data massima

.net c# entity-framework expression-trees linq

Domanda

Sto cercando di costruire un albero di espressioni per questa query di linq: così posso passare in un'entità generica:

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

Sto volendo creare una classe che prende un'entità generica e trova il massimo della sua proprietà TimeStamp.

Stavo provando qualcosa come sotto, ma si lamenta:

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();

Quando compilo, ottengo il seguente errore sull'ultima riga di codice:

La risoluzione del sovraccarico non è riuscita perché non è possibile chiamare nessun 'Seleziona' accessibile con questi argomenti:

Che cosa sto facendo di sbagliato?

Risposta accettata

(Come da commenti ...)

Il problema è che si sta tentando di utilizzare una combinazione di LINQ to Objects (che utilizza IEnumerable<T> e delegati) e LINQ basato su Queryable (che utilizza IQueryable<T> e alberi di espressione). Non è possibile passare Enumerable<T> un albero di espressioni.

Tre opzioni:

  • Converti la raccolta in un IQueryable<T> prima:

    DateTime maxdate = this.EntityCollection.AsQueryable().Select(lambda).Max();
    
  • Convertire prima l'albero dell'espressione in un delegato:

    DateTime maxdate = this.EntityCollection.Select(lambda.Compile()).Max();
    
  • Cambia il tuo metodo per accettare un IQueryable<T> invece di un IEnumerable<T>


Risposta popolare

Personalmente, preferisco il sovraccarico di Expression.Property che prende un'istanza di PropertyInfo .

In questo modo, potresti fare questo:

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();

È solo molto più pulito.

È possibile che la chiamata a Type.GetProperty non restituisca nulla e che ti stia dando l'errore. Ricordare che il nome della proprietà passato come parametro deve essere pubblico, altrimenti è necessario utilizzare il sovraccarico di GetProperty che consente di specificare valori GetProperty BindingFlags per indicare che si desidera includere proprietà non pubbliche.

Tuttavia, penso che ci sia un'alternativa migliore. Dovresti definire un'interfaccia in questo modo:

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

Ciò ti consente di definire il tuo metodo di estensione in questo modo:

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

Nota: DateTime? viene utilizzato al posto di DateTime nel caso in cui si abbia una sequenza vuota. Inoltre, è possibile creare un overload che richiede un IQueryable<T> se si desidera che l'esecuzione si verifichi su un server.

Il vantaggio principale che ottieni qui è il controllo in fase di compilazione di dove le chiamate sono valide. Questo è molto meglio che avere un'eccezione generata in fase di runtime.

Inoltre, non sarebbe difficile da implementare; si sta utilizzando Entity Framework che crea file di classi parziali ; per questo motivo, è facile aggiungere un altro file di classe parziale per ogni tipo che ha questo:

public partial class MyEntity : IHaveTimeStamp
{ }

Il tuo codice originale indica che hai già la proprietà TimeStamp su ciascuna delle entità su cui vuoi utilizzare questo metodo di estensione, per questo motivo, non devi fare nulla per implementare l'interfaccia, è già implicitamente implementata per te perché la proprietà TimeStamp dovrebbe essere pubblica.

Se non è pubblico, puoi cambiare la tua definizione in modo semplice:

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

Ad ogni modo, si tratta di un semplice lavoro di copia e incolla con qualche ritocco del nome della classe ogni volta.



Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché
Autorizzato sotto: CC-BY-SA with attribution
Non affiliato con Stack Overflow
È legale questo KB? Sì, impara il perché