sort parameter orderby c# linq linq-to-sql anonymous

c# - parameter - Construya dinĂ¡micamente la consulta "o" LIKE en LINQ to SQL



orderby linq string (3)

Tengo una consulta LINQ que está compuesta por un objeto anónimo.

En un punto dado, quiero limitar los resultados por los parámetros de búsqueda entrantes, pero este puede ser uno o más parámetros, y quiero realizar un "LIKE x OR LIKE y OR LIKE z" usando esos.

En el código, se vería así:

reservations = reservations.Where(r => r.GuestLastName.Contains(parameter1) || r.GuestFirstName.Contains(parameter1) || r.GuestLastName.Contains(parameter2) || r.GuestFirstName.Contains(parameter2) || // Parameter 3, 4, 5,.. );

¿Cómo podría construir esto dinámicamente, sabiendo que las reservations son del tipo IQueryable<''a> (anonymous object) ? He buscado varios recursos y solo puedo encontrar una manera de hacerlo cuando sé el tipo, no cuando uso tipos anónimos.

Es importante saber que es Linq a SQL, por lo que debe traducirse a una consulta SQL y no debe filtrarse en la memoria ...


Escribiría mi propio método de extensión genérico:

public static class CollectionHelper { public static IQueryable Filter<T>(this IQueryable source, string[] properties, string[] values) { var lambda = CombineLambdas<T>(properties, values); var result = typeof (Queryable).GetMethods().First( method => method.Name == "Where" && method.IsGenericMethodDefinition) .MakeGenericMethod(typeof (T)) .Invoke(null, new object[] {source, lambda}); return (IQueryable<T>) result; } // combine lambda expressions using OR operator private static LambdaExpression CombineLambdas<T>(string[] properties, string[] values) { var param = Expression.Parameter(typeof (T)); LambdaExpression prev = null; foreach (var value in values) { foreach (var property in properties) { LambdaExpression current = GetContainsExpression<T>(property, value); if (prev != null) { Expression body = Expression.Or(Expression.Invoke(prev, param), Expression.Invoke(current, param)); prev = Expression.Lambda(body, param); } prev = prev ?? current; } } return prev; } // construct expression tree to represent String.Contains private static Expression<Func<T, bool>> GetContainsExpression<T>(string propertyName, string propertyValue) { var parameterExp = Expression.Parameter(typeof (T), "type"); var propertyExp = Expression.Property(parameterExp, propertyName); var method = typeof (string).GetMethod("Contains", new[] {typeof (string)}); var someValue = Expression.Constant(propertyValue, typeof (string)); var containsMethodExp = Expression.Call(propertyExp, method, someValue); return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp); } }

y el uso:

var reservations = new List<TheType>() // sample collection { new TheType {FirstName = "aa", LastName = "bb"}, new TheType {FirstName = "cc", LastName = "dd"}, new TheType {FirstName = "ee", LastName = "ff"} }.AsQueryable(); var filtered = reservations .Filter<TheType>(new[] {"FirstName", "LastName"}, new[] {"d", "e"}); /* returnes 2 elements: * {FirstName = "cc", LastName = "dd"} and {FirstName = "ee", LastName = "ff"} */

No conozco una solución general que le gustaría tener, si existe, pero espero que pueda ser una alternativa aceptable que resuelva su caso creando dinámicamente el filtro deseado.


Hay dos formas posibles:

  1. Construyendo una Expression , como lo señala Coincoin
  2. Poniendo todos tus parámetros en una matriz y usando Any :

    var parameters = new [] { parameter1, parameter2, /*...*/ } reservations = reservations .Where(r => parameters.Any(p => r.GuestFirstName.Contains(p) || r.GuestLastName.Contains(p)));


Encontré la solución después de algunas depuraciones, pero creo un WhereFilter con múltiples selectores, uno para FirstName y otro para LastName.

Este es el método de extensión:

public static IQueryable<T> WhereFilter<T>(this IQueryable<T> source, string[] possibleValues, params Expression<Func<T, string>>[] selectors) { List<Expression> expressions = new List<Expression>(); var param = Expression.Parameter(typeof(T), "p"); var bodies = new List<MemberExpression>(); foreach (var s in selectors) { bodies.Add(Expression.Property(param, ((MemberExpression)s.Body).Member.Name)); } foreach (var v in possibleValues) { foreach(var b in bodies) { expressions.Add(Expression.Call(b, "Contains", null, Expression.Constant(v))); } } var finalExpression = expressions.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal)); return source.Where(Expression.Lambda<Func<T, bool>>(finalExpression, param)); }

Se puede usar así:

reservations = reservations.WhereFilter( array_of_allowed_values, r => r.GuestFirstName, r => r.GuestLastName );

Comprobé la cadena de seguimiento de la consulta y realmente se tradujo a SQL, por lo que el filtrado se realiza en la base de datos.