Я пытаюсь отладить сгенерированный SQL-запрос EF 6.1.1, и я смог воспроизвести проблему с этим небольшим количеством куде. Это простая односторонняя однонаправленная связь, которую я пытаюсь загрузить:
Модель выглядит следующим образом:
public class Subscription
{
public Guid Id { get; set; }
public IList<Recipient> RecipientHistory { get; set; }
public IList<Payer> PayerHistory { get; set; }
}
public class Recipient
{
public Guid Id { get; set; }
}
public class Payer
{
public Guid Id { get; set; }
}
И классы отображения EF:
public class SubscriptionMapping : EntityTypeConfiguration<Subscription>
{
public SubscriptionMapping()
{
ToTable("Subscriptions");
HasKey(p => p.Id);
Property(p => p.Id).HasColumnName("subscription_id");
HasMany(p => p.RecipientHistory)
.WithRequired()
.Map(m => m.MapKey("subscription_id"));
HasMany(p => p.PayerHistory)
.WithRequired()
.Map(m => m.MapKey("subscription_id"));
}
}
public class RecipientMapping : EntityTypeConfiguration<Recipient>
{
public RecipientMapping()
{
ToTable("Subscription_recipients");
HasKey(p => p.Id);
Property(p => p.Id).HasColumnName("subscription_recipient_id");
}
}
public class PayerMapping : EntityTypeConfiguration<Payer>
{
public PayerMapping()
{
ToTable("Subscription_payers");
HasKey(p => p.Id);
Property(p => p.Id).HasColumnName("subscription_payer_id");
}
}
Когда я выполняю следующий запрос в хранилище:
db.Set<Subscription>()
.Include(s => s.RecipientHistory)
.Include(s => s.PayerHistory)
.ToList();
В результате SQL:
SELECT
[UnionAll1].[C2] AS [C1],
[UnionAll1].[subscription_id] AS [C2],
[UnionAll1].[C1] AS [C3],
[UnionAll1].[C3] AS [C4],
[UnionAll1].[subscription_recipient_id] AS [C5],
[UnionAll1].[subscription_id1] AS [C6],
[UnionAll1].[C4] AS [C7],
[UnionAll1].[C5] AS [C8],
[UnionAll1].[C6] AS [C9]
FROM (SELECT
CASE WHEN ([Extent2].[subscription_recipient_id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1],
1 AS [C2],
[Extent1].[subscription_id] AS [subscription_id],
CASE WHEN ([Extent2].[subscription_recipient_id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C3],
[Extent2].[subscription_recipient_id] AS [subscription_recipient_id],
[Extent2].[subscription_id] AS [subscription_id1],
CAST(NULL AS int) AS [C4],
CAST(NULL AS uniqueidentifier) AS [C5],
CAST(NULL AS uniqueidentifier) AS [C6]
FROM [dbo].[Subscriptions] AS [Extent1]
LEFT OUTER JOIN [dbo].[Subscription_recipients] AS [Extent2] ON [Extent1].[subscription_id] = [Extent2].[subscription_id]
UNION ALL
SELECT
2 AS [C1],
2 AS [C2],
[Extent3].[subscription_id] AS [subscription_id],
CAST(NULL AS int) AS [C3],
CAST(NULL AS uniqueidentifier) AS [C4],
CAST(NULL AS uniqueidentifier) AS [C5],
2 AS [C6],
[Extent4].[subscription_payer_id] AS [subscription_payer_id],
[Extent4].[subscription_id] AS [subscription_id1]
FROM [dbo].[Subscriptions] AS [Extent3]
INNER JOIN [dbo].[Subscription_payers] AS [Extent4] ON [Extent3].[subscription_id] = [Extent4].[subscription_id]) AS [UnionAll1]
ORDER BY [UnionAll1].[subscription_id] ASC, [UnionAll1].[C1] ASC
И это кажется мне совершенно ненужным для такого простого запроса. Я что-то пропускаю в своих сопоставлениях или это предназначено?
Желательная загрузка двух разных зависимостей довольно сложна. Это безопасный способ сделать именно это, используя как можно меньше места - обратите внимание, что результаты являются null
для всех строк, которые не являются "включенной" частью.
Основная проблема заключается в том, что с двумя ассоциациями 1: * вам будет трудно отделить включенные объекты, которые из первой ассоциации связаны с второй. Та же проблема не возникает, если вы имеете дело с 1: (0-) 1, потому что результат будет просто отдельной записью.
Дело в том, что вы думаете об этом как о "простых отношениях от одного до многих". Если вы когда-либо пытались написать аналогичный запрос в SQL, вы должны знать, что 1: * на самом деле довольно сложно работать, особенно если вам нужно объединить много разных 1: * s в одном запросе. Теперь есть альтернативы (например, использование поля xml
в результирующем наборе), но это, вероятно, тот, который позволяет серверу делать столько оптимизаций, сколько может, и накладные расходы на самом деле будут на самом деле довольно небольшими на практике. что вы только получаете каждую часть данных один раз - остальное равно null
- почему первый запрос выполняет left join
(он должен возвращать родителя, даже если в этом отношении нет ничего), тогда как второй свободен делать inner join
(и не нужно выбирать какие-либо столбцы из родительского, за исключением первичного ключа). Это можно было бы улучшить, если бы был способ не возвращать родительские данные в каждой строке первого подзапроса, но это было легче сказать, чем сделать (и, вероятно, в конечном итоге будет намного медленнее).
В общем, это немного красноречивый запрос, но он делает то, что он должен делать. У вас проблемы с производительностью, или вам просто интересно?