#c#-4.0 #linq-to-entities #entity-framework-4.1
#c #-4.0 #linq-to-entities #entity-framework-4.1
Вопрос:
У меня есть Table1 со следующими связями (они не применяются, они только создают связь для свойств навигации)
Таблица1 (*)-> (1) Таблица2 Таблица1 (*)-> (1) Таблица3 Таблица1 (*)-> (1) Таблица4 Таблица1 (*)-> (1) Таблица5
Использование кода с активной загрузкой выглядит следующим образом
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 ). Я не разделяю сущности, это просто простые таблицы с FKS.
Если DefaultIfEmpty() является единственным решением, может ли кто-нибудь объяснить причину, по которой, когда все, кроме первой включенной таблицы, предоставляют ожидаемый SQL?
Я понимаю, что поведение по умолчанию для свойства навигации остается ВНЕШНИМ, но я не могу получить ВСЕ свойства для создания значения по умолчанию.
Любая помощь будет высоко оценена.
Заранее благодарю вас!
—— Создан TSQL (изменен для краткости, но структура та же) ——-
(@p__linq__0 int)ВЫБЕРИТЕ [Ограничение1].[Table1Id] КАК [Table1Id], [Ограничение1].[OtherData] КАК [OtherData] ИЗ (ВЫБЕРИТЕ TOP (2) [Extent1].[Table1Id] КАК [Table1Id], [Extent1].[OtherData] Как [OtherData] ИЗ [dbo].[Table1] КАК [Extent1] ВНУТРЕННЕЕ СОЕДИНЕНИЕ [dbo].[Table2] КАК [Extent2] НА [Extent1].[Table2Id] = [Extent2].[Table2Id] ЛЕВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ [dbo].[Table3] КАК [Extent3] НА [Extent1].[Table3Id] = [Extent3].[Table3Id] ЛЕВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ [dbo].[Table4] КАК [Extent4] НА [Extent1].[Table4Id] = [Extent4].[Table4Id] ЛЕВОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ [dbo].[Table5] КАК [Extent5] НА [Extent1].[Table5Id] = [Extent5].[Table5Id] ГДЕ [Extent1] .[Table1Id] = @p__linq__0 ) КАК [Limit1]
Комментарии:
1. В чем проблема? Разве запрос не возвращает ожидаемый результат со всеми включенными объектами? Я получаю то же самое с двумя включениями: внутренним соединением и внешним соединением (и объединением обоих). Я никогда этого не замечал, но я не вижу неправильного результата.
2. Мы не применяем принудительное существование данных в Table2, поэтому внутреннее объединение приводит к пустому результату при поиске Table1Id с допустимым идентификатором. Ни в одной из таблиц не применяются принудительные отношения, поэтому внутреннее соединение при ЛЮБОЙ из ожидаемых загрузок вызовет «Последовательность не найдена», если только соответствующие данные не должны были быть помещены в запрос для объединенной таблицы.
3. Почему он выполняет выбор TOP 2? Это кажется странным.
4. @mattruma: ПРИ использовании всегда создается SELECT TOP 2
Single
(чтобы проверить, есть ли более 1 записи). Это нормально.5. Если вы не применяете данные к Table2, вы делаете это неправильно, потому что в вашем отношении указано, что каждая запись в Table1 должна иметь связанную запись в Table2.
Ответ №1:
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]
С точки зрения модели это имеет смысл, потому что вы говорите, что никогда не может быть an Order
без a Customer
, если вы определяете отношения по мере необходимости. Если вы обойдете это требование, удалив принудительное исполнение в базе данных, и если у вас действительно есть заказы без клиента, вы нарушаете свое собственное определение модели.
Единственное решение, вероятно, сделает отношения необязательными, если у вас такая ситуация. Я не думаю, что можно управлять SQL, который создается при использовании Include
.
Комментарии:
1. Slauma спасибо за пример. Жаль, что я сначала не использовал код.
Ответ №2:
в EF при выполнении IQueryable.Include()
, если ни одно из свойств навигации не основано на принудительном отношении, тогда EF будет использовать первую таблицу. Он ожидает, что по крайней мере одно из отношений будет применено в схеме и что одно из них должно быть закодировано с IQueryable.Include()
первым, а затем добавить другие таблицы с Include()
Комментарии:
1. Не могли бы вы дать ссылку на документацию, которая подтверждает это, пожалуйста?
Ответ №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 и вводите некоторые условия, основанные на объединениях по цепочке. Но поскольку учащийся для планирования является необязательным, E.F. генерирует ВНЕШНИЕ соединения СЛЕВА.
Вместо этого вам следует начать с более низкого уровня цепочки и наращивать. Например:
var studentsWhoPassed = context.Set<ClassEntity>()
.Where(class => class.Tests.Any(test => test.Status == Status.Passed)
amp;amp; class.Schedule.Student.Something == studentSomething)
.Include(class => class.Schedule.Student)
Странно начинать с класса, когда вы пытаетесь запросить учащихся с критериями тестирования. Но на самом деле это упрощает LINQ.
Поскольку у студента не должно быть расписания, но … у класса должны быть тесты, и у класса должен быть ScheduleId, а у расписаний должен быть StudentID, вы получаете внутренние соединения повсюду.
Конечно, этот школьный пример абстрактен, но идея верна другим примерам, с которыми я работал с теми же типами отношений.