2009-05-13 4 views
7

J'ai une requête LINQ to SQL:LINQ to SQL Prenez w/o Ignorer les multiples causes des instructions SQL

from at in Context.Transaction 
select new { 
    at.Amount, 
    at.PostingDate, 
    Details = 
     from tb in at.TransactionDetail 
     select new { 
      Amount = tb.Amount, 
      Description = tb.Desc 
     } 
} 

Il en résulte une instruction SQL en cours d'exécution. Tout est bon. Cependant, si je tente de renvoyer des types connus à partir de cette requête, même s'ils ont la même structure que les types anonymes, une instruction SQL est exécutée pour le niveau supérieur, puis une instruction SQL supplémentaire pour chaque "enfant" ensemble.

Existe-t-il un moyen d'obtenir LINQ to SQL pour émettre une instruction SQL et utiliser des types connus?

EDIT: Je dois avoir un autre problème. Lorsque j'ai branché une version très simpliste (mais toujours hiérarchique) de ma requête dans LINQPad et utilisé des types connus récemment créés avec seulement 2 ou 3 membres, j'ai reçu une instruction SQL. Je posterai et mettrai à jour quand j'en saurai plus.

EDIT 2: Cela semble être dû à un bug dans Take. Voir ma réponse ci-dessous pour plus de détails.

Répondre

11

D'abord, un peu de raisonnement pour le bug Take.

Si vous prenez simplement, le traducteur de requête utilise simplement le haut. Top10 ne donnera pas la bonne réponse si la cardinalité est brisée en rejoignant une collection enfant. Ainsi, le traducteur de requête ne joint pas dans la collection enfant (à la place, il requeries pour les enfants).

Si vous Sauter et Take, le traducteur de requête entre en jeu avec une certaine logique RowNumber sur les lignes parents ... ces rownumbers laisser prendre 10 les parents, même si cela est vraiment 50 dossiers en raison de chaque parent ayant 5 enfants .

Si vous Ignorer (0) et Prendre, Ignorer est supprimé en tant que non-opération par le traducteur - c'est comme si vous n'aviez jamais dit Passer.

Cela va être un saut conceptuel difficile d'où vous êtes (en appelant Skip and Take) à une «solution de contournement simple». Ce que nous devons faire - est de forcer la traduction à se produire à un point où le traducteur ne peut pas supprimer Skip (0) en tant que non-opération. Nous devons appeler Sauter, et fournir le numéro ignoré à un moment ultérieur.

DataClasses1DataContext myDC = new DataClasses1DataContext(); 
    //setting up log so we can see what's going on 
myDC.Log = Console.Out; 

    //hierarchical query - not important 
var query = myDC.Options.Select(option => new{ 
    ID = option.ParentID, 
    Others = myDC.Options.Select(option2 => new{ 
    ID = option2.ParentID 
    }) 
}); 
    //request translation of the query! Important! 
var compQuery = System.Data.Linq.CompiledQuery 
    .Compile<DataClasses1DataContext, int, int, System.Collections.IEnumerable> 
    ((dc, skip, take) => query.Skip(skip).Take(take)); 

    //now run the query and specify that 0 rows are to be skipped. 
compQuery.Invoke(myDC, 0, 10); 

Ceci produit la requête suivante:

SELECT [t1].[ParentID], [t2].[ParentID] AS [ParentID2], (
    SELECT COUNT(*) 
    FROM [dbo].[Option] AS [t3] 
    ) AS [value] 
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[ID]) AS [ROW_NUMBER], [t0].[ParentID] 
    FROM [dbo].[Option] AS [t0] 
    ) AS [t1] 
LEFT OUTER JOIN [dbo].[Option] AS [t2] ON 1=1 
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p1 + @p2 
ORDER BY [t1].[ROW_NUMBER], [t2].[ID] 
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [0] 
-- @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0] 
-- @p2: Input Int (Size = 0; Prec = 0; Scale = 0) [10] 
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.1 

Et voici où nous gagnons!

WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p1 + @p2 
+0

Cela fonctionne mais j'ai dû utiliser IEnumerable au lieu de simplement IEnumerable pour appeler ToList. C'est aussi une demi-seconde plus rapide dans mon test. Bonne réponse. – JohnOpincar

0

Je n'ai pas eu la chance d'essayer cela, mais étant donné que le type anonyme ne fait pas partie de LINQ plutôt une construction C# Je me demande si vous pouvez utiliser:

from at in Context.Transaction 
select new KnownType(
    at.Amount, 
    at.PostingDate, 
    Details = 
     from tb in at.TransactionDetail 
     select KnownSubType(
       Amount = tb.Amount, 
       Description = tb.Desc 
     ) 
} 

Il est évident que les détails aurait besoin d'être une collection IEnumerable.

Je pourrais être miles de large sur ce sujet, mais il pourrait au moins vous donner une nouvelle ligne de pensée pour poursuivre qui ne peut pas blesser alors s'il vous plaît excusez mes randonnées.

2

J'ai maintenant déterminé que c'est le résultat d'un bug horrible. Le type anonyme versus connu s'est avéré ne pas être la cause. La vraie cause est Take.

Le résultat suivant dans une instruction SQL:

query.Skip(1).Take(10).ToList(); 
query.ToList(); 

Cependant, l'exposition suivante une instruction SQL par problème de ligne parent.

query.Skip(0).Take(10).ToList(); 
query.Take(10).ToList(); 

Quelqu'un peut-il penser à des solutions de contournement simples pour cela?

EDIT: La seule solution de contournement que j'ai inventée est de vérifier si je suis sur la première page (IE Skip (0)), puis de faire deux appels, l'un avec Take (1) et l'autre avec Skip (1) .Take (pageSize - 1) et addRange les listes ensemble.

+0

est pas le problème ici simplement que vous invoquez l'exécution de SQL avec le premier 'ToList()', puis la deuxième 'ToList()' invoque une requête pour tous les éléments des résultats du premier qui sont maintenant Mémoire? –

+0

Non, ce n'est pas le problème. – JohnOpincar