query - select entity framework c#
Consulta dinĂ¡mica con condiciones OR en Entity Framework (2)
Probablemente estés buscando algo como Predicate Builder, que te permite controlar los AND y OR de la declaración where más fácil.
También hay un Linq dinámico que le permite enviar la cláusula WHERE como una cadena SQL y la analizará en el predicado correcto para un WHERE.
Estoy creando una aplicación de creación que busca en la base de datos y le permite al usuario agregar dinámicamente cualquier criterio (alrededor de 50 posibles), al igual que la siguiente pregunta de SO: Crear consultas dinámicas con el marco de la entidad . Actualmente estoy trabajando en una búsqueda que verifica cada criterio, y si no está en blanco, lo agrega a la consulta.
DO#
var query = Db.Names.AsQueryable();
if (!string.IsNullOrWhiteSpace(first))
query = query.Where(q => q.first.Contains(first));
if (!string.IsNullOrWhiteSpace(last))
query = query.Where(q => q.last.Contains(last));
//.. around 50 additional criteria
return query.ToList();
Este código produce algo similar a lo siguiente en el servidor SQL (lo simplifiqué para una mejor comprensión)
SQL
SELECT
[Id],
[FirstName],
[LastName],
...etc
FROM [dbo].[Names]
WHERE [FirstName] LIKE ''%first%''
AND [LastName] LIKE ''%last%''
Ahora estoy tratando de agregar una forma de generar el siguiente SQL con C # a través del marco de la entidad pero con un OR en lugar de un AND , a la vez que mantengo la capacidad de agregar criterios de forma dinámica.
SQL
SELECT
[Id],
[FirstName],
[LastName],
...etc
FROM [dbo].[Names]
WHERE [FirstName] LIKE ''%first%''
OR [LastName] LIKE ''%last%'' <-- NOTICE THE "OR"
Normalmente, los criterios no serán mayores que dos o tres elementos para una consulta, pero combinarlos en una consulta gigantesca no es una opción. He intentado concat, union e intersect y todos duplican la consulta y se unen a ellos con UNION.
¿Existe una forma sencilla y limpia de agregar condiciones "O" a una consulta generada dinámicamente utilizando el marco de la entidad?
Editar con mi solución - 29/09/2015
Desde que publiqué esto, noté que esto ha recibido un poco de atención, así que decidí publicar mi solución.
// Make sure to add required nuget
// PM> Install-Package LinqKit
var searchCriteria = new
{
FirstName = "sha",
LastName = "hill",
Address = string.Empty,
Dob = (DateTime?)new DateTime(1970, 1, 1),
MaritalStatus = "S",
HireDate = (DateTime?)null,
LoginId = string.Empty,
};
var predicate = PredicateBuilder.False<Person>();
if (!string.IsNullOrWhiteSpace(searchCriteria.FirstName))
{
predicate = predicate.Or(p => p.FirstName.Contains(searchCriteria.FirstName));
}
if (!string.IsNullOrWhiteSpace(searchCriteria.LastName))
{
predicate = predicate.Or(p => p.LastName.Contains(searchCriteria.LastName));
}
// Quite a few more conditions...
foreach(var person in this.Persons.Where(predicate.Compile()))
{
Console.WriteLine("First: {0} Last: {1}", person.FirstName, person.LastName);
}
Si bien LINQKit y su PredicateBuilder son bastante versátiles, es posible hacerlo más directamente con unas pocas utilidades simples (cada una de las cuales puede servir de base para otras operaciones de manipulación de expresiones):
Primero, un Reemplazo de Expresión de propósito general:
public class ExpressionReplacer : ExpressionVisitor
{
private readonly Func<Expression, Expression> replacer;
public ExpressionReplacer(Func<Expression, Expression> replacer)
{
this.replacer = replacer;
}
public override Expression Visit(Expression node)
{
return base.Visit(replacer(node));
}
}
A continuación, un método de utilidad simple para reemplazar el uso de un parámetro con otro parámetro en una expresión dada:
public static T ReplaceParameter<T>(T expr, ParameterExpression toReplace, ParameterExpression replacement)
where T : Expression
{
var replacer = new ExpressionReplacer(e => e == toReplace ? replacement : e);
return (T)replacer.Visit(expr);
}
Esto es necesario porque los parámetros lambda en dos expresiones diferentes son en realidad parámetros diferentes, incluso cuando tienen el mismo nombre. Por ejemplo, si desea terminar con q => q.first.Contains(first) || q.last.Contains(last)
q => q.first.Contains(first) || q.last.Contains(last)
, luego el q
en q.last.Contains(last)
debe ser exactamente el mismo q
que se proporciona al comienzo de la expresión lambda.
A continuación, necesitamos un método de Func<T, TReturn>
de propósito general que sea capaz de unir las expresiones Lambda de estilo Func<T, TReturn>
con un generador de expresiones binarias dado.
public static Expression<Func<T, TReturn>> Join<T, TReturn>(Func<Expression, Expression, BinaryExpression> joiner, IReadOnlyCollection<Expression<Func<T, TReturn>>> expressions)
{
if (!expressions.Any())
{
throw new ArgumentException("No expressions were provided");
}
var firstExpression = expressions.First();
var otherExpressions = expressions.Skip(1);
var firstParameter = firstExpression.Parameters.Single();
var otherExpressionsWithParameterReplaced = otherExpressions.Select(e => ReplaceParameter(e.Body, e.Parameters.Single(), firstParameter));
var bodies = new[] { firstExpression.Body }.Concat(otherExpressionsWithParameterReplaced);
var joinedBodies = bodies.Aggregate(joiner);
return Expression.Lambda<Func<T, TReturn>>(joinedBodies, firstParameter);
}
Usaremos esto con Expression.Or
, pero podría usar el mismo método para una variedad de propósitos, como combinar expresiones numéricas con Expression.Add
.
Finalmente, juntándolo todo, puedes tener algo como esto:
var searchCriteria = new List<Expression<Func<Name, bool>>();
if (!string.IsNullOrWhiteSpace(first))
searchCriteria.Add(q => q.first.Contains(first));
if (!string.IsNullOrWhiteSpace(last))
searchCriteria.Add(q => q.last.Contains(last));
//.. around 50 additional criteria
var query = Db.Names.AsQueryable();
if(searchCriteria.Any())
{
var joinedSearchCriteria = Join(Expression.Or, searchCriteria);
query = query.Where(joinedSearchCriteria);
}
return query.ToList();