Entity Framework (4.2) HasRequired приводит к неожиданному LEFT OUTER JOIN

Похоже, что Entity Framework (последняя версия от NuGet) может игнорировать конфигурацию HasRequired при создании соединений для свойств навигации, отличных от первого определенного.

Например, для объекта POCO (Person) со следующей конфигурацией:

var person = modelBuilder.Entity<Person>();
person.ToTable("The_Peoples");
person.HasKey(i => i.Id);
person.Property(i => i.Id).HasColumnName("the_people_id");
person.HasRequired(i => i.Address)
    .WithMany()
    .Map(map => map.MapKey("address_id"));
person.HasRequired(i => i.WorkPlace)
    .WithMany()
    .Map(map => map.MapKey("work_place_id"));

Я пытаюсь загрузить список людей со следующим запросом:

myContext.Set<People>()
    .Include(o => o.Address)
    .Include(o => o.WorkPlace);

Entity Framework генерирует следующий запрос:

FROM  [dbo].[The_Peoples] AS [Extent1]
INNER JOIN [dbo].[The_Addresses] AS [Extent2] ON [Extent1].[address_id] = [Extent2].[address_id]
LEFT OUTER JOIN [dbo].[The_Work_Places] AS [Extent3] ON [Extent1].[work_place_id] = [Extent3].[work_place_id]

Обратите внимание, что соединение с таблицей *The_Addresses* является внутренним соединением (как и ожидалось), однако последующее соединение с *The_Work_Places* является внешним соединением. Учитывая, что свойства Address и WorkPlace помечены как обязательные, я ожидаю, что оба соединения будут внутренними соединениями. Я также попытался пометить свойства Address и WorkPlace атрибутом Required, но это не дало никакого эффекта.

Это баг или я что-то неправильно настроил? Предложения?


person mindlessgoods    schedule 15.12.2011    source источник
comment
В вашей базе данных The_Peoples.work_place_id помечен как ненулевой? Если да, имеет ли значение, является ли соединение внешним или внутренним, поскольку столбец никогда не будет содержать пустых значений?   -  person danludwig    schedule 16.12.2011
comment
Если вы измените порядок включения, чтобы рабочее место было на первом месте, а адрес — на втором, получит ли адрес LOJ, а рабочее место — IJ?   -  person danludwig    schedule 17.12.2011
comment
Переключение порядка включений, похоже, не изменило поведение.   -  person mindlessgoods    schedule 17.12.2011


Ответы (3)


Конфигурация вашей модели верна, и я думаю, что это не ошибка, а поведение по дизайну, но я не могу точно сказать, какой дизайн. Я также видел этот SQL в таких запросах. Только несколько замечаний:

  • Запрос, который вы видите, не относится к EF 4.2. Это также произойдет для EF 4.1 и EF 4.0. Но нет для EF 1 (.NET 3.5). В EF 1 каждый Include, а также первый, был сопоставлен с LEFT OUTER JOIN, также для необходимых отношений.

  • Я думаю, нельзя сказать, что использование INNER JOIN "правильно" для требуемых свойств навигации, а LEFT OUTER JOIN неправильно. С точки зрения сопоставления не имеет значения, что вы используете, учитывая, что ограничения в базе данных правильно представляют отношения в модели. Для обязательного свойства навигации столбец FK в базе данных не должен иметь значение NULL, и в базе данных должно быть ограничение, которое обеспечивает, чтобы FK ссылался на существующую строку в целевой таблице. Если это так, каждый JOIN должен возвращать строку, независимо от того, используете ли вы INNER JOIN или LEFT OUTER JOIN.

  • Что произойдет, если модель и база данных «не синхронизированы» в отношении отношений? По сути, в обоих случаях происходит ерунда: если вы используете LEFT OUTER JOIN, а FK равен NULL в БД или ссылается на несуществующую строку, вы получите объект, в котором свойство навигации равно null, нарушая определение модели, что свойство является обязательным. . Использование INNER JOIN не лучше: вы вообще не получите никакой сущности, результат запроса будет как минимум таким же неправильным, как и результат с LEFT OUTER JOIN, если не хуже.

  • Итак, я думаю, что изменение в .NET 4 для использования INNER JOINs для некоторых Includes было сделано не потому, что SQL в EF 1 был неправильным, а для создания лучшего и более производительного SQL. Это изменение фактически внесло критическое изменение в том, что некоторые запросы теперь возвращали другие результаты, чем в EF 1: http://thedatafarm.com/blog/data-access/ef4-breaking-change-ef4-inner.-joins-affect-eager-loading-many-to-many/

  • #P6# <блочная цитата> #P7# #P8#

Извините, это больше историческая история, чем реальное объяснение, почему вы получаете INNER JOIN, а затем LEFT OUTER JOIN. Как уже говорилось, нет ничего неправильного в том, чтобы написать запрос таким образом, так как было бы неправильно использовать два INNER JOIN или два LEFT OUTER JOIN. Я предполагаю, что только команда EF может точно объяснить, почему ваш запрос создает этот конкретный SQL.

Я бы рекомендовал - если у вас нет серьезных проблем с производительностью - не беспокоиться об этом SQL (поскольку результат, который вы получаете, в конце концов, правильный) и продолжить. Если вам не нравится SQL, который создает EF, это приводит либо к написанию большого количества запросов на функции и изменения, либо к написанию большого количества необработанных SQL-запросов, либо к полному отказу от EF.

person Slauma    schedule 16.12.2011
comment
Отлично пишешь, спасибо за подробности. Вы правы в том, что с учетом ограничений ненулевого и внешнего ключа запросы будут давать одинаковые результаты независимо от типа соединения. В случае внешнего соединения это может привести к несколько менее производительному запросу, но это ни в коем случае не является препятствием для показа. - person mindlessgoods; 17.12.2011

Это похоже на шею, но сегодня у меня такая же проблема...

проблема с этим:

FROM  [dbo].[The_Peoples] AS [Extent1]
INNER JOIN [dbo].[The_Addresses] AS [Extent2] ON [Extent1].[address_id] = [Extent2].[address_id]
LEFT OUTER JOIN [dbo].[The_Work_Places] AS [Extent3] ON [Extent1].[work_place_id] = [Extent3].[work_place_id]

когда вы применяете фильтр, полученный из [dbo].[The_Peoples], вы указываете, что он должен применять фильтры к объединению, поэтому это занимает меньше времени, но мы обнаружили (вы можете запустить план запроса, чтобы увидеть эту проблему), что он делает присоединяется к полному содержимому таблиц, а затем применяет фильтр.. что требует много дополнительного времени.. в нашем случае это вызывает тайм-аут, запрос, который должен занимать от 1 до 3 секунд, занимал 1+ минута

person DarkChuky    schedule 23.10.2013

Попробуйте использовать WithRequiredDependent() вместо WithMany()

person Ivan Gomes    schedule 21.03.2019