【问题标题】:How to properly use nested queries in EntityFramework?如何在 EntityFramework 中正确使用嵌套查询?
【发布时间】:2020-06-04 05:22:32
【问题描述】:

在构建投票应用练习时,我正在使用 WebAPI 玩 EntityFrameworkCore

我希望尽可能以异步方式编写代码。

那么我是否必须以某种方式以异步方式使用嵌套查询(// 问题 1,// 问题 2)?

/* The target of the question - the query*/
var pollResults =
                await _context.Polls
                        .Select(poll => new PollDto
                        {
                            Id = poll.Id,
                            Question = poll.Question,
                            CreatedAt = poll.CreatedAt,
                            Options = poll.Options
                            .Select(option => new OptionDto
                            {
                                Id = option.Id,
                                Value = option.Value,
                                VotesCount = option.Votes.Count() // Problem 1
                            })
                            .ToList(), // Problem 2
                            LastVotedAt = _context.PollVotes.Where(vote=>vote.PollId == poll.Id).Select(vote => vote.VoteDate).SingleOrDefault()
                        })
                        .ToListAsync(); 

/* Domain classes */

public class Poll
    {
        public int Id { get; set; }
        public ICollection<PollOption> Options { get; set; } = new List<PollOption>();
        public ICollection<PollVote> Votes { get; set; } = new List<PollVote>();
    }

public class PollOption
    {
        public int Id { get; set; }
        public string Value { get; set; }
        public int PollId { get; set; }
        public Poll Poll { get; set; }
        public ICollection<PollVote> Votes { get; set; } = new List<PollVote>();
    }

 public class PollVote
    {
        public int Id { get; set; }
        public int PollId { get; set; }
        public Poll Poll { get; set; }
        public int OptionId { get; set; }
        public PollOption Option { get; set; }
        public DateTime VoteDate { get; set; }
    }

/* Dto classes */

public class PollDto
    {
        public int Id { get; set; }
        public string Question { get; set; }
        public ICollection<OptionDto> Options { get; set; } = new List<OptionDto>();
        public DateTime LastVotedAt { get; set; }
    }

public class OptionDto
    {
        public int Id { get; set; }
        public string Value { get; set; }
        public int VotesCount { get; set; }
    }

因此,在非嵌套查询中,Count 和 SingleOrDefault 会向数据库发出请求,并且应该以异步方式执行。但在我的情况下,整个查询是一个请求。

我应该修改一些东西以异步方式完成方法 Count 和 SingleOrDefault 吗?还是最后调用 ToListAsync 就够了?

我相信答案是对数据库的 1 个请求进入 1 个异步调用。但是我在互联网上没有找到任何解决方案。

【问题讨论】:

  • 在玩了几个小时后,我意识到:1. 调用 Count(来自问题 1)告诉 EF 如何对数据库进行查询。因此不会获取所有选票,而只是添加在数据库级别的 PollVotes 表上工作的 VotesCount; 2. 在我的应用程序级别调用 ToList(来自问题 2)处理已收到的数据。所以我的两个问题是不同的。如果我错了,请修理我。

标签: c# asp.net sql-server entity-framework entity-framework-core


【解决方案1】:

ToListAsync() 最后就够了。 EF 使用查询中的表达式来组成查询。它们不像 SQL 那样被“执行”为针对 DbSet 的独立语句。

例如当我运行类似的东西时:

var parents = await context.Parents
    .Select(x => new
    {
        x.ParentId,
        x.Name,
        Children = x.Children.Select(c => new { c.ChildId, c.Name }).ToList(),
        ChildCount = x.Children.Count()
    }).ToListAsync();

在测试中设置断点并运行探查器。该语句生成一条 SQL 语句:

SELECT 
    [Project2].[ParentId] AS [ParentId], 
    [Project2].[Name] AS [Name], 
    [Project2].[C2] AS [C1], 
    [Project2].[C1] AS [C2], 
    [Project2].[ChildId] AS [ChildId], 
    [Project2].[Name1] AS [Name1]
    FROM ( SELECT 
        [Project1].[ParentId] AS [ParentId], 
        [Project1].[Name] AS [Name], 
        [Extent3].[ChildId] AS [ChildId], 
        [Extent3].[Name] AS [Name1], 
        CASE WHEN ([Extent3].[ChildId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1], 
        [Project1].[C1] AS [C2]
        FROM   (SELECT 
            [Extent1].[ParentId] AS [ParentId], 
            [Extent1].[Name] AS [Name], 
            (SELECT 
                COUNT(1) AS [A1]
                FROM [dbo].[Children] AS [Extent2]
                WHERE [Extent1].[ParentId] = [Extent2].[ParentId]) AS [C1]
            FROM [dbo].[Parents] AS [Extent1] ) AS [Project1]
        LEFT OUTER JOIN [dbo].[Children] AS [Extent3] ON [Project1].[ParentId] = [Extent3].[ParentId]
    )  AS [Project2]
    ORDER BY [Project2].[ParentId] ASC, [Project2].[C1] ASC
go

您可能担心不会阻止 3 个查询。这是在查看相关记录的导航属性时。

在查看您的示例进行仔细检查时,我看到的更大的问题是这一行:

LastVotedAt = _context.PollVotes.Where(vote=>vote.PollId == poll.Id).Select(vote => vote.VoteDate).SingleOrDefault()

因为这将直接返回到上下文,而不是通过投票上的集合访问投票。但我也尝试过,但它仍然导致单个查询。

Children = x.Children.Select(c => new { c.ChildId, c.Name }).ToList(),
ChildCount = x.Children.Count(),
YoungestChild = context.Children.OrderBy(c=>c.BirthDate).Where(c=>c.ParentId == x.ParentId).FirstOrDefault()

在我的测试示例中,我返回上下文以检索父记录的最小子项​​,而不是子项导航属性。在这种情况下,它仍然作为 1 个查询执行。

对于此类问题,我绝对建议使用本地数据库创建一个 EF 实验沙箱项目,然后利用 SQL 分析器工具来观察正在生成的 SQL 语句以及它们何时执行。异步对于预计需要一段时间才能运行的查询很有用,但应谨慎使用,因为在用于每个琐碎查询时,它们会降低正在运行的查询的整体性能。

【讨论】:

  • 非常感谢。公认。我在几个小时内才意识到这一点,我只是加了赞扬。你告诉我我没有错。
猜你喜欢
  • 2018-12-20
  • 2020-06-18
  • 2022-11-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-27
相关资源
最近更新 更多