0

这是我要完成的查询:

var contacts = await dbContext.Contacts
                .GroupBy(o => o.UserId)
                .Select(group => new
                {
                    UserId = group.Key,
                    Contacts = group.ToList()
                }).ToListAsync();

这是联系人实体:

[Table("Contacts")]
public class WAContact
{
    public int Id { get; set; }
    public string Phone { get; set; }
    public string Name { get; set; }
        
    [NotNull]
    public int UserId { get; set; }
    
    public WAUser User { get; set; }
    
}

此代码引发此异常:

.ToList()' 无法翻译。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。

我已经看到执行 ToList() 的示例可以毫无问题地检索组项目,但是,不知道我的代码中发生了什么。

PD 经过更多测试后,我注意到调用 First()、Last() 等时也出现同样的错误。但是例如 Count() 可以工作。奇怪的!

4

4 回答 4

0

这是问题。 联系人 = group.ToList()

为什么不更改代码

var grouprs= await dbContext.Contacts.Select(c=>c)
                .GroupBy(o => o.UserId);
           
于 2020-10-16T16:10:16.343 回答
0

您收到此异常是因为 EF 无法将 LINQ 转换为等效的 SQL。

将您的查询更改为此

// Load the Contact from the DB
var contacts = await dbContext.Contacts.ToListAsync();
// Perform the group by in memory
var userContacts = contacts
.GroupBy(o => o.UserId)
.Select(group => new
{
    UserId = group.Key,
    Contacts = group.Select(contact => new 
    {
      contact.Name,
      contact.Phone,
      contact.Id
    }).ToList()
}).ToList();

现在 EF 将能够将 LINQ 转换为正确的 SQL。

于 2020-10-16T16:13:57.630 回答
0

此查询不可转换为 SQL。我已经为此类错误写了小答案,您的查询位于列表顶部:LINQ to Database:如何正确分组实体和 GroupBy 限制

于 2020-10-16T16:46:21.243 回答
0

您可以通过多种方式实现您想要的查询,具体取决于您想要的结果:

A) 返回所有 WAContact 实体

因为每个实体都必须有一个 UserId,所以不需要实际查询 WAUsers 表:

var userIdsWithContactsWithoutJoin = context.Contacts
    .AsEnumerable()
    .GroupBy(c => c.UserId)
    .ToList();

代码只是执行 a SELECT,然后切换到客户端评估以将返回的数据分组到内存中:

SELECT `c`.`Id`, `c`.`Name`, `c`.`Phone`, `c`.`UserId`
FROM `Contacts` AS `c`

B) 仅返回具有相关 WAContact 实体的所有 WAUser Id(完整)

var userIdsWithContacts = context.Users
    .SelectMany(
        u => context.Contacts
            .Where(c => u.Id == c.UserId),
        (u, c) => new
        {
            c.UserId,
            Contact = c
        })
    .AsEnumerable()
    .GroupBy(j => j.UserId, j => j.Contact)
    .ToList();

代码首先执行INNER JOIN,然后切换到客户端评估以将返回的数据分组到内存中:

SELECT `c`.`UserId`, `c`.`Id`, `c`.`Name`, `c`.`Phone`
FROM `Users` AS `u`
INNER JOIN `Contacts` AS `c` ON `u`.`Id` = `c`.`UserId`

C) 返回所有 WAUser 实体(完整),有或没有相关的 WAContact 实体(完整)

var usersWithOrWithoutContacts = context.Users
    .SelectMany(
        u => context.Contacts
            .Where(c => u.Id == c.UserId)
            .DefaultIfEmpty(),
        (u, c) => new
        {
            User = u,
            Contact = c
        })
    .AsEnumerable()
    .GroupBy(j => j.User, j => j.Contact)
    .ToList();

代码首先执行 LEFT JOIN,然后切换到客户端评估以将返回的数据分组到内存中:

SELECT `u`.`Id`, `u`.`Name`, `c`.`Id`, `c`.`Name`, `c`.`Phone`, `c`.`UserId`
FROM `Users` AS `u`
LEFT JOIN `Contacts` AS `c` ON `u`.`Id` = `c`.`UserId`

所有三个查询都返回尽可能少的数据,然后用于AsEnumerable()切换到客户端评估以在内存中执行实际分组。


示例程序

这是一个完整的示例项目,它演示了查询(包括检查):

using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;

namespace IssueConsoleTemplate
{
    [Table("Contacts")]
    public class WAContact
    {
        public int Id { get; set; }
        public string Phone { get; set; }
        public string Name { get; set; }
        
        [NotNull]
        public int UserId { get; set; }
    
        public WAUser User { get; set; }
    }

    [Table("Users")]
    public class WAUser
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

    // 
    // DbContext:
    // 

    public class Context : DbContext
    {
        public DbSet<WAContact> Contacts { get; set; }
        public DbSet<WAUser> Users { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder
                .UseMySql(
                    "server=127.0.0.1;port=3306;user=root;password=;database=So64391764",
                    b => b.ServerVersion("8.0.21-mysql")
                          .CharSetBehavior(CharSetBehavior.NeverAppend))
                .UseLoggerFactory(
                    LoggerFactory.Create(
                        b => b
                            .AddConsole()
                            .AddFilter(level => level >= LogLevel.Information)))
                .EnableSensitiveDataLogging()
                .EnableDetailedErrors();
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<WAUser>()
                .HasData(
                    new WAUser {Id = 1, Name = "John"},
                    new WAUser {Id = 2, Name = "Jane"},
                    new WAUser {Id = 3, Name = "Mike"});

            modelBuilder.Entity<WAContact>()
                .HasData(
                    new WAContact {Id = 11, Name = "John's First Contact", Phone = "12345", UserId = 1},
                    new WAContact {Id = 12, Name = "John's Second Contact", Phone = "23456", UserId = 1},
                    new WAContact {Id = 21, Name = "Jane's Only Contact", Phone = "09876", UserId = 2});
        }
    }

    internal class Program
    {
        private static void Main()
        {
            using var context = new Context();

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();
            
            // Return all WAContact entities. Because every entity must have a UserId,
            // there is no need to actually query the WAUsers table.
            // Just performs a SELECT, then switches to client-evaluation to group the returned
            // data in memory:
            //     SELECT `c`.`Id`, `c`.`Name`, `c`.`Phone`, `c`.`UserId`
            //     FROM `Contacts` AS `c`

            var userIdsWithContactsWithoutJoin = context.Contacts
                .AsEnumerable()
                .GroupBy(c => c.UserId)
                .ToList();

            Debug.Assert(userIdsWithContactsWithoutJoin.Count == 2);
            Debug.Assert(userIdsWithContactsWithoutJoin[0].Key == 1);
            Debug.Assert(userIdsWithContactsWithoutJoin[0].Count() == 2);
            Debug.Assert(userIdsWithContactsWithoutJoin[0].First().Name == "John's First Contact");

            // Return all WAUser Ids only with related WAContact entities (full).
            // First performs an INNER JOIN, then switches to client-evaluation to group the
            // returned data in memory:
            //     SELECT `c`.`UserId`, `c`.`Id`, `c`.`Name`, `c`.`Phone`
            //     FROM `Users` AS `u`
            //     INNER JOIN `Contacts` AS `c` ON `u`.`Id` = `c`.`UserId`

            var userIdsWithContacts = context.Users
                .SelectMany(
                    u => context.Contacts
                        .Where(c => u.Id == c.UserId),
                    (u, c) => new
                    {
                        c.UserId,
                        Contact = c
                    })
                .AsEnumerable()
                .GroupBy(j => j.UserId, j => j.Contact)
                .ToList();

            Debug.Assert(userIdsWithContacts.Count == 2);
            Debug.Assert(userIdsWithContacts[0].Key == 1);
            Debug.Assert(userIdsWithContacts[0].Count() == 2);
            Debug.Assert(userIdsWithContacts[0].First().Name == "John's First Contact");

            // Return all WAUser entities (full) with or without related WAContact entities (full).
            // First performs a LEFT JOIN, then switches to client-evaluation to group the returned
            // data in memory:
            //     SELECT `u`.`Id`, `u`.`Name`, `c`.`Id`, `c`.`Name`, `c`.`Phone`, `c`.`UserId`
            //     FROM `Users` AS `u`
            //     LEFT JOIN `Contacts` AS `c` ON `u`.`Id` = `c`.`UserId`

            var usersWithOrWithoutContacts = context.Users
                .SelectMany(
                    u => context.Contacts
                        .Where(c => u.Id == c.UserId)
                        .DefaultIfEmpty(),
                    (u, c) => new
                    {
                        User = u,
                        Contact = c
                    })
                .AsEnumerable()
                .GroupBy(j => j.User, j => j.Contact)
                .ToList();

            Debug.Assert(usersWithOrWithoutContacts.Count == 3);
            Debug.Assert(usersWithOrWithoutContacts[0].Key.Name == "John");
            Debug.Assert(usersWithOrWithoutContacts[0].Count() == 2);
            Debug.Assert(usersWithOrWithoutContacts[0].First().Name == "John's First Contact");
        }
    }
}

您也可以运行这个.NET Fiddle(但使用 SQL Server 而不是 MySQL)。


更多的信息

有关GROUP BY查询的一般信息,请查看复杂查询运算符

于 2020-10-17T19:52:48.910 回答