web-dev-qa-db-fra.com

Framework d'entité: interrogation d'entités enfants

J'apprends Entity Framework au mo et j'ai des problèmes !!

Quelqu'un peut-il préciser si j'ai raison de penser que je ne peux pas obtenir un parent et un sous-ensemble de ses enfants de la base de données?

Par exemple...

db.Parents
.Include(p => p.Children)
.Where(p => p.Children.Any(c => c.Age >= 5))

Cela rendra tous les parents qui ont un enfant âgé de 5 ans et plus, mais si je parcourt la collection Parents.Children, tous les enfants seront présents (pas seulement ceux de plus de 5 ans).

Maintenant, la requête a un sens pour moi (j'ai demandé d'inclure des enfants et je les ai!), Mais je peux imaginer que j'aimerais que la clause where soit appliquée à la collection d'enfants dans certains scénarios.

Questions:

  1. Ce que j'ai dit est-il correct?
  2. Est-il possible d'obtenir les parents et juste un sous-ensemble de la base de données sans effectuer de nombreux appels vers la base de données?
  3. Suis-je loin de la marque? (Ce ne serait pas la 1ère fois) !!!!

J'ai trouvé quelques blogs et SO articles qui touchent au sujet, mais rien qui l'explique assez bien pour mon petit cerveau.

[~ # ~] modifier [~ # ~]

Ayant lu ceci blog (merci Daz Lewis) ....... Je ne comprends toujours pas !!!

Dans l'exemple donné dans le blog, je peux voir comment je peux y parvenir contre une seule instance de Parent, mais j'ai du mal à trouver comment je peux le faire avec une collection.

Comment pourrais-je obtenir un IEnumerable, dans lequel chacun des parents a une collection filtrée d'enfants (âge> = 5)?

Plus de précisions:

En réponse au commentaire de DonAndre, je suis après a) Une liste de parents qui ont un enfant de plus de 5 ans (et n'incluent que ces enfants).

Toute aide appréciée,

Merci.

44
ETFairfax

La seule façon d'obtenir une collection de parents avec une collection d'enfants filtrée dans un aller-retour de base de données unique est d'utiliser une projection. Il n'est pas possible d'utiliser un chargement rapide (Include) car il ne prend pas en charge le filtrage, Include charge toujours toute la collection. La méthode de chargement explicite indiquée par @Daz nécessite un aller-retour par entité parent.

Exemple:

var result = db.Parents
    .Select(p => new
    {
        Parent = p,
        Children = p.Children.Where(c => c.Age >= 5)
    })
    .ToList();

Vous pouvez travailler directement avec cette collection d'objets de type anonyme. (Vous pouvez également projeter dans votre propre type nommé au lieu d'une projection anonyme (mais pas dans une entité comme Parent).)

Le contexte d'EF remplira également la collection Children de Parent automatiquement si vous ne désactivez pas le suivi des modifications (en utilisant AsNoTracking() par exemple). Dans ce cas, vous pouvez ensuite projeter le parent hors du type de résultat anonyme (se produit en mémoire, pas de requête DB):

var parents = result.Select(a => a.Parent).ToList();

parents[i].Children Contiendra vos enfants filtrés pour chaque Parent.


Edit à votre dernier Edit dans la question:

Je suis après a) Une liste de parents qui ont un enfant de plus de 5 ans (et n'incluent que ces enfants).

Le code ci-dessus retournerait tous les parents et inclurait uniquement les enfants avec Age> = 5, donc potentiellement aussi les parents avec une collection d'enfants vide s'il n'y a que des enfants avec Age <5. Vous pouvez les filtrer en utilisant une clause Where supplémentaire pour que les parents obtiennent uniquement les parents qui ont à au moins un (Any) enfant avec Age> = 5:

var result = db.Parents
    .Where(p => p.Children.Any(c => c.Age >= 5))
    .Select(p => new
    {
        Parent = p,
        Children = p.Children.Where(c => c.Age >= 5)
    })
    .ToList();
47
Slauma

En prenant votre exemple, ce qui suit devrait faire ce dont vous avez besoin. Jetez un oeil ici pour plus d'informations.

db.Entry(Parents)
.Collection("Children")
.Query().Cast<Child>()
.Where(c => c.Age >= 5))
.Load();
3
Darren Lewis

Je pense que les parents et l'enfant ne sont pas vraiment bien adaptés en tant qu'entités distinctes. Un enfant peut toujours aussi être un parent et généralement un enfant a deux parents (un père et une mère), ce n'est donc pas le contexte le plus simple. Mais je suppose que vous avez juste une relation 1: n simple comme dans le modèle maître-esclave suivant que j'ai utilisé.

Ce que vous devez faire est de faire un jointure externe gauche (cette réponse m'a conduit sur le bon chemin). Une telle jointure est un peu délicate à faire, mais voici le code

var query = from m in ctx.Masters
            join s in ctx.Slaves
              on m.MasterId equals s.MasterId into masterSlaves
            from ms in masterSlaves.Where(x => x.Age > 5).DefaultIfEmpty()
            select new {
              Master = m,
              Slave = ms
            };

foreach (var item in query) {
  if (item.Slave == null) Console.WriteLine("{0} owns nobody.", item.Master.Name);
  else Console.WriteLine("{0} owns {1} at age {2}.", item.Master.Name, item.Slave.Name, item.Slave.Age);
}

Cela se traduira par l'instruction SQL suivante avec EF 4.1

SELECT 
[Extent1].[MasterId] AS [MasterId], 
[Extent1].[Name] AS [Name], 
[Extent2].[SlaveId] AS [SlaveId], 
[Extent2].[MasterId] AS [MasterId1], 
[Extent2].[Name] AS [Name1], 
[Extent2].[Age] AS [Age]
FROM  [dbo].[Master] AS [Extent1]
LEFT OUTER JOIN [dbo].[Slave] AS [Extent2]
ON ([Extent1].[MasterId] = [Extent2].[MasterId]) AND ([Extent2].[Age] > 5)

Notez qu'il est important d'effectuer la clause where supplémentaire sur l'âge sur la collection jointe et non entre le from et le select.

MODIFIER:

SI vous voulez un résultat hiérarchique, vous pouvez convertir la liste plate en effectuant un regroupement:

var hierarchical = from line in query
                   group line by line.Master into grouped
                   select new { Master = grouped.Key, Slaves = grouped.Select(x => x.Slave).Where(x => x != null) };

foreach (var elem in hierarchical) {
   Master master = elem.Master;
   Console.WriteLine("{0}:", master.Name);
   foreach (var s in elem.Slaves) // note that it says elem.Slaves not master.Slaves here!
     Console.WriteLine("{0} at {1}", s.Name, s.Age);
}

Notez que j'ai utilisé un type anonyme pour stocker le résultat hiérarchique. Vous pouvez bien sûr également créer un type spécifique comme celui-ci

class FilteredResult {
  public Master Master { get; set; }
  public IEnumerable<Slave> Slaves { get; set; }
}

puis projetez le groupe dans des instances de cette classe. Cela rend plus facile si vous devez transmettre ces résultats à d'autres méthodes.

2
Andreas