您可以通过多种方式实现您想要的查询,具体取决于您想要的结果:
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
查询的一般信息,请查看复杂查询运算符。