J'essaie de créer une requête qui utilise une liste d'ID dans la clause where, en utilisant l'API du client Silverlight ADO.Net Data Services (et donc Linq To Entities). Quelqu'un connaît-il une solution de contournement à Contains non pris en charge?
Je veux faire quelque chose comme ça:
List<long?> txnIds = new List<long?>();
// Fill list
var q = from t in svc.OpenTransaction
where txnIds.Contains(t.OpenTransactionId)
select t;
J'ai essayé ceci:
var q = from t in svc.OpenTransaction
where txnIds.Any<long>(tt => tt == t.OpenTransactionId)
select t;
Mais obtenu "La méthode 'Any' n'est pas prise en charge".
Mise à jour: EF ≥ 4 prend en charge Contains
directement (Checkout Any
), vous n'avez donc pas besoin de solution de contournement.
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);
}
USAGE:
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"
);
}
}
Vous pouvez vous rabattre sur le codage manuel de certains e-sql (notez le mot-clé "it"):
return CurrentDataSource.Product.Where("it.ID IN {4,5,6}");
Voici le code que j'ai utilisé pour générer du e-sql à partir d'une collection, YMMV:
string[] ids = orders.Select(x=>x.ProductID.ToString()).ToArray();
return CurrentDataSource.Products.Where("it.ID IN {" + string.Join(",", ids) + "}");
De 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);
}
et la requête devient:
var query2 = context.Entities.Where(BuildContainsExpression<Entity, int>(e => e.ID, ids));
Je ne suis pas sûr de Silverligth, mais dans linq to objects j'utilise toujours any () pour ces requêtes.
var q = from t in svc.OpenTranaction
where txnIds.Any(t.OpenTransactionId)
select t;
Pour compléter l'enregistrement, voici le code que j'ai finalement utilisé (vérification d'erreur omise pour plus de clarté) ...
// 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);
}
Merci beaucoup. La méthode d'extension WhereIn me suffisait. Je l'ai profilé et généré la même commande SQL dans la base de données que e-sql.
public Estado[] GetSomeOtherMore(int[] values)
{
var result = _context.Estados.WhereIn(args => args.Id, values) ;
return result.ToArray();
}
Généré ceci:
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])
En plus de la réponse sélectionnée.
Remplacer Expression.Or
avec Expression.OrElse
à utiliser avec Nhibernate et à corriger Unable to cast object of type 'NHibernate.Hql.Ast.HqlBitwiseOr' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'
exception.
Désolé nouvel utilisateur, j'aurais commenté la réponse réelle, mais il semble que je ne puisse pas encore le faire?
Quoi qu'il en soit, en ce qui concerne la réponse avec un exemple de code pour BuildContainsExpression (), sachez que si vous utilisez cette méthode sur des entités de base de données (c'est-à-dire pas des objets en mémoire) et que vous utilisez IQueryable, elle doit en fait aller dans la base de données car il fait essentiellement beaucoup de conditions SQL "ou" pour vérifier la clause "where in" (exécutez-la avec SQL Profiler pour voir).
Cela peut signifier que si vous affinez un IQueryable avec plusieurs BuildContainsExpression (), cela ne le transformera pas en une instruction SQL qui sera exécutée à la fin comme vous vous y attendez.
La solution de contournement pour nous consistait à utiliser plusieurs jointures LINQ pour le limiter à un seul appel SQL.
Voici un exemple où je montre comment écrire des requêtes basées sur un ensemble à l'aide de DataServiceContext: http://blogs.msdn.com/phaniraj/archive/2008/07/17/set-based-operations-in-ado -net-data-services.aspx
Je pense qu'un Join in LINQ peut être une solution.
Je n'ai cependant pas testé le code. J'espère que ça aide. À votre santé. :-)
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;
Rejoignez LINQ:
http://weblogs.asp.net/salimfayad/archive/2008/07/09/linq-to-entities-join-queries.aspx