related query multiple framework data columns all c# sql-server entity-framework linq linq-to-entities

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