【问题标题】:How to build several left join query in Entity Framework Core for several many to one related entities如何在 Entity Framework Core 中为多个多对一相关实体构建多个左连接查询
【发布时间】:2016-08-09 22:53:04
【问题描述】:

定义的模型和类

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

问题是如何使用 EF 核心在一个查询中使用它查询节点 nodeLinks 和 rootLinks?

在普通的 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);
    }

两者都会生成多个查询。

【问题讨论】:

  • 好问题。我正在查看.Includes 的第一个变体,并尝试稍微改变模型(仍然没有成功,直到现在)。

标签: c# left-join entity-framework-core


【解决方案1】:

这个答案是基于fact

如果你有正确的索引,大多数时候 EXISTS 会执行 与 JOIN 相同。异常非常复杂 子查询,通常使用 EXISTS 更快。

如果您的 JOIN 键未编入索引,则使用 E​​XISTS 可能会更快,但 您需要针对您的具体情况进行测试。

所以,我假设带有EXISTS 的子句将是JOIN可接受替换。

我在WithOne 中使用lambda 定义了模型,读取this 客户与订单的单元测试。

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"

【讨论】:

  • 使用与您的答案相同的查询执行初始模型。这就是问题所在 - 3 个单独的查询。我使用 SQL Server,当我通过 SQL Profiler 嗅探命令时,我看到执行了 3 个批处理命令(每个查询一个)。这意味着对 SQL Server 有 3 个单独的请求。即使记住连接池 - 这看起来不是最佳方式。
  • 顺便说一句,在我添加“.OnDelete(DeleteBehavior.Restrict);”之前,我无法用你的模型更新数据库。在每个 FK 声明上。错误消息是“在表 'RootLinks' 上引入 FOREIGN KEY 约束 'FK_RootLinks_Nodes_RootNodeId' 可能会导致循环或多个级联路径。指定 ON DELETE NO ACTION 或 ON UPDATE NO ACTION,或修改其他 FOREIGN KEY 约束。”
  • 至于exists子句相当于一个连接,我认为没有必要优化这个sql,或者至少应该用数值证明。顺便说一句,我能够用我的模型更新我的数据库(插入和删除记录),正如我的答案中所写的那样。
  • 关于性能证明你说得对,我稍后再试。感谢您的宝贵时间
  • fyi 我正在使用 sqlite:也许这可以解释外键的不同行为......?所以这将是一个 sql server(而不是 EF 核心)特定的错误......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-09-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多