2009-08-12 1 views
3

Ok,Linq2Sql - En utilisant une collection locale dans le cadre d'une requête sous - requêtes « avec des collections locales ne sont pas pris en charge »

dernière fois que je posté (la semaine dernière), je ne l'ai pas décrit correctement le problème. J'ai créé un échantillon rapide de ce problème. L'interrogation des collections locales fonctionne correctement lorsque vous l'utilisez dans le cadre de la requête de base. Le problème que je trouve est de l'utiliser avec une partie d'une sous-requête. Par exemple.

Ceci est assez difficile à décrire sans vous donner un schéma de base de données ou un schéma de code, mais je ferai de mon mieux. J'essaye d'exécuter mon code avec une requête à la DB. Je ne veux pas avoir à le décomposer et envoyer plusieurs commandes. Le faire de cette façon a certains avantages, y compris un éviter un problème possible que je vais expliquer vers la fin de cela.

Je rejoins certaines tables qui ont une relation. La table attributs (DataEventAttributes) décrit bien sûr les attributs propres à une ligne spécifique de la table principale (DataEvents).

Lorsque je l'interroge sans aucune collecte locale, chose fonctionne très bien et extrêmement rapidement contre ma base de données de 20 Go. Cependant, si je jette une collection locale de valeurs dans une partie de la sous-requête qui obtient les résultats, j'obtiendrai les "Requêtes avec des collections locales non supportées"

Cela a été assez difficile à reproduire dans mon code, donc je vais le commenter aussi bien que je peux faire, vous pouvez suivre ce que je fais.

// gets the initial query and join. We actually only care about the ID in the end, but we use the joined data 
     // to determine if a row needs to be pulled. 
     var initialQuery = from dataEvent in DataEvent.GetByQueryExpression(context) 
            join attribute in DataEventAttribute.GetByQueryExpression(context) on dataEvent.DataEventID 
             equals attribute.DataEventID 
          select new 
          { 
           ID = dataEvent.DataEventID, 
           PluginID = dataEvent.DataOwnerID, 
           TimeStamp = dataEvent.DataTimeStamp, 
           DataEventKeyID = attribute.DataEventKeyID, 
           ValueString = attribute.ValueString, 
           ValueDecimal = attribute.ValueDecimal 
          }; 

     // list of some ids that we need to confirm exist in the initial query before the final query 
     var someSetOfIDs = new List<int>() {1, 2, 3, 4, 5}; 

     // This is the local collection thats filtering out some results before I rebuild the entire result set in the final query 
     // If you comment this line out, the finalQuery will execute just fine. 
     // with this in place, the "Queries with local collections are not supported" error will come about. 
     initialQuery = initialQuery.Where(x => x.DataEventKeyID == 1 && someSetOfIDs.Contains((int) x.ValueDecimal)); 

     // reusable query for the sub queries in the results -- not part of the problem, just part of the example 
     var attributeBaseQuery = from attribute in DataEventAttribute.GetByQueryExpression(context) select attribute; 

     // Builds the final result With the IDs from the initial query 
     // the group by is to remove any duplicates that may be in the collection. 
     // the select key is getting the ID that i needed 
     // the select ID is the ID of the first item that was grouped. 
     // the contains compares the local dataEvent object with the ID table (checking to see if it exists) 
     // the result is just an example of one item I can be pulling out of the database with the new type 
     var finalQuery = from dataEvent in DataEvent.GetByQueryExpression(context) 
         where initialQuery.GroupBy(x => x).Select(x => x.Key).Select(x => x.ID).Contains(dataEvent.DataEventID) 
         select new 
            { 
             BasicData = 
             attributeBaseQuery.Where(
             attrValue => 
             attrValue.DataEventID == dataEvent.DataEventID && 
             attrValue.DataEventKeyID == (short) DataEventTypesEnum.BasicData).FirstOrDefault(). 
             ValueString 
            }; 

     var finalResult = finalQuery.Take(100).ToList(); 

L'une solution j'ai trouvé est de faire un .ToList() après la .Select (x => x.ID) dans le finalQuery, mais l'effet secondaire comporte deux négatifs. Premièrement, il exécute cette requête en premier, et obtient les ID de la base de données. Ensuite, il doit renvoyer ces résultats au serveur SQL en tant que paramètres de la requête finale. Le second problème majeur (show stopper) est que s'il y a beaucoup de résultats dans le .ToList(), le serveur SQL lancera un étrange message d'erreur et les recherches Google montrent qu'il y a beaucoup de paramètres passés (ce qui aurait du sens, le nombre de paramètre pourrait être dans les 10-100s de milliers). Donc, cela dit, j'essaie de comprendre comment construire une requête que je peux ajuster les critères dynamiquement, puis reconstruire mes ensembles de résultats avec tous les attributs qui correspondent à l'ID qui répond aux critères de la sous-requête . Dans le serveur SQL via le studio, cela fonctionne très bien, mais le problème de la collection m'a un hold-up. J'ai essayé de plusieurs façons, mais il semble que la seule façon de reproduire cela est d'avoir une requête qui utilise une collection locale, puis d'utiliser cette requête dans le cadre d'une autre requête qui filtre les résultats en utilisant la première requête.

Des idées comment je peux le faire?

Screen shot show you know I'm not crazy.

Merci à l'avance pour l'aide

Répondre

2

AFAIK, il n'est pas possible d'utiliser des collections en mémoire dans les requêtes LINQ to SQL. Je peux penser à deux contournements possibles:

Option 1: Effectuer une requête pour chaque ID:

var someSetOfIDs = new List<int>() {1, 2, 3, 4, 5}; 

    // queryPerID will have type IEnumerable<IQueryable<'a>> 
    var queryPerID = from id in someSetOfIDs 
        select (
         from dataEvent in DataEvent.GetByQueryExpression(context) 
         join attribute in DataEventAttribute.GetByQueryExpression(context) 
         on dataEvent.DataEventID 
            equals attribute.DataEventID 
         where attribute.DataEventKeyID == 1 
           && (int)attribute.ValueDecimal == id // Changed from Contains 
         select new 
         { 
          ID = dataEvent.DataEventID, 
          PluginID = dataEvent.DataOwnerID, 
          TimeStamp = dataEvent.DataTimeStamp, 
          DataEventKeyID = attribute.DataEventKeyID, 
          ValueString = attribute.ValueString, 
          ValueDecimal = attribute.ValueDecimal 
         }); 

    // For each of those queries, we an equivalent final queryable 
    var res = from initialQuery in queryPerID 
       select (
        from dataEvent in DataEvent.GetByQueryExpression(context) 
        where initialQuery.GroupBy(x => x).Select(x => x.Key.ID).Contains(dataEvent.DataEventID) 
        select new 
        { 
         BasicData = 
          attributeBaseQuery.Where(
          attrValue => 
           attrValue.DataEventID == dataEvent.DataEventID && 
           attrValue.DataEventKeyID == (short) DataEventTypesEnum.BasicData).FirstOrDefault(). 
           ValueString 
        }) into finalQuery 
       from x in finalQuery 
       select x; 

    var finalResult = finalQuery.Take(100).ToList(); 

Je ne suis pas sûr que même compilable, mais il devrait être assez proche.

Option 2: Générez une expression de prédicat à partir de someSetOfIDs pour la transmettre à SQL.

 var someSetOfIDs = new List<decimal>() { 1, 2, 3, 4, 5 }; 

     Expression<Func<DataEventAttribute, bool>> seed = x => false; 
     var predicate = someSetOfIDs.Aggregate(seed, 
      (e, i) => Expression.Lambda<Func<DataEventAttribute, bool>>(
       Expression.OrElse(
        Expression.Equal(
         Expression.Property(
          e.Parameters[0], 
          "ValueDecimal"), 
         Expression.Constant(i)), 
        e.Body), 
       e.Parameters)); 

Essentiellement, nous avons construit une clause where:

x => ((x.ValueDecimal = 5) || ((x.ValueDecimal = 4) || ((x.ValueDecimal = 3) || 
((x.ValueDecimal = 2) || ((x.ValueDecimal = 1) || False))))) 

Il est important de noter que cette approche ne fonctionne pas avec les types anonymes, vous devez utiliser le prédicat sur un interrogeable avec un type nommé. Ce n'est pas un problème si vous réorganisez un peu (et peut produire un meilleur plan de requête, en fait):

var attributes = DataEventAttribute.GetByQueryExpression(context) 
        .Where(a => a.DataEventKeyID ==1) 
        .Where(predicate); 

    var initialQuery = from dataEvent in DataEvent.GetByQueryExpression(context) 
         join attribute in attributes 
         select new 
         { 
          ID = dataEvent.DataEventID, 
          PluginID = dataEvent.DataOwnerID, 
          TimeStamp = dataEvent.DataTimeStamp, 
          DataEventKeyID = attribute.DataEventKeyID, 
          ValueString = attribute.ValueString, 
          ValueDecimal = attribute.ValueDecimal 
         }; 
+0

Dahlbyk, Merci beaucoup. L'option 2 était le chemin à parcourir. Le SQl résultant était: OERE (([t5]. [ValeurDécimale] = @ p1) OU ([t5]. [ValeurDécimale] = @ p2) OU ([t5]. [ValeurDécimale] = @ p3) OU ([ t5]. [ValueDecimal] = @ p4) OU ([t5]. [ValueDecimal] = @ p5)) ET ([t5]. [DataEventKeyID] = @ p6) C'était exactement ce que je voulais/je voulais. Ce que vous avez montré dans votre exemple a prolongé mon processus de pensée et peut avoir résolu d'autres idées dans ma tête. Merci beaucoup pour la solution! – TravisWhidden

0

Je ne suis pas expert en la matière, mais LinqToSql fonctionne en construisant un arbre d'expression qui est convertie en une requête SQL au moment de l'exécution. Cela fonctionne très bien si toute votre requête peut être convertie en SQL. Cependant, ce que vous faites est en train de tenter de rejoindre votre requête SQL avec une collection d'objets .NET. Le problème est que cela ne fonctionnera pas car la jointure ne peut pas être traduite en requête SQL. Vous mélangez deux choses différentes - LinqToSql et LinqToObjects. Appeler ToList() sur votre LinqToSql lui a permis de fonctionner comme vous êtes alors de retour dans le domaine de LinqToObjects. Désolé, je crains de ne pas savoir comment ça marche.

PS.Peut-être voir cette question: Linq2Sql -> Searching the database against a local collection of values - Queries with local collections are not supported

+0

LINQ to SQL ne permet les tests contre les collections locales, mais pas dans toutes les situations. –

+0

Merci Dan. Cela fonctionne réellement si vous tirez simplement le sous IQueryable hors de la requête, et l'exécutez tout seul. vous pouvez interroger une liste/une collection locale par rapport à Linq2Sql, et l'objet/collection local est converti en une instruction -in- nice. Moi, j'essayais de trouver comment peupler le contenu du "In" (alias Contains dans le linq2sql) avec un select sans avoir les valeurs. Où dans (Sélectionnez someID d'une table où blah). dahlbyk avait une bonne idée avec le prédicat. Je garderai cela à l'esprit cependant. Merci pour les commentaires! – TravisWhidden

+0

Excellent, c'est une excellente nouvelle. Je suppose qu'une simple liste de types de valeur, tels que Ints pourrait fonctionner comme une instruction SQL IN. Je pensais à des collections d'objets plus génériques, qui évidemment ne seraient pas facilement convertibles. De toute façon, c'est bon d'apprendre quelque chose, alors merci. –

Questions connexes