.NET

ExpressionVisitor para consultas dinámicas en Entity framework

En nuestros desarrollos, a menudo necesitamos construir expresiones LINQ de forma dinámica. Puede ser que, por ejemplo, una de nuestras aplicaciones web tenga un sistema de búsqueda complejo o que necesitemos aplicar filtros dinámicos a un conjunto de datos usando Entity Framework.

Este dinamismo en nuestras consultas se puede conseguir de diversas maneras, pero una de las más elegantes es utilizar ExpressionVisitor.

También existe la posibilidad de descargar el NuGET de LinqKit o el PredicateBuilder de BinBin.Linq, pero añadir librería de terceros no es siempre una opción. Además, aplicando nuestra propia implementación, podemos tener un control total sobre todo el código que hay en nuestros sistemas.

¿Qué es ExpressionVisitor?

ExpressionVisitor es una clase introducida en la versión 4.0 de .NET Framework que nos permite aplicar el patrón visitor a nuestras expresiones LINQ. Esto nos permite dinamizar mucho nuestras consultas a base de datos utilizando, por ejemplo, EntityFramework.

El patrón visitor, explicado de forma muy simple, no es más que una forma de separar la lógica de nuestros algoritmos de la estructura de datos sobre la que se aplican. En nuestro caso, la estructura de datos es el árbol de expresiones y los algoritmos, por ejemplo, serán los métodos que utilicemos para modificar dichas expresiones.

No vamos a entrar en más detalle sobre el patrón, ya que sería necesario un post completo sólo para ello. Además, hay muchas y muy buenas explicaciones del mismo por las redes (esta, por ejemplo).

 

Ejemplo

Somos los responsables de crear una aplicación para gestionar los pagos que recibe una empresa y el cliente nos pide una pantalla en la que se muestren los pagos que han realizado una serie de personas, que identificaremos con el NIF, en fechas concretas. Tenemos una clase Payment como la siguiente:

public class Payment
{
    public string Nif { get; set;}
    public DateTime PaymentDate { get; set;}
    public decimal Amount { get; set; }
}

Además, disponemos de un diccionario que nos proporciona la relación persona-fecha de pago que nos interesa. Este diccionario no sería fijo, sino que vendría de algún servicio externo y podría tener cientos de entradas.

var nifWithPaymentDate = new Dictionary<string, DateTime>
{
    ["00000000T"] = new DateTime(2001, 10, 6),
    ["99999999R"] = new DateTime(2012, 4, 2)
}

¿Cómo construimos dinámicamente una expresión LINQ que nos permita obtener los datos que queremos sin hacer n consultas? Veamos el ExpressionVisitor.

// Heredamos de ExpressionVisitor
public class ReplaceExpressionVisitor : ExpressionVisitor
{

    private readonly Expression oldValue;
    private readonly Expression newValue;

    public MyExpressionVisitor(Expression oldValue, Expression newValue)
    {
        this.oldValue = oldValue;
        this.newValue = newValue;
    }

    // Implementación del método Visit
    public override Expression Visit(Expression node)
    {
        // Si la expresión a visitar es igual a la antigua reemplazamos
        return node == this.oldValue ? this.newValue : base.Visit(node);
    }
}

Como se puede apreciar, simplemente tenemos que heredar de ExpressionVisitor para poder utilizar lo que nos aporta. Esta implementación simplemente reemplazará la expresión pasada como oldValue por la que se pase como newValue.

Por otro lado, para construir nuestra expresión dinámicamente, también necesitaremos un método de extensión para las consultas con LINQ a EntityFramework para llamar a nuestro ReplaceExpressionVisitor.

public static Expression<Func<T, bool>> Or<T>(
    this Expression<Func<T, bool>> expr1,
    Expression<Func<T, bool>> expr2)
{

    // Obtenemos el tipo de parámetro T de nuestras expresiones
    var parameter = Expression.Parameter(typeof(T));

    // Instanciamos un visitor para expr1
    var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);

    // Visitamos la expresión
    var left = leftVisitor.Visit(expr1.Body);

    // Instanciamos un visitor para expr1
    var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);

    // Visitamos la expresión
    var right = rightVisitor.Visit(expr2.Body);

    // Devolvemos la lambda resultado
    return Expression.Lambda<Func<T, bool>>(
        Expression.OrElse(left, right), parameter);

Este método tiene algo más de miga. Como vemos, visitamos ambas expresiones para reemplazar el parámetro origen T por los parámetros suministrados por cada una de las expresiones. Con esto listo, construimos una nueva lambda combinando las expresiones visitadas con OrElse y la devolvemos.

Ahora veamos cómo funciona todo esto en conjunto.

IQueryable<Payment> paymentsFromDb = db.Payments;

// Inicializamos a false por defecto
Expression<Func<Payment, bool>> theExpression = p => false;

// Iteramos sobre el diccionario
foreach (var paymentInfo in nifWithPaymentDate)
{
    // Por cada una de las entradas del diccionario
    // Construimos dinámicamente la consulta
    theExpression = theExpression
        .Or(p => p.Nif == paymentInfo.Key && p.PaymentDate == paymentInfo.Value);
}

// Pasamos la expresión resultante a un Where
var result = paymentsFromDb.Where(theExpression.ToList();

Fácil, ¿verdad? Simplemente iteramos sobre el diccionario con los datos externos para construir una expresión a partir de una base. En cada iteracción vamos acumulando condiciones Or con el método de extensión que aplica nuestro ExpressionVisitor. Posteriormente sólo nos queda pasar dicha expresión a Entity Framework para hacer la consulta.

Resumen

Utilizar ExpressionVisitor es una forma genial y elegante de crear expresiones LINQ de forma dinámica. Siguiendo con Entity Framework, podríamos combinarlo con un QueryInterceptor para añadir condiciones fijas a todas nuestras consultas. Esto podría ser útil para borrados lógicos, por ejemplo, ayudándonos a evitar tener que añadir la condición que comprueba dicho borrado en cada expresión.

En conclusión, la capacidad de aplicar el patrón visitor a árboles de expresiones nos ofrece un grado más de dinamismo y versatilidad a la hora de trabajar con LINQ.

Compartir
Publicado por
Juan Carlos Martínez García

Este sitio web utiliza cookies para que tengas la mejor experiencia de usuario. Si continuas navegando, estás dando tu consentimiento para aceptar las cookies y también nuestra política de cookies (esperemos que no te empaches con tanta cookie 😊)