UPDATE
Как отмечали другие, при работе с Entity Framework 4.0 выравнивание результатов (как показано ниже) не требуется, поскольку оно может преобразовать запрос LINQ в эффективный для вас плоский результат. Поэтому следующий код необходим только при работе с LINQ to SQL (или, возможно, другими поставщиками LINQ). Обратите внимание, что я проверял это только с EF поверх SQL Server, а не над Oracle, поскольку такое поведение может зависеть от поставщика LINQ, что означает, что поставщик Oracle (все еще в бета-версии) или коммерческий поставщик Devart для Oracle все еще может выполнять N + 1.
То, что вы пытаетесь сделать, это получить набор объектов, которые структурированы как дерево. Без особой заботы вы будете вызывать много запросов к базе данных. С одним уровнем вложенности вы будете запускать N + 1 запросов, но так как ваша вложенность имеет два уровня глубины, вы будете запускать M x (N + 1) + 1 запросов, что почти наверняка будет очень плохо для производительности (независимо от размера вашего набора данных). Вам нужно убедиться, что в базу данных отправлен только один запрос. Чтобы убедиться в этом, вы должны создать промежуточный запрос, который выравнивает результат, как вы это делали в старые добрые времена SQL, для извлечения данных, подобных дереву: -). Взгляните на следующий пример:
var records =
from record in db.TableC
where ... // any filtering can be done here
select record;
// important to call ToArray. This ensures that the flatterned result
// is pulled in one single SQL query.
var results = (
from c in records
select new
{
tableA_rowid = c.B.A.Id,
tableA_Prop1 = c.B.A.Property1,
tableA_Prop2 = c.B.A.Property2,
tableA_PropN = c.B.A.PropertyN,
tableB_rowid = c.B.Id,
tableB_Property1 = c.B.Property1,
tableB_Property2 = c.B.Property2,
tableB_PropertyN = c.B.PropertyN,
tableC_rowid = c.Id,
tableC_Property1 = c.Property1,
tableC_Property2 = c.Property2,
tableC_PropertyN = c.PropertyN,
})
.ToArray();
Следующим шагом является преобразование этой структуры данных в памяти (с использованием этого анонимного типа) в древовидную структуру объектов DTO:
// translate the results to DTO tree structure
TableA_DTO[] dtos = (
from aresult in results
group aresult by aresult.tableA_rowid into group_a
let a = group_a.First()
select new TableA_DTO
{
tableA_rowid = a.tableA_rowid,
tableA_Prop1 = a.tableA_Prop1,
tableA_Prop2 = a.tableA_Prop2,
TableB_records = (
from bresult in group_a
group bresult by bresult.tableB_rowid into group_b
let b = group_b.First()
select new TableB_DTO
{
tableB_rowid = b.tableB_rowid,
tableB_Prop1 = b.tableB_Prop1,
tableB_Prop2 = b.tableB_Prop2,
TableC_records = (
from c in group_b
select new TableC_DTO
{
tableC_rowid = c.tableC_rowid,
tableC_Prop1 = c.tableC_Prop1,
tableC_Prop2 = c.tableC_Prop2,
}).ToList(),
}).ToList()
})
.ToArray();
Как видите, первая часть решения на самом деле является «старым» способом сделать это, еще тогда, когда мы все еще писали наши SQL-запросы вручную. Приятно, однако, что как только мы получим этот типизированный набор данных в памяти, мы снова сможем использовать LINQ (для объектов), чтобы получить эти данные в нужной нам структуре.
Обратите внимание, что это также позволяет вам выполнять разбиение по страницам и сортировку. Это будет немного сложнее, но, конечно, не невозможно.