web-dev-qa-db-fra.com

Solution de contournement «Contains ()» utilisant Linq to Entities?

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".

85
James Bloomer

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"
      );
  }
}
96
Shimmy

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) + "}");
18
Rob Fonseca-Ensor

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));
13
James Bloomer

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;
2
AndreasN

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);
        }
1
James Bloomer

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])
0
jrojo

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.

0
smg

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.

0
Shannon

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

0
Phani Raj

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

0
GabrielC