EF Core 3.1 / EF Core 5.0 中的 GroupBy 不起作用,即使是最简单的例子

Ian*_*son 31 c# entity-framework group-by ef-core-3.1 ef-core-5.0

我正在将 EF6.x 项目更新到 EF Core 3.1。决定回归基础并再次遵循如何从头开始建立关系的示例。

根据微软官方文档EF Core Relationship Examples,我将示例翻译成下面的控制台应用程序:

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace BlogPostsExample
{
    class Program
    {
        async static Task Main(string[] args)
        {
            // SQL Running in a Docker container - update as required
            var conString = "data source=localhost,14330;initial catalog=BlogsDb;persist security info=True;user id=sa;password=<Your super secure SA password>;MultipleActiveResultSets=True;App=EntityFramework;";

            var ctx = new MyContext(conString);

            await ctx.Database.EnsureCreatedAsync();

            var result = await ctx.Posts.GroupBy(p => p.Blog).ToArrayAsync();

        }
    }

    class MyContext : DbContext
    {
        private readonly string _connectionString;

        public MyContext(string connectionString)
        {
            _connectionString = connectionString;
        }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder
                .UseSqlServer(_connectionString);
            }
        }
        public DbSet<Blog> Blogs { get; set; }
        public DbSet<Post> Posts { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {

            modelBuilder.Entity<Post>()
            .HasOne(p => p.Blog)
            .WithMany(b => b.Posts)
            .HasForeignKey(p => p.BlogId) //Tried with and without these keys defined.
            .HasPrincipalKey(b => b.BlogId);
        }

    }
    public class Blog
    {
        public int BlogId { get; set; }
        public string Url { get; set; }

        public List<Post> Posts { get; set; }
    }

    public class Post
    {
        public int PostId { get; set; }
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public Blog Blog { get; set; }
    }
}

Run Code Online (Sandbox Code Playgroud)

数据库中没有数据。EF Core 无法转换

ctx.Posts.GroupBy(p => p.Blog)  
Run Code Online (Sandbox Code Playgroud)

到商店查询。在我看来,这是您可以尝试的 GroupBy 的最简单示例。

当您运行此代码时,您会收到以下异常:

System.InvalidOperationException: 'The LINQ expression 'DbSet<Post>
    .Join(
        outer: DbSet<Blog>, 
        inner: p => EF.Property<Nullable<int>>(p, "BlogId"), 
        outerKeySelector: b => EF.Property<Nullable<int>>(b, "BlogId"), 
        innerKeySelector: (o, i) => new TransparentIdentifier<Post, Blog>(
            Outer = o, 
            Inner = i
        ))
    .GroupBy(
        source: p => p.Inner, 
        keySelector: p => p.Outer)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'
Run Code Online (Sandbox Code Playgroud)

让它工作的唯一方法是在GroupBy.

从性能的角度来看,这显然不是很好,它将分组操作变成了客户端操作,您确实希望在服务器端进行分组。

我错过了一些非常明显的东西吗?我很难相信 EF Core 无法完成 EF 框架从第一天起就一直在做的最简单的组。这似乎是任何数据驱动应用程序的基本要求?(或任何具有少量数据的应用程序!)

更新: 在此处输入图片说明

添加属性,例如相关博客的主键,没有任何区别。

更新 2:

如果您按照这篇 JetBrains 文章,您可以这样做:

var ctx = new EntertainmentDbContext(conString);
await ctx.Database.EnsureCreatedAsync();

var dataTask = ctx
                .Ratings
                .GroupBy(x => x.Source)
                .Select(x => new {Source = x.Key, Count = x.Count()})
                .OrderByDescending(x => x.Count)
                .ToListAsync();

var data = await dataTask;
Run Code Online (Sandbox Code Playgroud)

不是这个:

var ctx = new EntertainmentDbContext(conString);
await ctx.Database.EnsureCreatedAsync();

var dataTask = ctx
                .Ratings
                .GroupBy(x => x.Source)
                // .Select(x => new {Source = x.Key, Count = x.Count()})
                // .OrderByDescending(x => x.Count)
                .ToListAsync();

var data = await dataTask;
Run Code Online (Sandbox Code Playgroud)

它只适用于聚合函数,例如上面的 Count。

SQL 中类似的东西

SELECT COUNT(R.Id), R.Source
FROM 
    [EntertainmentDb].[dbo].[Ratings] R
GROUP BY R.Source
Run Code Online (Sandbox Code Playgroud)

但是,删除聚合函数, COUNT 不会,您会收到类似于以下内容的消息:

Column 'EntertainmentDb.dbo.Ratings.Id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.

所以看起来我正在尝试向 EF Core 提出一个我无法在 TSQL 中提出的问题

小智 5

当服务器端评估不可能时,早期的 EF/EF 核心会自动转换为客户端查询评估。

SQL 不支持按键而不选择 select 进行分组,并且始终是客户端操作。

通过 EF 3.0+,他们明确表明哪个查询应在服务器或客户端上运行。从技术上讲,最好明确地知道哪个查询将在服务器上运行以及什么查询将在客户端上运行,而不是由框架代表我们决定。

您可以在这里阅读更多相关信息: https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/writing-changes#linq-queries-are-不再在客户端进行评估


Lac*_*tah -2

我认为这可能是 GroupBy 的不恰当使用,因为您实际上并未按新内容进行分组或聚合数据,而是使用现有关系并选择博客并包含帖子。

注意:未经测试的代码

var blogs = ctx.Blogs.Include(x => x.Posts);
// Optional filters.
var blogsWithPosts = ctx.Blogs
    .Include(x => x.Posts)
    .Where(x => x.Posts.Any())  // Only blogs with posts
    .Where(x => x.Posts.Any(y => y.Title == "A Specific Title")) // Only blogs with posts that have the title "A Specific Title"
Run Code Online (Sandbox Code Playgroud)

如果您只需要包含博客帖子的子集,您也可以这样做。

var blogsAndMathingPosts = ctx.Blogs
    .Where(x => x.Posts.Any(y => y.Title == "A Specific Title")) // Only blogs that have at least one post with "A Specific Title"
    .Select(x => new Blog() {
        BlogId = x.BlogId,
        Url = x.Url,
        Posts = ctx.Posts.Where(y => y.BlogId == x.BlogId && y.Title == "A Specific Title").ToList()
    );
Run Code Online (Sandbox Code Playgroud)