34

我正在将 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; }
    }
}

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

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

到商店查询。在我看来,这似乎是您可以尝试的最简单的 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.'

让它工作的唯一方法是在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;

不是这个:

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;

它仅适用于聚合函数,例如上面的计数。

SQL中类似的东西有效

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

但是,删除聚合函数,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 中问的问题

4

4 回答 4

3

当无法进行服务器端评估时,早期的 EF/EF 核心会自动转换为客户端查询评估。

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

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

您可以在此处阅读有关它的更多信息: https ://docs.microsoft.com/en-us/ef/core/what-is-new/ef-core-3.x/break-changes#linq-queries-are-不再对客户进行评估

于 2021-07-29T08:50:20.627 回答
-1

在我看来,问题在于,正如您在编辑中所说,您将一些应该可以工作的 LINQ 放在一起;但数据库提供商不支持。

不幸的是,EFCore 的工作方式似乎是有效的(我没有参与创建它,所以这纯粹基于观察);不是在服务器上操作可查询的对象(您正在使用的 LINQ 可以使用该对象),而是将 LINQ 转换为 SQL 并将其扔到数据库中(据我所知)。在团队更改 EFCore 功能之前,在我看来,您有两个选择:

使用 LINQ 或 T-SQL 在数据库提供者的规定范围内操作数据(即使用聚合规范)。

或者,使用 AsEnumerable 或等效块在 .NET 代码中创建数据的本地副本并对其进行操作。无论您是在客户端还是服务器端执行此操作取决于您(我建议通过 API 调用或类似的方式在服务器端执行此操作。

于 2021-08-13T13:48:05.667 回答
-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"

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

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()
    );
于 2021-01-23T22:09:47.597 回答
-3

只需按照异常消息所说的去做!您还需要将“var”更改为显式。

我有这个并且有与你一样的异常消息:

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

我改成这个了。测试并且工作正常。

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

所以基本上像我一样改变'var'。*这里的 int,IGrouping<int,...> 是 Grouping Key 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)
                    ...

为具有相同异常消息的任何人试一试。

于 2021-03-20T18:43:31.790 回答