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 вокруг.

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