c# - net - ''Contiene()'' solución alternativa utilizando Linq para Entidades?
linq.net core (10)
Intento crear una consulta que use una lista de identificadores en la cláusula where, utilizando la API del cliente ADO.Net Data Services de Silverlight (y, por lo tanto, Linq To Entities). ¿Alguien sabe de una solución alternativa a que Contains no es compatible?
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 obtuve "El método ''Cualquiera'' no es compatible".
Además de la respuesta seleccionada.
Reemplace Expression.Or
con Expression.OrElse
para usar con Nhibernate y corregir Unable to cast object of type ''NHibernate.Hql.Ast.HqlBitwiseOr'' to type ''NHibernate.Hql.Ast.HqlBooleanExpression''
excepción Unable to cast object of type ''NHibernate.Hql.Ast.HqlBitwiseOr'' to type ''NHibernate.Hql.Ast.HqlBooleanExpression''
.
Aquí hay un ejemplo donde demuestro cómo escribir consultas basadas en conjuntos usando el DataServiceContext: http://blogs.msdn.com/phaniraj/archive/2008/07/17/set-based-operations-in-ado-net-data-services.aspx
Creo que un Join in LINQ puede ser un paseo.
Aunque no he probado el código. Espero eso ayude. Aclamaciones. :-)
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;
Únete en LINQ:
http://weblogs.asp.net/salimfayad/archive/2008/07/09/linq-to-entities-join-queries.aspx
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));
Lo siento nuevo usuario, habría comentado sobre la respuesta real, pero parece que no puedo hacer eso todavía?
De todos modos, en cuanto a la respuesta con código de ejemplo para BuildContainsExpression (), tenga en cuenta que si usa ese método en entidades de base de datos (es decir, no en objetos en memoria) y está utilizando IQueryable, realmente tiene que ir a la base de datos ya que básicamente hace un montón de SQL "o" condiciones para verificar la cláusula "where in" (ejecutarlo con SQL Profiler para ver).
Esto puede significar que, si está redefiniendo 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 utilizar varias combinaciones LINQ para mantenerlo en una llamada SQL.
Muchas gracias. Donde el método de extensión fue suficiente para mí. Lo perfilé y generé el mismo comando SQL para la Base de datos como 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])
No estoy seguro acerca de Silverligth, pero en linq a los objetos, siempre uso any () para estas consultas.
var q = from t in svc.OpenTranaction
where txnIds.Any(t.OpenTransactionId)
select t;
Para completar el registro, aquí está el código que finalmente utilicé (se omite 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);
}
Puede recurrir a la codificación manual de algunos e-sql (tenga en cuenta la palabra clave "eso"):
return CurrentDataSource.Product.Where("it.ID IN {4,5,6}");
Aquí está el código que utilicé 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) + "}");
Actualización: EF ≥ 4 admite Contains
directamente (Checkout Any
), 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"
);
}
}