【问题标题】:How to create an EF query which returns a nested list with navigation properties based on different entity ids?如何创建一个 EF 查询,该查询返回一个嵌套列表,其中包含基于不同实体 ID 的导航属性?
【发布时间】:2021-11-26 02:32:00
【问题描述】:

我有 3 个嵌套实体,TvShow、Season 和 Episodes:

public class TvShow
{
    public Id { get; set; }
    public List<Season> Seasons { get; set; }
}

public class Season
{
    public Id { get; set; }
    public int TvShowId { get; set; }
    public TvShow TvShow { get; set; }
    public List<Episode> Episodes { get; set; }
}

public class Episode
{
    public Id { get; set; }
    public int TvShowId { get; set; }
    public TvShow TvShow { get; set; }
    public int SeasonId { get; set; }
    public Season Season { get; set; }
}

我正在尝试编写一个查询,我可以在其中提供以下输入:

    public List<int> TvShowIds { get; }
    public List<int> TvShowSeasonIds { get; }
    public List<int> TvShowEpisodeIds { get; }

这是我卡住的地方,查询应该包括所有不同类型的 id 及其子导航。

例如对于每个TvShowId,它应该返回包含所有SeasonsEpisodesTvShow 实体。对于每个SeasonId,它应该包括其所有Episodes 及其父TvShow。对于每个EpisodeId,它应该包括其父SeasonTvShow,而不包括属于同一Season 的任何其他Episodes

那么所有这些都应该作为一个合并的(联合?)嵌套的List&lt;TvShow&gt;() 返回,没有任何重复。

我尝试了不同的方法,但每次都卡住了。这应该不难,也许我在思考过程中遗漏了什么?

非常感谢您的帮助!

【问题讨论】:

  • E.g. For every TvShowId, it should return the TvShow entity - 那么您想要ID 列表还是TvShow 实体 列表?或者您想知道如何从List&lt;int&gt; ID 中获取List&lt;TvShow
  • 这是三个不同的查询,而不是一个。
  • 第二个选项,我想得到一个 List ,它的导航属性基于 List Ids
  • @DavidBrowne-Microsoft,所以 3 个不同的查询然后使用联合?如果是这样,我将如何在不同的嵌套实体上使用联合?
  • Project TvShow 包含每个查询的相关实体,然后您可以将它们添加到单个列表中。

标签: c# entity-framework linq entity-framework-core


【解决方案1】:

假设我正确理解了您的问题,我怀疑 EF 核心是否能够翻译,除非它是在内存中完成的,但您可以试试这个:

await Db.TvShows
        .Where(x => TvShowIds.Contains(x.Id))
            .Select(x => { x.Seasons = x.Seasons.Where(y => TvShowSeasonsIds.Contains(y.Id))
                .Select(y => { y.Episodes = y.Episodes.Where(z => TvShowEpisodeIds.Contains(z.Id)).ToList(); return y; }).ToList(); return x; })
        .ToListAsync();

编辑,我刚刚在 .Net Core 3.1 中尝试了以下内容,它似乎有效。如果您需要相关实体向上,您可以添加到选择中。您可能也应该使用 DTO 而不是实际的 EF 核心实体Create Data Transfer Objects (DTOs)

await Db.TvShows
        .Where(x => TvShowIds.Contains(x.Id))
        .Select(x => new TvShow
         {
             Id = x.Id,
             Seasons = x.Seasons.Where(y => TvShowSeasonsIds.Contains(y.Id))
                                .Select(y => new Season
                                {
                                    Id = y.Id,
                                    TvShowId = y.TvShowId,
                                    Episodes = y.Episodes.Where(z => TvShowEpisodeIds.Contains(z.Id)).ToList(),
                                }).ToList()
         }).ToListAsync();

【讨论】:

  • 谢谢,我收到“无法将带有语句体的 lambda 表达式转换为表达式树”,但我认为您的方向正确
【解决方案2】:

鉴于您有(最多)3 个不同的过滤要求,这可能应该通过单独的查询来完成,因为您的代码需要处理一些警告条件。例如,我可能会从一个季节或节目中选择一个剧集 ID,而我已经为其指定了一个节目或季节 ID,在这种情况下,我不想加倍该节目。

那么所有这些都应该作为一个合并的(联合?)嵌套的 List() 返回,没有任何重复。

还有一种可能性是,对于给定的节目,我可能会通过季节过滤器选择它,比如巴比伦 5 的第 2 季,然后给出一个剧集过滤器,从第 4 季中选择第 3 集。从事物的声音中你会可能想在第 2 季的所有内容中检索巴比伦 5(节目),但也可以在第 3 集的第 4 季中检索。

因为我们可能需要选择由它们各自的剧集组成的多个季节,所以最好从剧集中进行攻击,然后从节目端重新组合视图,该视图应该仅列出所需的相应季节。

关于这种方法而不是投影的一个警告是,生成的实体将不再是数据的完整或可完整表示。通过仅使用所需剧集的子集选择节目和季节,节目和季节不再反映完整的剧集列表。如果将此实体传递给任何期望对 Show /w 季节和剧集的完整表示进行操作的方法,请小心。

List<Episodes> episodes = new List<Episode>();
IEnumerable<int> existingEpisodeIds = new List<int>();

if (filter.TvShowsIds.Any())
{
    episodes.AddRange(Context.Episodes
        .Include(x => x.Season)
            .ThenInclude(x => x.TvShow)
         .Where(x => filter.TvShowIds.Contains(x.Season.TvShow.Id))
         .ToList());
    existingEpisodeIds = episode.Select(x => x.id).ToList();
}
if (filter.SeasonIds.Any())
{
    episodes.AddRange(Context.Episodes
        .Include(x => x.Season)
            .ThenInclude(x => x.TvShow)
        .Where(x => !existingEpisodeIds.Contains(x.Id) 
            && filter.SeasonIds.Contains(x.Season.Id)
        .ToList());
    existingEpisodeIds = episodes.Select(x => x.id).ToList();
}
if (filter.Episodes.Any())
{
    episodes.AddRange(Context.Episodes
        .Include(x => x.Season)
            .ThenInclude(x => x.TvShow)
        .Where(x => !existingEpisodeIds.Contains(x.Id) 
            && filter.EpisodeIds.Contains(x.Id)))
         .ToList();
}

var shows = episodes.Select(x => x.Season.Show).Distinct().ToList();

通过从剧集中对此进行攻击并在该方向急切加载相应的季节和节目,相关季节将出现在每个选定的剧集中,就像电视节目一样。这意味着出现在结果节目中的唯一季节将是那些导致至少一集被选中的季节。我们跟踪选定的剧集 ID,并在随后的搜索中刷新它,以避免返回的剧集加倍。 (即第 2 季中的第 3 集,如果已选择所有第 2 季剧集,或该节目的所有剧集)

这种方法的注意事项是它假设所有节目都有季节并且所有季节都有剧集,我认为这是一个安全的假设。例如,如果您确实有一个没有季节或剧集的 TvShow Id #99,并且具有 99 的 TvShowId 过滤器,则不会返回它,因为我们正在查询剧集。

【讨论】:

  • 这正是我想要的!非常感谢您的广泛回答!我会尽快尝试,但我希望它能够奏效,因为您也概述了您的思考过程!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多