Подтвердить что ты не робот

Entity Framework: запрос дочерних объектов

Я изучаю Entity Framework в mo, и у меня проблемы!

Может ли кто-нибудь уточнить, правильно ли я полагаю, что я не могу получить родителя и его подмножество из db?

Например...

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

Это приведет к возвращению всех родителей, у которых есть ребенок в возрасте 5+, но если я буду проходить через коллекцию Parents.Children, все дети будут присутствовать (а не только те, кто старше 5 лет).

Теперь запрос имеет смысл для меня (я попросил включить детей, и у меня их есть!), но могу предположить, что я хотел бы, чтобы в некоторых сценариях было применено предложение where к дочерней коллекции.

Вопросы:

  • Это то, что я сказал правильно?
  • Можно ли получить родителей и просто подмножество из db без загрузки вызовов в db?
  • Неужели я не знаком? (Не было бы в первый раз)!!!!

Я нашел несколько блогов и сообщений SO, которые касаются темы, но ничего, что объясняет это достаточно хорошо для моего маленького мозга.

ИЗМЕНИТЬ

Прочитав это blog (спасибо Daz Lewis)....... Я все еще не понимаю!!!

В примере, приведенном в блоге, я вижу, как я могу добиться его от одного экземпляра родителя, но я изо всех сил пытаюсь понять, как я могу сделать это с помощью коллекции.

Как я могу получить IEnumerable, в котором каждый из родителей имеет фильтрованную коллекцию Children (Age >= 5)?

Дальнейшие разъяснения:

В ответ на комментарий DonAndre, я за a) Список родителей, у которых есть ребенок старше 5 (и включают только этих детей).

Любая помощь оценивается,

Спасибо.

4b9b3361

Ответ 1

Единственный способ получить коллекцию родителей с отфильтрованной дочерней коллекцией в одной броузере базы данных - это использовать проекцию. Нельзя использовать загружаемую загрузку (Include), поскольку она не поддерживает фильтрацию, Include всегда загружает всю коллекцию. Способ загрузки explicite, показанный @Daz, требует одного округления на родительский объект.

Пример:

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

Вы можете напрямую работать с этой коллекцией объектов анонимного типа. (Вы также можете проектировать свой собственный именованный тип вместо анонимной проекции (но не в объект, такой как Parent).)

Контекст EF также будет автоматически заполнять коллекцию Children Parent, если вы не отключите отслеживание изменений (например, с помощью AsNoTracking()). В этом случае вы можете проецировать родителя из анонимного типа результата (происходит в памяти, без запроса БД):

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

parents[i].Children будет содержать ваши фильтрованные дети для каждого Parent.


Изменить до последнего Редактировать в вопросе:

Я за a) Список родителей, у которых есть ребенок старше 5 лет (и включают только тех детей).

Приведенный выше код вернет всех родителей и включит только детей с Age >= 5, поэтому потенциально также родители с пустой дочерней коллекцией, если есть только дети с Age < 5. Вы можете отфильтровать их с помощью дополнительного предложения Where для родителей, чтобы получить только родителей, у которых есть хотя бы один (Any) ребенок с 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();

Ответ 2

Взяв ваш пример, вы должны сделать то, что вам нужно. Взгляните здесь для получения дополнительной информации.

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

Ответ 3

Я думаю, что родители и ребенок не очень хорошо подходят как отдельные сущности. Ребенок всегда может быть родителем, и обычно у ребенка есть два родителя (отец и мать), поэтому это не самый простой контекст. Но я предполагаю, что у вас просто простая связь 1: n, как в следующей модели master-slave, которую я использовал.

Что вам нужно сделать, это сделать левое внешнее соединение (этот ответ привел меня к правильному пути). Такое соединение немного сложно сделать, но вот код

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);
}

Это приведет к следующему оператору SQL с 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)

Обратите внимание, что важно выполнить дополнительное предложение where в возрасте по объединенной коллекции, а не между символами from и select.

EDIT:

ЕСЛИ вы хотите получить иерархический результат, вы можете преобразовать плоский список, выполнив группировку:

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);
}

Обратите внимание, что я использовал анонимный тип для хранения иерархического результата. Вы можете, конечно, создать также определенный тип, подобный этому

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

а затем проецируйте группу в экземпляры этого класса. Это облегчает вам передачу этих результатов другим методам.