Entity Framework и принудительное внутреннее соединение - PullRequest
10 голосов
/ 03 октября 2011

У меня есть Таблица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 (), первая включенная таблица имеет внутреннее соединение в своем сгенерированном TSQL, а остальные - Left Outer Join (я ожидаю Left Outer для всех них).Я не Entity Splitting, это просто простые таблицы с FK.

Если DefaultIfEmpty () - единственное решение, может кто-нибудь объяснить причину, почему, когда все, кроме первой включенной таблицы, предоставляют ожидаемый SQL?

Насколько я понимаю, поведение по умолчанию для свойства навигации - ВЛЕВО НАРУЖНО, но я не могу получить ВСЕ свойства для создания значения по умолчанию.

Любая помощь будет ОЧЕНЬ благодарна.

Заранее спасибо!

----- Создан 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]

Ответы [ 3 ]

20 голосов
/ 04 октября 2011

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 как обязательное свойство для 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, если вы определяете отношение как обязательно .Если вы обойдете это требование, удалив принудительное применение в базе данных, и если у вас действительно есть заказы без клиента, вы нарушите собственное определение модели.

Только решение может сделать отношение необязательным если у вас есть такая ситуация.Я не думаю, что можно контролировать SQL, который создается при использовании Include.

3 голосов
/ 09 ноября 2012

в EF при выполнении IQueryable.Include(), если ни одно из свойств навигации не основано на принудительном отношении, тогда EF будет использовать первую таблицу. Ожидается, что по крайней мере одно из отношений введено в схему, и что сначала нужно закодировать с IQueryable.Include(), затем добавить другие таблицы с Include()

0 голосов
/ 17 января 2017

Как заставить 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 и вводите некоторые условия, основанные на соединениях по цепочке. Но поскольку учиться по расписанию необязательно, E.F. генерирует левые внешние соединения.

Вместо этого вы должны начать опускаться вниз по цепочке и наращивать. Например:

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.

Из-за того, что у Ученика нет необходимости иметь Расписание, но ... Класс должен иметь Тест (ы), а у Класса должен быть ScheduleID, а у Расписаний должен быть StudentID, вы получаете Внутренние Присоединения ко наоборот.

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

...