简体   繁体   中英

Manual Include in EF Core

I have the following structure: Training has many Module has many Phase has many Question .

I use the following query to get the above

Context.Trainings
       .Include(x => x.Modules)
       .ThenInclude(x => x.Phases)
       .ThenInclude(y => y.Questions)

Question also has many Comment but that relationship is not defined as navigation property because Comment can have different type of patents. So Comment just has a ParentId that is sometimes Question and sometimes other things.

My question is how do I modify the above query to, for every Question , count the child Comment from the Context.Comments and assign it to Question.CommentCount ? Kind of like a manual Include

In my head it's something like this

Context.Trainings
       .Include(x => x.Modules)
       .ThenInclude(x => x.Phases)
       .ThenInclude(y => y.Questions.Select(x=> new Question.Question {              
                Name = x.Name,
                Description = x.Description,                
                CommentCount = Context.Comments.Where(y=>y.ParentId == x.Id)                
            }));

But it seems you can't put projections in Include and I don't know how to think about this in another way.

With the entities set up such as ...

public class Training
{
    public int Id { get; set; }
    
    public ICollection<Module> Modules { get; set; }
}

public class Module
{
    public int Id { get; set; }

    public ICollection<Phase> Phases { get; set; }
}

public class Phase
{
    public int Id { get; set; }

    public ICollection<Question> Questions { get; set; }
}

public class Question
{
    public int Id { get; set; }

    [NotMapped]
    public int CommentCount { get; set; }
}

public class Comment
{
    public int Id { get; set; }

    public int ParentId { get; set; }
}

// DbContext
public DbSet<Training> Trainings { get; set; }
public DbSet<Module> Modules { get; set; }
public DbSet<Phase> Phases { get; set; }
public DbSet<Question> Questions { get; set; }
public DbSet<Comment> Comments { get; set; }

... it can be done in a single query, but it's quite messy.

// query all nested navigations using projections with extra data
var projected = await context.Trainings
    .Select(t => 
        new 
        { 
            Training = t, 
            Modules = t.Modules.Select(m => 
                new 
                { 
                    Module = m, 
                    Phases = m.Phases.Select(p => 
                        new 
                        { 
                            Phase = p, 
                            Questions = p.Questions.Select(q => 
                                new 
                                { 
                                    Question = q, 
                                    CommentCount = context.Comments.Count(c => c.ParentId == q.Id) 
                                }
                            )
                        }
                    )
                }
            )
        }
    )
    .ToListAsync();

// fixup by setting comment count from dto projection to "real" tracked entity
foreach (var q in projected.SelectMany(t => t.Modules).SelectMany(m => m.Phases).SelectMany(m => m.Questions))
{
    q.Question.CommentCount = q.CommentCount;
}

// thanks to ef core entity tracker this will still work
var trainings = projected.Select(p => p.Training);
var totalCommentCount = trainings.SelectMany(t => t.Modules).SelectMany(m => m.Phases).SelectMany(p => p.Questions).Sum(q => q.CommentCount);

final query

SELECT [t].[Id], [t0].[Id], [t0].[TrainingId], [t0].[Id0], [t0].[ModuleId], [t0].[Id00], [t0].[PhaseId], [t0].[c]
      FROM [Trainings] AS [t]
      LEFT JOIN (
          SELECT [m].[Id], [m].[TrainingId], [t1].[Id] AS [Id0], [t1].[ModuleId], [t1].[Id0] AS [Id00], [t1].[PhaseId], [t1].[c]
          FROM [Modules] AS [m]
          LEFT JOIN (
              SELECT [p].[Id], [p].[ModuleId], [q].[Id] AS [Id0], [q].[PhaseId], (
                  SELECT COUNT(*)
                  FROM [Comments] AS [c]
                  WHERE [c].[ParentId] = [q].[Id]) AS [c]
              FROM [Phases] AS [p]
              LEFT JOIN [Questions] AS [q] ON [p].[Id] = [q].[PhaseId]
          ) AS [t1] ON [m].[Id] = [t1].[ModuleId]
      ) AS [t0] ON [t].[Id] = [t0].[TrainingId]
      ORDER BY [t].[Id], [t0].[Id], [t0].[Id0]

As pointed out in comments, you could benefit from using TPH with real navigation collection back to comments from questions, and you should also probably use split query or multiple queries instead of joining it all up like this. But depending on use case, perhaps a single query might perform better for you.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM