简体   繁体   English

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

[英]GroupBy in EF Core 3.1 / EF Core 5.0 not working, even for the simplest example

I'm updating an EF6.x project to EF Core 3.1.我正在将 EF6.x 项目更新到 EF Core 3.1。 Decided to go back to basics and follow the example of how to set up relationships from scratch again.决定回归基础并再次遵循如何从头开始建立关系的示例。

According to the official Microsoft documentation, EF Core Relationship Examples , I translated the examples into a console app below:根据微软官方文档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; }
    }
}

There is no data in the DB.数据库中没有数据。 EF Core fails to convert EF Core 无法转换

ctx.Posts.GroupBy(p => p.Blog)  

to a store query.到商店查询。 This appears to me to be the simplest example of a GroupBy you could try.在我看来,这是您可以尝试的 GroupBy 的最简单示例。

When you run this code you get the following exception:当您运行此代码时,您会收到以下异常:

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.'

The only way to get this to work is to add something like AsEnumerable() before the GroupBy .让它工作的唯一方法是在GroupBy之前添加类似 AsEnumerable() 的东西。

This is clearly not great from a performance point of view, it turns the group by operation into a client side operation where you really want to be doing grouping on the server side.从性能的角度来看,这显然不是很好,它将分组操作变成了客户端操作,您确实希望在服务器端进行分组。

Have I missed something glaringly obvious?我错过了一些非常明显的东西吗? I struggle to believe that EF Core can't do the simplest group by that EF Framework has been doing since day 1. This seems like a fundamental requirement of any data driven app?我很难相信 EF Core 无法完成 EF 框架从第一天起就一直在做的最简单的组。这似乎是任何数据驱动应用程序的基本要求? (Or any app with a modicum of data!) (或任何具有少量数据的应用程序!)

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

Adding a property, such as the Primary Key for the Blog in question makes no difference.添加属性,例如相关博客的主键,没有任何区别。

Update 2:更新 2:

If you follow this JetBrains article , you can do this:如果您按照这篇 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;

But NOT this:不是这个:

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;

It only works with with an aggregating function, eg Count as above.它只适用于聚合函数,例如上面的 Count。

Something similar in SQL works SQL 中类似的东西

SELECT COUNT(R.Id), R.Source
FROM 
    [EntertainmentDb].[dbo].[Ratings] R
GROUP BY R.Source

But, removing the aggregating function, COUNT does not, you receive messages similar to:但是,删除聚合函数, 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.

So it looks like I am trying to ask EF Core a question that I cannot ask in TSQL所以看起来我正在尝试向 EF Core 提出一个我无法在 TSQL 中提出的问题

Earlier EF/EF core automatically converted to client-side query evaluation when server-side evaluation was not possible.当无法进行服务器端评估时,早期的 EF/EF 核心会自动转换为客户端查询评估。

Grouping by a key without select is not something supported by SQL and would always have been a client-side operation. SQL 不支持按键分组而不选择选择,并且始终是客户端操作。

With EF 3.0+, they made it explicit on which query should run on server or on client.在 EF 3.0+ 中,他们明确说明了哪个查询应该在服务器上或客户端上运行。 Technically it is better to explicitly know which query will run on server and what will run on client rather than a framework deciding it on our behalf.从技术上讲,最好明确知道哪个查询将在服务器上运行,什么将在客户端运行,而不是由框架代表我们决定它。

You can read more about it here: https://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/breaking-changes#linq-queries-are-no-longer-evaluated-on-the-client您可以在此处阅读更多相关信息: https : //docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/break-changes#linq-queries-are-不再由客户评估

I think this may be an inappropriate use of GroupBy since you're not actually grouping by something new or aggregating data, instead use the existing relationship and select the Blogs and include posts.我认为这可能是 GroupBy 的不当使用,因为您实际上并未按新的或聚合数据进行分组,而是使用现有关系并选择博客并包含帖子。

Note: Untested Code注意:未经测试的代码

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"

If you need to include only a subset of a blogs posts, you can do that too.如果您只需要包含博客帖子的一个子集,您也可以这样做。

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()
    );

Just do exactly what exception message says!只需按照异常消息所说的去做! You'll need to change 'var' to explicit as well.您还需要将“var”更改为显式。

I had this and had same exception message as you got:我有这个并且有与您相同的异常消息:

var GroupByM2S =
            dbContext.CatL1s
           .GroupBy(x => x.UserId);   

I changed to this.我改成了这个。 Tested and works fine.经测试,工作正常。

IEnumerable<IGrouping<int, CatL1>> MsGrpByAsEnumerExplicit =              
            (dbContext.CatL1s).AsEnumerable()
           .GroupBy(x => x.UserId);

So basically change 'var' as I have.所以基本上像我一样改变'var'。 *The int here, IGrouping<int,...> is data type of your Grouping Key Prop/Col Then surround dbContext.EntityName with pars and then .AsEnumerable().GroupBy(...) *这里的int,IGrouping<int,...>是你的分组键Prop/Col的数据类型然后用pars包围dbContext.EntityName,然后用.AsEnumerable().GroupBy(...)

IEnumerable<IGrouping<dataTypeOfGpByKey, EntityName>> GrpByIEnumAsEnumExplicit =
        ( //<--Open Par dbCtx.EntityName
    .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
            )).AsEnumerable() //<-- Put here
                    .GroupBy(
                    source: p => p.Inner,
                    keySelector: p => p.Outer)
                    ...

Give that a shot for anyone with same exception message.给任何有相同异常消息的人一个机会。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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