J'ai un modèle de données où un objet 'Top' a entre 0 et N 'objets' Sub '. En SQL, ceci est réalisé avec une clé étrangère dbo.Sub.TopId
.
var query = context.Top
//.Include(t => t.Sub) Doesn't seem to do anything
.Select(t => new {
prop1 = t.C1,
prop2 = t.Sub.Select(s => new {
prop21 = s.C3 //C3 is a column in the table 'Sub'
})
//.ToArray() results in N + 1 queries
});
var res = query.ToArray();
Dans Entity Framework 6 (avec le chargement différé), cette requête Linq serait convertie en une requête SQL single. Le résultat serait entièrement chargé, donc res[0].prop2
serait un IEnumerable<SomeAnonymousType>
déjà rempli.
Lors de l'utilisation de EntityFrameworkCore (NuGet v1.1.0), la sous-collection n'est pas encore chargée et est du type:
System.Linq.Enumerable.WhereSelectEnumerableIterator<Microsoft.EntityFrameworkCore.Storage.ValueBuffer, <>f__AnonymousType1<string>>.
Les données ne seront pas chargées jusqu'à ce que vous les parcouriez, ce qui entraîne N + 1 requêtes. Lorsque j'ajoute .ToArray()
à la requête (comme indiqué dans les commentaires), les données sont entièrement chargées dans var res
, mais l'utilisation d'un profileur SQL indique toutefois que cela n'est plus possible dans une requête SQL. Pour chaque objet "Top", une requête sur la table "Sub" est exécutée.
D'abord, spécifier .Include(t => t.Sub)
ne semble rien changer. L'utilisation de types anonymes ne semble pas être le problème non plus, remplacer les blocs new { ... }
par new MyPocoClass { ... }
ne change rien.
Ma question est la suivante: Existe-t-il un moyen d'obtenir un comportement similaire à EF6, où toutes les données sont chargées immédiatement?
Note: Je réalise que, dans cet exemple, le problème peut être résolu en créant les objets anonymes en mémoire après en exécutant la requête comme suit:
var query2 = context.Top
.Include(t => t.Sub)
.ToArray()
.Select(t => new //... select what is needed, fill anonymous types
Cependant, il ne s'agit que d'un exemple, j'ai besoin de la création d'objets pour faire partie de la requête Linq, car AutoMapper l'utilise pour remplir les DTO de mon projet.
Mise à jour: Testé avec le nouvel EF Core 2.0, le problème est toujours présent. (21-08-2017)
Le problème est suivi dans le aspnet/EntityFrameworkCore
GitHub repo: Numéro 4007 </ S>.
Mise à jour: Un an plus tard, ce problème a été corrigé dans la version 2.1.0-preview1-final
. (2018-03-01)
Mise à jour: EF version 2.1 a été publiée, elle inclut un correctif. voir ma réponse ci-dessous. (2018-05-31)
Le numéro de GitHub # 4007 a été marqué comme closed-fixed
pour le jalon 2.1.0-preview1
. Et maintenant, la version 2.1 de preview1 est disponible sur NuGet comme indiqué dans cet article .NET Blog post .
La version 2.1 proprement dite est également publiée, installez-la avec la commande suivante:
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.1.0
Utilisez ensuite .ToList()
sur la .Select(x => ...)
imbriquée pour indiquer que le résultat doit être extrait immédiatement. Pour ma question initiale, cela ressemble à ceci:
var query = context.Top
.Select(t => new {
prop1 = t.C1,
prop2 = t.Sub.Select(s => new {
prop21 = s.C3
})
.ToList() // <-- Add this
});
var res = query.ToArray(); // Execute the Linq query
Cela entraîne l'exécution de 2 requêtes SQL sur la base de données (au lieu de N + 1); Tout d'abord, une table SELECT
FROM
laine dans la table 'Top', puis une table SELECT
FROM
_la 'Sous' avec une table INNER JOIN
FROM
'la 'table', basée sur la relation clé-clé étrangère [Sub].[TopId] = [Top].[Id]
. Les résultats de ces requêtes sont ensuite combinés en mémoire.
Le résultat est exactement ce que vous attendiez et très similaire à ce que EF6 aurait renvoyé: Un tableau de type anonyme 'a
qui a les propriétés prop1
et prop2
où prop2
est une liste de type anonyme 'b
qui a une propriété prop21
. Plus important encore tout cela est complètement chargé après l'appel .ToArray()
!
J'ai rencontré le même problème.
La solution que vous avez proposée ne fonctionne pas pour des tables relativement grandes. Si vous examinez la requête générée, il s'agira d'une jointure interne sans condition where.
var query2 = context.Top .Include (t => t.Sub) .ToArray () .Select (t => new // ... sélectionne ce qui est nécessaire, remplit les types anonymes
Je l'ai résolu avec la refonte de la base de données, même si je serais heureux d'entendre une meilleure solution.
Dans mon cas, j’ai deux tables A et B. La table A contient un-à-plusieurs avec B. Lorsque j’essayais de la résoudre directement avec une liste comme vous l’aviez décrite, je n’étais pas parvenu à le faire LINQ était de 0,5 seconde, alors que .NET Core LINQ a échoué après 30 secondes de temps d'exécution).
En conséquence, j'ai dû créer une clé étrangère pour la table B et partir du côté de la table B sans liste interne.
context.A.Where(a => a.B.ID == 1).ToArray();
Ensuite, vous pouvez simplement manipuler les objets .NET obtenus.