Necesidad de construir el árbol de expresiones para el valor máximo de fecha

.net c# entity-framework expression-trees linq

Pregunta

Estoy tratando de construir un árbol de expresiones para esta consulta linq: así puedo pasar una Entidad genérica:

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

Estoy deseando crear una clase que tome una Entidad genérica y encuentre el máximo de su propiedad TimeStamp.

Estaba intentando algo como abajo, pero se queja:

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

Cuando compilo, obtengo el siguiente error en la última línea de código:

La resolución de sobrecarga falló porque no se puede llamar 'Select' accesible con estos argumentos:

¿Qué estoy haciendo mal?

Respuesta aceptada

(Según comentarios ...)

El problema es que está intentando usar una combinación de LINQ a Objetos (que usa IEnumerable<T> y delegados) y LINQ basado en Queryable (que usa IQueryable<T> y árboles de expresiones). No puedes pasar a Enumerable<T> un árbol de expresiones.

Tres opciones:

  • Convertir la colección a un IQueryable<T> primero:

    DateTime maxdate = this.EntityCollection.AsQueryable().Select(lambda).Max();
    
  • Convertir el árbol de expresiones a un delegado primero:

    DateTime maxdate = this.EntityCollection.Select(lambda.Compile()).Max();
    
  • Cambie su método para aceptar un IQueryable<T> lugar de un IEnumerable<T>


Respuesta popular

Personalmente, prefiero la sobrecarga de Expression.Property que toma una instancia de PropertyInfo .

Haciendo eso, podrías hacer esto:

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 mucho más limpio.

Es posible que la llamada a Type.GetProperty no esté devolviendo nada, y eso le está dando el error. Recuerde, el nombre de la propiedad que se pasa como parámetro debe ser público; de lo contrario, debe usar la sobrecarga de GetProperty que le permite especificar valores de la enumeración BindingFlags para indicar que desea que se incluyan propiedades no públicas.

Sin embargo, creo que hay una mejor alternativa. Deberías definir una interfaz así:

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

Eso te permite luego definir tu método de extensión así:

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

Nota: DateTime? se utiliza en lugar de DateTime en el caso de que tenga una secuencia vacía. Además, puede crear una sobrecarga que tome un IQueryable<T> si desea que se ejecute en un servidor.

El principal beneficio que obtiene aquí es que obtiene la verificación en tiempo de compilación de dónde son válidas las llamadas. Esto es mucho mejor que tener una excepción lanzada en tiempo de ejecución.

Además, no sería difícil de implementar; está utilizando Entity Framework, que crea archivos de clase parciales ; debido a esto, es fácil agregar otro archivo de clase parcial para cada tipo que tiene esto:

public partial class MyEntity : IHaveTimeStamp
{ }

Su código original indica que ya tiene la propiedad TimeStamp en cada una de las entidades en las que desea usar este método de extensión, por lo que no necesita hacer nada para implementar la interfaz, ya está implementado implícitamente para usted porque La propiedad TimeStamp debe ser pública.

Si no es público, entonces puede cambiar su definición para ser fácilmente esto:

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

De cualquier manera, es un simple trabajo de copiar y pegar con algunas modificaciones del nombre de la clase cada vez.



Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow
Licencia bajo: CC-BY-SA with attribution
No afiliado con Stack Overflow