如何在实体框架核心中为多个多对一相关实体构建多个左连接查询
本文关键字:实体 构建 查询 连接 多对一 核心 框架 | 更新日期: 2023-09-27 18:01:49
已定义的模型和类
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<RootLink>()
.HasOne(link => link.Node)
.WithMany(node => node.RootLinks)
.HasForeignKey(link => link.NodeId);
modelBuilder.Entity<RootLink>()
.HasOne(link => link.RootNode)
.WithMany()
.HasForeignKey(rootLink => rootLink.RootNodeId)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<NodeLink>()
.HasOne(link => link.Node)
.WithMany(node => node.NodeLinks)
.HasForeignKey(link => link.NodeId);
modelBuilder.Entity<NodeLink>()
.HasOne(l => l.LinkedNode)
.WithMany()
.HasForeignKey(l => l.LinkedNodeId)
.OnDelete(DeleteBehavior.Restrict);
}
…
public class Node
{
public long Id { get; set; }
public ICollection<RootLink> RootLinks { get; set; }
public ICollection<NodeLink> NodeLinks { get; set; }
public int Value { get; set; }
}
public class NodeLink
{
public long Id { get; set; }
public long NodeId { get; set; }
public Node Node { get; set; }
public long LinkedNodeId { get; set; }
public Node LinkedNode { get; set; }
}
public class RootLink
{
public long Id { get; set; }
public long NodeId { get; set; }
public Node Node { get; set; }
public long RootNodeId { get; set; }
public Node RootNode { get; set; }
}
DB的填充方式如下:
var node1 = new Node();
var node2 = new Node();
var node3 = new Node();
node1.NodeLinks = new List<NodeLink>()
{
new NodeLink
{
Node = node1,
LinkedNode = node2
},
new NodeLink
{
Node = node3,
LinkedNode = node3
}
};
node1.RootLinks = new List<RootLink>
{
new RootLink {RootNode = node1},
new RootLink {RootNode = node3}
};
ctx.Nodes.AddRange(node1, node2, node3);
他们的问题是我如何用它查询节点nodellinks和rootLinks在一个查询使用EF核心?
在普通sql中,它看起来像这样:SELECT [node].[Id], [node].[Value], [rootLink].[Id], [rootLink].[NodeId], [rootLink].[RootNodeId]
FROM [Nodes] AS [node]
LEFT JOIN [RootLinks] AS [rootLink] ON [node].[Id] = [rootLink].[NodeId]
LEFT JOIN [NodeLinks] AS [nodeLink] ON [node].[Id] = [rootLink].[NodeId]
WHERE [node].[Id] in (NODE_ID_LIST)
ORDER BY [node].[Id]
使用ef i最终得到以下查询变量:
public static IEnumerable<Node> FindVariant1(TestDbContext ctx, params long[] nodeIdList)
{
return ctx.Nodes
.Include(node => node.NodeLinks)
.Include(node => node.RootLinks)
.Where(node => nodeIdList.Contains(node.Id)).ToList();
}
public static IEnumerable<Node> FindVariant2(TestDbContext ctx, params long[] nodeIdList)
{
return ctx.Nodes
.GroupJoin(ctx.RootLinks, node => node.Id, rootLink => rootLink.NodeId,
(node, rootLinks) => new {node, rootLinks})
.SelectMany(info => info.rootLinks.DefaultIfEmpty(), (info, rootLink) => new {info.node, rootLink})
.GroupJoin(ctx.NodeLinks, node => node.node.Id, nodeLink => nodeLink.NodeId,
(info, nodeLinks) => new {info.node, info.rootLink, nodeLinks})
.SelectMany(info => info.nodeLinks.DefaultIfEmpty(),
(info, nodeLink) => new {info.node, info.rootLink, nodeLink})
.Where(node => nodeIdList.Contains(node.node.Id)).ToList()
.Select(r => r.node);
}
都生成几个查询。
这个答案是基于
如果您有合适的索引,大多数情况下EXISTS都可以执行与JOIN相同。例外情况非常复杂子查询,通常使用EXISTS会更快。
如果JOIN键没有被索引,使用EXISTS可能会更快您需要针对您的具体情况进行测试。
所以,我假设带有EXISTS
的条款将是可接受的替代JOIN
。
我在WithOne
中使用lambda定义了模型,读取客户与订单的单元测试。
modelBuilder.Entity<RootLink>().ToTable("RootLinks");
modelBuilder.Entity<NodeLink>().ToTable("NodeLinks");
modelBuilder.Entity<Node>().HasKey(r => r.NodeId);
modelBuilder.Entity<Node>()
.HasMany(link => link.NodeLinks)
.WithOne(
l => l.Node
).HasForeignKey(l => l.NodeId);
modelBuilder.Entity<Node>()
.HasMany(link => link.RootLinks)
.WithOne(
l => l.Node
).HasForeignKey(l => l.NodeId);
我查询var test = ctx.Nodes
.Where(n => new long[] { 1, 2 }.Contains( n.NodeId))
.Include(c => c.NodeLinks)
.Include(c => c.RootLinks);
var myRes = test.ToList();
SQL(我还添加了一些无关的名称字段)
SELECT "n"."NodeId", "n"."Value"
FROM "Nodes" AS "n"
WHERE "n"."NodeId" IN (1, 2)
ORDER BY "n"."NodeId"Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT "r"."RootLinkId", "r"."NodeId", "r"."RootName"
FROM "RootLinks" AS "r"
WHERE EXISTS (
SELECT 1
FROM "Nodes" AS "n"
WHERE "n"."NodeId" IN (1, 2) AND ("r"."NodeId" = "n"."NodeId"))
ORDER BY "r"."NodeId"Executed DbCommand (0ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT "n0"."NodeLinkId", "n0"."LinkName", "n0"."NodeId"
FROM "NodeLinks" AS "n0"
WHERE EXISTS (
SELECT 1
FROM "Nodes" AS "n"
WHERE "n"."NodeId" IN (1, 2) AND ("n0"."NodeId" = "n"."NodeId"))
ORDER BY "n0"