Estoy tratando de crear una consulta que usa una lista de identificadores en la cláusula where, usando la API del cliente Silverlight ADO.Net Data Services (y por lo tanto Linq To Entities). ¿Alguien sabe de alguna solución para que no se admita el contenido?
Quiero hacer algo como esto:
List<long?> txnIds = new List<long?>();
// Fill list
var q = from t in svc.OpenTransaction
where txnIds.Contains(t.OpenTransactionId)
select t;
Intenté esto:
var q = from t in svc.OpenTransaction
where txnIds.Any<long>(tt => tt == t.OpenTransactionId)
select t;
Pero obtuvo "El método 'Cualquiera' no es compatible".
c#
linq
entity-framework
.net-3.5
linq-to-entities
James Bloomer
fuente
fuente
Respuestas:
Actualización: EF ≥ 4 admite
Contains
directamente (CheckoutAny
), por lo que no necesita ninguna solución.public static IQueryable<TEntity> WhereIn<TEntity, TValue> ( this ObjectQuery<TEntity> query, Expression<Func<TEntity, TValue>> selector, IEnumerable<TValue> collection ) { if (selector == null) throw new ArgumentNullException("selector"); if (collection == null) throw new ArgumentNullException("collection"); if (!collection.Any()) return query.Where(t => false); ParameterExpression p = selector.Parameters.Single(); IEnumerable<Expression> equals = collection.Select(value => (Expression)Expression.Equal(selector.Body, Expression.Constant(value, typeof(TValue)))); Expression body = equals.Aggregate((accumulate, equal) => Expression.Or(accumulate, equal)); return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p)); } //Optional - to allow static collection: public static IQueryable<TEntity> WhereIn<TEntity, TValue> ( this ObjectQuery<TEntity> query, Expression<Func<TEntity, TValue>> selector, params TValue[] collection ) { return WhereIn(query, selector, (IEnumerable<TValue>)collection); }
USO:
public static void Main() { using (MyObjectContext context = new MyObjectContext()) { //Using method 1 - collection provided as collection var contacts1 = context.Contacts.WhereIn(c => c.Name, GetContactNames()); //Using method 2 - collection provided statically var contacts2 = context.Contacts.WhereIn(c => c.Name, "Contact1", "Contact2", "Contact3", "Contact4" ); } }
fuente
if (!collection.Any()) //action;
acción - reemplazar con simplemente devolver una consulta vacía del tipo solicitado para obtener el mejor rendimiento - o simplemente elimine esta línea.Puede recurrir a la codificación manual de algunos e-sql (tenga en cuenta la palabra clave "it"):
return CurrentDataSource.Product.Where("it.ID IN {4,5,6}");
Aquí está el código que usé para generar algunos e-sql de una colección, YMMV:
string[] ids = orders.Select(x=>x.ProductID.ToString()).ToArray(); return CurrentDataSource.Products.Where("it.ID IN {" + string.Join(",", ids) + "}");
fuente
Desde MSDN :
static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>( Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values) { if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); } if (null == values) { throw new ArgumentNullException("values"); } ParameterExpression p = valueSelector.Parameters.Single(); // p => valueSelector(p) == values[0] || valueSelector(p) == ... if (!values.Any()) { return e => false; } var equals = values.Select( value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue)))); var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal)); return Expression.Lambda<Func<TElement, bool>>(body, p); }
y la consulta se convierte en:
var query2 = context.Entities.Where(BuildContainsExpression<Entity, int>(e => e.ID, ids));
fuente
No estoy seguro acerca de Silverligth, pero en relación con los objetos, siempre uso any () para estas consultas.
var q = from t in svc.OpenTranaction where txnIds.Any(t.OpenTransactionId) select t;
fuente
Para completar el registro, aquí está el código que finalmente usé (se omitió la verificación de errores para mayor claridad) ...
// How the function is called var q = (from t in svc.OpenTransaction.Expand("Currency,LineItem") select t) .Where(BuildContainsExpression<OpenTransaction, long>(tt => tt.OpenTransactionId, txnIds)); // The function to build the contains expression static System.Linq.Expressions.Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>( System.Linq.Expressions.Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values) { if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); } if (null == values) { throw new ArgumentNullException("values"); } System.Linq.Expressions.ParameterExpression p = valueSelector.Parameters.Single(); // p => valueSelector(p) == values[0] || valueSelector(p) == ... if (!values.Any()) { return e => false; } var equals = values.Select(value => (System.Linq.Expressions.Expression)System.Linq.Expressions.Expression.Equal(valueSelector.Body, System.Linq.Expressions.Expression.Constant(value, typeof(TValue)))); var body = equals.Aggregate<System.Linq.Expressions.Expression>((accumulate, equal) => System.Linq.Expressions.Expression.Or(accumulate, equal)); return System.Linq.Expressions.Expression.Lambda<Func<TElement, bool>>(body, p); }
fuente
Aquí hay un ejemplo en el que demuestro cómo escribir consultas basadas en conjuntos utilizando DataServiceContext: http://blogs.msdn.com/phaniraj/archive/2008/07/17/set-based-operations-in-ado-net-data -services.aspx
fuente
Muchas gracias. WhereIn el método de extensión fue suficiente para mí. Lo perfilé y generé el mismo comando SQL en la base de datos que e-sql.
public Estado[] GetSomeOtherMore(int[] values) { var result = _context.Estados.WhereIn(args => args.Id, values) ; return result.ToArray(); }
Generado esto:
SELECT [Extent1].[intIdFRLEstado] AS [intIdFRLEstado], [Extent1].[varDescripcion] AS [varDescripcion] FROM [dbo].[PVN_FRLEstados] AS [Extent1] WHERE (2 = [Extent1].[intIdFRLEstado]) OR (4 = [Extent1].[intIdFRLEstado]) OR (8 = [Extent1].[intIdFRLEstado])
fuente
Creo que unirse a LINQ puede ser un recorrido.
Aunque no he probado el código. Espero eso ayude. Salud. :-)
List<long?> txnIds = new List<long?>(); // Fill list var q = from t in svc.OpenTransaction join tID in txtIds on t equals tID select t;
Únase a LINQ:
http://weblogs.asp.net/salimfayad/archive/2008/07/09/linq-to-entities-join-queries.aspx
fuente
Lo siento nuevo usuario, habría comentado la respuesta real, pero parece que todavía no puedo hacerlo.
De todos modos, con respecto a la respuesta con código de muestra para BuildContainsExpression (), tenga en cuenta que si usa ese método en Entidades de base de datos (es decir, no objetos en memoria) y está usando IQueryable, en realidad tiene que ir a la base de datos ya que básicamente hace muchas condiciones SQL "o" para verificar la cláusula "dónde en" (ejecútelo con SQL Profiler para ver).
Esto puede significar que, si está refinando un IQueryable con múltiples BuildContainsExpression (), no lo convertirá en una declaración SQL que se ejecuta al final como espera.
La solución para nosotros fue usar múltiples combinaciones LINQ para mantenerlo en una llamada SQL.
fuente
Además de la respuesta seleccionada.
Reemplace
Expression.Or
conExpression.OrElse
para usar con Nhibernate y corrija laUnable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'
excepción.fuente