EF Core 2.1, заставить SelectMany произвести левое соединение - PullRequest
0 голосов
/ 02 октября 2018

Предположим, мы имеем простое one-> one-> many отношение со следующей структурой таблиц:

public class City 
{
  public string Name { get; set; }

  [Column("DtaCentralSchoolId")]
  [ForeignKey("MyCentralSchool")]
  public int? CentralSchoolId { get; set; }

  public CentralSchool MyCentralSchool { get; set; }
}

public class CentralSchool
{
  public string Name { get; set; }

  [InverseProperty("MyCentralSchool")]
  public virtual IList<Student> MyStudents { get; set; }
}

public class Student 
{
  public string Name { get; set; }

  [Column("DtaCentralSchoolId")]
  [ForeignKey("MyCentralSchool")]
  public int? CentralSchoolId { get; set; }

  public CentralSchool MyCentralSchool { get; set; }
}

И пытаемся выполнить следующий запрос:

var result = await dbContext.Set<City>()
            .AsNoTracking()
            .SelectMany(x => x.MyCentralSchool.MyStudents.DefaultIfEmpty(), (c, s) => new {City = c, Student = s})
            .Where(x => x.Student == null || !x.Student.IsDeleted && x.Student.MyStoreId == storeId)
            .FirstOrDefaultAsync();

Так по какой-то причине для CentralSchool INNER JOIN создается, в то время как для Student есть LEFT JOIN , что вполне нормально, поскольку DefaultIfEmpty() используется.На самом деле я ожидаю LEFT JOIN и для CentralSchool, поэтому, когда CentralSchool не будет, некоторые строки все равно будут в результате, как я могу добиться этого в текущей конструкции, не переписывая некрасивый запрос вручную и не заставляя LEFTПРИСОЕДИНИТЬСЯ , чтобы появиться?

ОБНОВЛЕНИЕ
Проблема была решена, исправление будет выпущено в 2.2 : https://github.com/aspnet/EntityFrameworkCore/issues/13511

1 Ответ

0 голосов
/ 03 октября 2018

Одна вещь выделяется и должна быть проверена в реальном коде:

.Where(x => x.Student == null || !x.Student.IsDeleted && x.Student.MyStoreId == storeId)

Если это будет:

.Where(x => x.Student == null || (!x.Student.IsDeleted && x.Student.MyStoreId == storeId))

Свободные условия, подобные этому, могут привести к срабатыванию EF при условииx.Student.MyStoreId независимо от того, есть ученик или нет, что приводит к условию внутреннего соединения.

Редактировать: я пытался воспроизвести эту проблему, и с моей схемой запрос не присоединяется в City to Central School,Вместо этого он присоединяет город к студенческому флакону CentralSchoolId FKs.Я подозреваю, что проблема с вашей ситуацией заключается в том, что в базе данных не определены FK?База данных настроена с помощью кода сначала + миграции или сначала базы данных?

Результирующий запрос:

SELECT TOP (1) 
    [Extent1].[CityId] AS [CityId], 
    [Extent1].[Name] AS [Name], 
    [Extent1].[CentralSchoolId] AS [CentralSchoolId], 
    [Extent2].[StudentId] AS [StudentId], 
    [Extent2].[Name] AS [Name1], 
    [Extent2].[IsDeleted] AS [IsDeleted], 
    [Extent2].[CentralSchoolId] AS [CentralSchoolId1]
    FROM  [dbo].[Cities] AS [Extent1]
    LEFT OUTER JOIN [dbo].[Students2] AS [Extent2] ON [Extent2].[CentralSchoolId] = [Extent1].[CentralSchoolId]
    WHERE ([Extent2].[StudentId] IS NULL) OR ([Extent2].[IsDeleted] <> 1)

Примечание. В моем случае я не отображал StoreId в Student, простоIsDeleted.Кроме того, имя таблицы было Student2 только из-за конфликта имен в моей существующей базе данных области тестирования.

Определения сущностей были идентичны вашим, за исключением сопоставленных PK и добавления IsDeleted в Student.

public class City
{
    [Key]
    public int CityId { get; set; }
    public string Name { get; set; }

    [ForeignKey("MyCentralSchool")]
    public int? CentralSchoolId { get; set; }

    public virtual CentralSchool MyCentralSchool { get; set; }
}

public class CentralSchool
{
    [Key]
    public int CentralSchoolId { get; set; }
    public string Name { get; set; }

    [InverseProperty("MyCentralSchool")]
    public virtual IList<Student> MyStudents { get; set; }
}
[Table("Students2")]
public class Student
{
    [Key]
    public int StudentId { get; set; }
    public string Name { get; set; }

    public bool IsDeleted { get; set; }
    [ForeignKey("MyCentralSchool")]
    public int? CentralSchoolId { get; set; }

    public virtual CentralSchool MyCentralSchool { get; set; }
}

Выполнение тестового выражения:

var result = context.Set<City>()
    .AsNoTracking()
    .SelectMany(x => x.MyCentralSchool.MyStudents.DefaultIfEmpty(), (c, s) => new { City = c, Student = s })
    .Where(x => x.Student == null || !x.Student.IsDeleted)
    .FirstOrDefault();

Я также запустил Async, и был сгенерирован тот же запрос.Запуск с EF6 для SQL Server.

Редактировать 2: Подтверждено, что существует разница в генерации запросов между EF6 и EF Core.EF Core создает внутреннее соединение между Городом и Центральной школой при разрешении отношений между Городом и Студентом, где EF 6 оптимизирует это, объединяя таблицы через общий FK.Я бы посоветовал поднять это как потенциальную ошибку в EF Core.

Учитывая, что вы хотите получить список всех активных студентов со своими ассоциированными студентами, а также включить все города, в которых нет активных студентов (поэтому будут перечислены все города).а также)

Обходное решение, хотя и некрасивое, для возврата результатов сопоставления в EF Core:

var result2 = context.Set<City>()
    .AsNoTracking()
    .SelectMany(x => x.MyCentralSchool.MyStudents.DefaultIfEmpty(), (c, s) => new { City = c, Student = s })
    .Where(x => !x.Student.IsDeleted)
    .Union(context.Set<City>().AsNoTracking().Where(x => x.MyCentralSchool == null || !x.MyCentralSchool.MyStudents.Any(s => !s.IsDeleted))
        .Select(x => new { City = x, Student = (Student)null }))
    .ToList();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...