EF Core Много-много где на внутренней коллекции LINQ - PullRequest
0 голосов
/ 08 ноября 2018

У меня есть ASP.NET Core Web API с EF Core 2.1.

Это мои (упрощенные) сущности.

public class Application
{
    public int Id { get; set; }
    public string Name { get; set; }    
    public ICollection<ApplicationDiscipline> ApplicationDisciplines { get; set; }
}

public class Discipline 
{
    public int Id { get; set; }
    public ICollection<ApplicationDiscipline> ApplicationDisciplines { get; set; }
    public ICollection<DisciplineTranslation> DisciplineTranslations { get; set; }
}

public class ApplicationDiscipline 
{
    public int ApplicationId { get; set; }
    public Application Application { get; set; }
    public int DisciplineId { get; set; }
    public Discipline Discipline { get; set; }
}

public class DisciplineTranslation {
    public int DisciplineId { get; set; }
    public Discipline Discipline { get; set; }
    public string TranslatedDisciplineName { get; set; }
    public string TranslatedDisciplineDescription { get; set; }
    public int LanguageId { get; set; }
    public Language { get; set; }
}

public class Language
{   
    public string Name { get; set; }
    public string Key { get; set; }
}

Таким образом, существует множество многих между Application и Discipline и один ко многим между Discipline и DisciplineTranslation.

Теперь я хочу сделать запрос к моей базе данных с двумя параметрами: ApplicationName и LanguageKey.

Я не могу понять, как я могу запросить правильное приложение с его дисциплинами и соответствующими дисциплинарными переводами. Единственное, что я придумал, это добавить [NotMapped] ICollection<Disciplines> Disciplines на Application и использовать этот запрос.

var application = await Table
    .Include(a => a.ApplicationDisciplines)
    .Where(x => x.Name.Equals(appName, StringComparison.InvariantCultureIgnoreCase))
    .Select(a => new Application()
    {
        Id = a.Id,
        Name = a.Name,
        Disciplines = a.ApplicationDisciplines
            .Select(ad => new Discipline()
            {
                Id = ad.Discipline.Id,
                DisciplineTranslations = ad.Discipline
                    .DisciplineTranslations
                    .Where(dt => dt.Language
                        .Key.Equals(languageKey, StringComparison.InvariantCultureIgnoreCase))
                        .ToList()
            })
    })
    .FirstOrDefaultAsync();

Это было бы намного проще без EF Core ApplicationDiscipline.

Какой запрос LINQ выполнит эту работу без добавления ICollection<Discipline> Disciplines к Application?

Ответы [ 2 ]

0 голосов
/ 09 ноября 2018

Используя Select(), я смог отфильтровать до DisciplineTranslations.

var application = await Table.Include(a => a.ApplicationDisciplines)
.ThenInclude(x => x.Discipline)
.Where(x => x.Name.Equals(appName, StringComparison.InvariantCultureIgnoreCase))
.Select(x => new Application(){
   Id = x.Id,
   Name = x.Name,
   ApplicationDisciplines = x.ApplicationDisciplines.Select(y => 
   new ApplicationDiscipline() {
       ApplicationId = y.ApplicationId,
       Application = y.Application,
       DisciplineId = y.Discipline.Id,
       Discipline = new Discipline() {
            Id = y.Discipline.Id
            DisciplineTranslations = y.Discipline.DisciplineTranslations.Where(z => z.Language.Key == languageKey).ToList()
       }        
   })
}).SingleOrDefaultAsync();

Используя SQL Server Profiler, мы заметили один недостаток. В нашем случае приложение имеет (к счастью, только) 4 Disciplines, поэтому EF Core выполняет 1 запрос, чтобы получить Application, и 4 запроса, чтобы получить DisciplineTranslations. Так что еще есть возможности для улучшения.

0 голосов
/ 08 ноября 2018

С точки зрения получения Application в качестве конечного результата, один вариант будет

Включите все - Не слишком здорово: это выглядело бы как var x = await Table.Include(a=> a.ApplicationDisciplines).ThenInclude(a=> a.Discipline).ThenInclude(a=> a.DisciplineTranslations).ThenInclude(a=> a.Language).Where( a=> a.Name == appName && a.Any(b=> b.Discipline.DisciplineTranslations.Any(c=> c.Language.Key == languageKey))).FirstOrDefaultAsync()

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

Я бы сказал, что это плохой подход, поскольку в целом он может привести к довольно медленному запросу.

Использование соединения: Возможно, лучшее решение в целом

var aThing = ApplicationDisciplineTable.Include(x=> x.Application).Include(x=> x.Discipline).Where(x=> x.Application.Name == appName).ToList().Join(DisciplineTranslationTable.Include(x=> x.Language).Where(x=> x.Language.Key == appKey), AppDis => AppDis.DisciplineId, DisTrans => DisTrans.DisciplineId, (AppDis, DisTrans) => new {whatever details you want in here}).FirstOrDefault();

Итак, соединение не выглядит супер чистым, вы МОЖЕТЕ выбрать только приложение в конце с точными деталями, которые вы хотите в нем, так как у вас есть все, что вам нужно. Но это использование соединения непосредственно на промежуточных объектах, которые были отфильтрованы, поэтому в итоге получится только одна дисциплина, приложение и язык, которые соответствуют критериям

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...