17 votes

Requête Linq avec plusieurs Containers/Any pour RavenDB

J'ai une classe de document qui contient une liste de "balises". Quelque chose comme :

class Item {
  string Name { get; set; }
  List<string> Tags {get; set;}
}

Je voudrais maintenant créer une requête pour RavenDB qui me donne tous les éléments filtrés par une liste de tags. En utilisant Entity Framework, j'ai réussi à le faire avec quelque chose comme ceci :

var query = GetQueryable();
foreach (var tag in tags)
{
   query = query.Where(i => i.Tags.Contains(tag));
}

Cependant, cela ne semble pas fonctionner avec RavenDB, très probablement parce que Contains n'est pas supporté . J'ai également essayé de le réécrire en utilisant Any, ( Where(i => i.Tags.Any(t=>t == tag)) ) mais cela me donne une exception étrange :

Unable to cast object of type
'System.Linq.Expressions.PrimitiveParameterExpression`1[System.String]'
to type 'System.Linq.Expressions.MemberExpression

Des idées géniales ? Est-ce que je m'y prends mal ?

17voto

Rob Ashton Points 2238

Contains n'est en effet pas encore supporté (il devrait peut-être l'être, mais c'est une toute autre question - nous n'ajoutons vraiment le support pour divers opérateurs que lorsque cela est demandé).

En ce qui concerne les requêtes multiples contre Any, je suppose que vous essayez de faire des données dynamiques et que vous voulez obtenir quelque chose du genre

"X OR Y OR Z"

C'est une question délicate, et le fournisseur LINQ par défaut regroupera ces multiples clauses WHERE avec AND, de sorte que votre exemple ressemble à ceci

"X AND Y AND Z"

Ce qui ne sera évidemment jamais le cas.

Votre meilleure option est de passer à la requête Lucene (du moins pour l'instant) et de faire quelque chose comme ceci :

var results = s.Advanced.LuceneQuery<Item>()
                   .Where(string.Format("Tags,:({0})", string.Join(" OR ", tags))); 

C'est logique ?

La requête ci-dessus ressemblera à quelque chose comme

"Tags,:(X OR Y OR Z)"

Note : "Tags," indique à RavenDB que Tags est un tableau.

Okay, [edit] !

Le moyen le plus simple d'obtenir ce que vous avez réellement veulent est de faire quelque chose de ce genre

                new IndexDefinition<Item, Item>()
                {
                    Map = docs => from doc in docs
                                  select new
                                  {
                                      Tags = doc.Tags
                                  },
                    Indexes = {{ x => x.Tags, FieldIndexing.Analyzed }}
                }.ToIndexDefinition(store.Conventions));

Ensuite, pour faire une requête pour vos "et", vous pouvez faire quelque chose comme ça :

                var results = s.Advanced.LuceneQuery<Item, WhateverYouCalledThatIndex>()
                   .Where(string.Format("Tags:({0})", string.Join(" AND ", tags)));

Maintenant, les choses à savoir

       Tags = doc.Tags

Sérialise l'ensemble du tableau en un seul blob géant, puisque ce ne sont que des chaînes de caractères qui fonctionnent dans cet exemple.

Je suis en train de chercher de meilleures façons d'exprimer cela, il est peu probable que nous trouvions une façon de le faire à la manière de LINQ, car il n'y a pas vraiment de correspondance entre les deux, mais cela es une réponse qui fonctionne :)

Je pense que j'aimerais bien être capable de faire au moins

  Map = docs => from doc in docs
                                  select new
                                  {
                                      Tags = String.Join(" ", doc.Tags)
                                  },

(Cela ne marchera pas, n'essayez pas), mais il est un peu plus explicite sur ce que vous voulez obtenir.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X