Entity Framework и принудительное внутреннее соединение
У меня есть таблица1 со следующими отношениями (они не применяются, они только создают отношения для свойств навигации)
Table1 (*)->(1) Table2
Table1 (*)->(1) Table3
Table1 (*)->(1) Table4
Table1 (*)->(1) Table5
Использование загружаемого кода загрузки выглядит как
IQueryable<Table1> query = context.Table1s;
query = query.Include(Table1 => Table1.Table2);
query = query.Include(Table1 => Table1.Table3);
query = query.Include(Table1 => Table1.Table4);
query = query.Include(Table1 => Table1.Table5);
query = query.Where(row => row.Table1Id == table1Id);
query.Single();
Каждый способ, с помощью которого я пытаюсь организовать инструкции Include(), включает в себя первую таблицу Inner Join в ее сгенерированном TSQL, а остальные - Left Outer Join (я ожидаю, что Left Outer для всех). Я не разделяю Entity Split, они просто обычные таблицы с FK.
Если DefaultIfEmpty() является единственным решением, может ли кто-нибудь объяснить причину, почему, когда все, кроме первой таблицы, обеспечивают ожидаемый SQL?
Я понимаю, что поведение по умолчанию для свойства Navigation - LEFT OUTER, но я не могу получить ВСЕ свойства для создания значения по умолчанию.
Любая помощь была бы очень оценена.
Заранее благодарю вас!
----- Созданный TSQL (измененный для краткости, но структура тот же) -------
(@p__linq__0 int)SELECT
[Limit1].[Table1Id] AS [Table1Id],
[Limit1].[OtherData] AS [OtherData]
FROM ( SELECT TOP (2)
[Extent1].[Table1Id] AS [Table1Id],
[Extent1].[OtherData] As [OtherData]
FROM [dbo].[Table1] AS [Extent1]
INNER JOIN [dbo].[Table2] AS [Extent2] ON [Extent1].[Table2Id] = [Extent2].[Table2Id]
LEFT OUTER JOIN [dbo].[Table3] AS [Extent3] ON [Extent1].[Table3Id] = [Extent3].[Table3Id]
LEFT OUTER JOIN [dbo].[Table4] AS [Extent4] ON [Extent1].[Table4Id] = [Extent4].[Table4Id]
LEFT OUTER JOIN [dbo].[Table5] AS [Extent5] ON [Extent1].[Table5Id] = [Extent5].[Table5Id]
WHERE [Extent1].[Table1Id] = @p__linq__0
) AS [Limit1]
Ответы
Ответ 1
в EF при выполнении IQueryable.Include()
, если ни одно из свойств навигации не основано на принудительной связи, тогда EF будет использовать первую таблицу. Он ожидает, что по крайней мере одно из отношений будет применено в схеме и что сначала нужно закодировать с помощью IQueryable.Include()
, а затем добавить другие таблицы с помощью Include()
Ответ 2
EF, похоже, использует INNER JOIN
для включения требуемых и LEFT OUTER JOIN
для включения свойства необязательного навигации. Пример:
public class Order
{
public int Id { get; set; }
public string Details { get; set; }
public Customer Customer { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
}
Если я определяю Customer
как свойство required на Order
...
public class MyContext : DbContext
{
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasRequired(o => o.Customer)
.WithMany();
}
}
... и выдать этот запрос...
using (var ctx = new MyContext())
{
var result = ctx.Orders
.Include(o => o.Customer)
.Where(o => o.Details == "Peanuts")
.FirstOrDefault();
}
... Я получаю этот SQL:
SELECT TOP (1)
[Extent1].[Id] AS [Id],
[Extent1].[Details] AS [Details],
[Extent2].[Id] AS [Id1],
[Extent2].[Name] AS [Name]
FROM [dbo].[Orders] AS [Extent1]
INNER JOIN [dbo].[Customers] AS [Extent2]
ON [Extent1].[Customer_Id] = [Extent2].[Id]
WHERE N'Peanuts' = [Extent1].[Details]
Если я изменю конфигурацию модели .HasRequired(o => o.Customer)
на...
.HasOptional(o => o.Customer)
... Я получаю точно такой же запрос, за исключением того, что INNER JOIN [dbo].[Customers] AS [Extent2]
заменяется на:
LEFT OUTER JOIN [dbo].[Customers] AS [Extent2]
С точки зрения модели это имеет смысл, потому что вы говорите, что никогда не может быть Order
без Customer
, если вы определяете взаимосвязь как required. Если вы обойдете это требование, удалив принудительное исполнение в базе данных, и если у вас действительно есть заказы без клиента, вы нарушите свое определение модели.
Только решение может сделать связь необязательной, если у вас есть такая ситуация. Я не думаю, что можно управлять SQL, который создается при использовании Include
.
Ответ 3
Как заставить Entity Framework выполнять внутренние соединения, если у вас есть структура таблицы, такая что:
- У студентов может быть расписание, но не нужно
- Расписания могут иметь классы, но не должны
- Классы ДОЛЖНЫ иметь учебные планы
- Учебные планы ДОЛЖНЫ иметь тесты
Если вы хотите найти студентов, которые прошли конкретные тесты, вы логически сделаете что-то вроде:
var studentsWhoPassed = context.Set<StudentEntity>()
.Where(x => x.Something)
.Include(x => x.Schedules.Select(y => y.Classes.Select(z => z.Tests)))
.Etc().Etc()
Суть в том, что вы начинаете с StudentEntity и устанавливаете некоторые условия, основанные на объединениях по цепочке. Но из-за того, что Student to Schedule является необязательным, E.F. генерирует LEFT OUTER Joins.
Вместо этого вы должны начать снижать цепочку и наращивать ее. Например:
var studentsWhoPassed = context.Set<ClassEntity>()
.Where(class => class.Tests.Any(test => test.Status == Status.Passed)
&& class.Schedule.Student.Something == studentSomething)
.Include(class => class.Schedule.Student)
Странно начинать с класса, когда вы пытаетесь запросить учащихся с критериями проверки. Но это фактически упрощает LINQ.
Из-за того, что у ученика нет расписания, но... у класса должен быть Test (ы), а класс должен иметь ScheduleID, а в Schedules должен быть идентификатор StudentID, вы получаете Inner Joins all вокруг.
Конечно, этот школьный пример является абстрактным, но идея верна для других примеров, с которыми я работал с теми же типами отношений.