C# EF Core+Automapper ProjectTo中的递归CTE

C# EF Core+Automapper ProjectTo中的递归CTE,c#,sql,entity-framework-core,automapper,recursive-cte,C#,Sql,Entity Framework Core,Automapper,Recursive Cte,在我的项目中,我有Comment和CommentDto类型: 我有以下递归CTE,封装在表值函数中: 此函数允许获取给定帖子的注释层次结构,它采用帖子的ID 数据库中存储有注释: 为了使其更具可读性,我将其表示为仅按层次顺序排列的内容属性: 你好,世界! 你是程序员吗? 当然 什么 我也想去火星! 月球上见: 使用efcore调用函数fn_后共产主义 注意:我做了粗体注释,没有父根注释 将注释映射到CommentDto 上面的代码使用实体类型Comment,但我想将其映射到CommentDto

在我的项目中,我有Comment和CommentDto类型:

我有以下递归CTE,封装在表值函数中:

此函数允许获取给定帖子的注释层次结构,它采用帖子的ID

数据库中存储有注释:

为了使其更具可读性,我将其表示为仅按层次顺序排列的内容属性:

你好,世界! 你是程序员吗? 当然 什么 我也想去火星! 月球上见: 使用efcore调用函数fn_后共产主义 注意:我做了粗体注释,没有父根注释

将注释映射到CommentDto 上面的代码使用实体类型Comment,但我想将其映射到CommentDto。因此,让我们使用ProjectTo实现此目的:

List<CommentDto> commentHierarchy = await _context.Comment
    .FromSqlInterpolated($"SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('post-id-here')")
    .ProjectTo<CommentDto>(_mapper.ConfigurationProvider)
    .ToListAsync();
注意:我做了粗体注释,没有父根注释。 比较使用ProjectTo之前和之后的结果。为什么它们不同

对于上述代码,EF Core将以下SQL查询发送到SQL server:

SELECT [c].[CommentId], [c].[Content], [c].[ParentCommentId], [c0].[CommentId], [c0].[Content], [c0].[ParentCommentId]
FROM (
    SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('69f3ca3a-66fc-4142-873d-01e950d83adf')
) AS [c]
LEFT JOIN [Comment] AS [c0] ON [c].[CommentId] = [c0].[ParentCommentId]
ORDER BY [c].[CommentId], [c0].[CommentId]
问题 为什么使用ProjectTo之前的结果和使用ProjectTo之后的结果不一样? 如何解决这个问题

更新1 根据Svyatoslav Danyliv的说法:


递归CTE返回平面列表,然后必须构建层次结构 再说一遍

但是为什么在这种情况下我应该使用递归CTE呢? 以下解决方案的工作方式相同:

List<CommentDto> commentFlatList = await _context.Comment
    .Where(c => c.PostId == Guid.Parse("post-id-here"))
    .ProjectTo<CommentDto>(_mapper.ConfigurationProvider)
    .ToListAsync();
                
Dictionary<Guid, CommentDto> commentDictionary = commentFlatList
    .ToDictionary(c => c.CommentId);

foreach (var comment in commentFlatList)
{
    if (comment.ParentCommentId == null)
    {
        continue;
    }

    if (commentDictionary.TryGetValue((Guid) comment.ParentCommentId, out CommentDto parent))
    {
        parent.Children.Add(comment);
    }
}

List<CommentDto> commentHierarchy = commentFlatList.Where(c => c.ParentCommentId == null);
EF Core将其转换为以下内容:

exec sp_executesql N'SELECT [c].[CommentId], [c].[Content], [c].[ParentCommentId]
FROM [Comment] AS [c]
WHERE [c].[PostId] = @__request_PostId_0',N'@__request_PostId_0 uniqueidentifier',@__request_PostId_0='post-id-here'

递归CTE返回平面列表,然后您必须再次构建层次结构

var commentHierarchy=wait_context.Comment .FromSqlInterpolated$SELECT CommentId,Content,PostId,ParentCommentId FROM dbo.fn_post-commenthierarchy'post-id-here' .ProjectTo_mapper.ConfigurationProvider .ToListAsync; var lookup=commentHierarchy.ToLookupx=>x.commentId; 注释层次结构中的foreach var c { 如果lookup.Containsc.commentId c、 inverseParentComment.AddRangelookup.Item[c.commentId]; } var result=commentHierarchy.Wherec=>c.parentCommentId==null;
谢谢你的主意。我更新了我的问题,请看一下您调用的递归CTE,不要从数据库加载整个表。只需要一片叶子。@SvyatoslavDanyliv,你认为我为什么要把整张桌子都装上?我再次更新了我的问题,以显示EF Core发送到数据库的SQL查询是的,如果您可以通过PostId进行筛选,则不需要CTE。谢谢。我会确认你的回答。当您必须使用递归CTE时,可以使用它。但我认为我的案例可以简化解决方案。
[
{
    "commentId": "be02742a-9170-4335-afe7-3c7c22684424",
    "content": "Hello World!",
    "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
    "post": null,
    "parentCommentId": null,
    "parentComment": null,
    "commentRates": [],
    "inverseParentComment": [
    {
        "commentId": "59656765-d1ed-4648-8696-7d576ab7419f",
        "content": "Are you a programmer?",
        "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
        "post": null,
        "parentCommentId": "be02742a-9170-4335-afe7-3c7c22684424",
        "commentRates": [],
        "inverseParentComment": [
        {
            "commentId": "0bb77a43-c7bb-482f-9bf8-55c4050974da",
            "content": "Sure",
            "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
            "post": null,
            "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
            "commentRates": [],
            "inverseParentComment": []
        },
        {
            "commentId": "b8d61cfd-d274-4dae-a2be-72e08cfa9066",
            "content": "What?",
            "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
            "post": null,
            "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
            "commentRates": [],
            "inverseParentComment": []
        }
        ]
    }
    ]
},
{
    "commentId": "cfe126b3-4601-4432-8c87-445c1362a225",
    "content": "I wanna go to Mars too!",
    "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
    "post": null,
    "parentCommentId": null,
    "parentComment": null,
    "commentRates": [],
    "inverseParentComment": [
    {
        "commentId": "ab6d6b49-d772-48cd-9477-8d40f133c37a",
        "content": "See you on the Moon :)",
        "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
        "post": null,
        "parentCommentId": "cfe126b3-4601-4432-8c87-445c1362a225",
        "commentRates": [],
        "inverseParentComment": []
    }
    ]
},
{
    "commentId": "ab6d6b49-d772-48cd-9477-8d40f133c37a",
    "content": "See you on the Moon :)",
    "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
    "post": null,
    "parentCommentId": "cfe126b3-4601-4432-8c87-445c1362a225",
    "parentComment": {
        "commentId": "cfe126b3-4601-4432-8c87-445c1362a225",
        "content": "I wanna go to Mars too!",
        "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
        "post": null,
        "parentCommentId": null,
        "parentComment": null,
        "commentRates": [],
        "inverseParentComment": []
    },
    "commentRates": [],
    "inverseParentComment": []
},
{
    "commentId": "59656765-d1ed-4648-8696-7d576ab7419f",
    "content": "Are you a programmer?",
    "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
    "post": null,
    "parentCommentId": "be02742a-9170-4335-afe7-3c7c22684424",
    "parentComment": {
        "commentId": "be02742a-9170-4335-afe7-3c7c22684424",
        "content": "Hello World!",
        "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
        "post": null,
        "parentCommentId": null,
        "parentComment": null,
        "commentRates": [],
        "inverseParentComment": []
    },
    "commentRates": [],
    "inverseParentComment": [
    {
        "commentId": "0bb77a43-c7bb-482f-9bf8-55c4050974da",
        "content": "Sure",
        "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
        "post": null,
        "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
        "commentRates": [],
        "inverseParentComment": []
    },
    {
        "commentId": "b8d61cfd-d274-4dae-a2be-72e08cfa9066",
        "content": "What?",
        "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
        "post": null,
        "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
        "commentRates": [],
        "inverseParentComment": []
    }
    ]
},
{
    "commentId": "0bb77a43-c7bb-482f-9bf8-55c4050974da",
    "content": "Sure",
    "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
    "post": null,
    "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
    "parentComment": {
        "commentId": "59656765-d1ed-4648-8696-7d576ab7419f",
        "content": "Are you a programmer?",
        "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
        "post": null,
        "parentCommentId": "be02742a-9170-4335-afe7-3c7c22684424",
        "parentComment": {
            "commentId": "be02742a-9170-4335-afe7-3c7c22684424",
            "content": "Hello World!",
            "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
            "post": null,
            "parentCommentId": null,
            "parentComment": null,
            "commentRates": [],
            "inverseParentComment": []
        },
        "commentRates": [],
        "inverseParentComment": [
        {
            "commentId": "b8d61cfd-d274-4dae-a2be-72e08cfa9066",
            "content": "What?",
            "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
            "post": null,
            "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
            "commentRates": [],
            "inverseParentComment": []
        }
        ]
    },
    "commentRates": [],
    "inverseParentComment": []
},
{
    "commentId": "b8d61cfd-d274-4dae-a2be-72e08cfa9066",
    "content": "What?",
    "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
    "post": null,
    "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
    "parentComment": {
        "commentId": "59656765-d1ed-4648-8696-7d576ab7419f",
        "content": "Are you a programmer?",
        "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
        "post": null,
        "parentCommentId": "be02742a-9170-4335-afe7-3c7c22684424",
        "parentComment": {
            "commentId": "be02742a-9170-4335-afe7-3c7c22684424",
            "content": "Hello World!",
            "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
            "post": null,
            "parentCommentId": null,
            "parentComment": null,
            "commentRates": [],
            "inverseParentComment": []
        },
        "commentRates": [],
        "inverseParentComment": [
        {
            "commentId": "0bb77a43-c7bb-482f-9bf8-55c4050974da",
            "content": "Sure",
            "postId": "69f3ca3a-66fc-4142-873d-01e950d83adf",
            "post": null,
            "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
            "commentRates": [],
            "inverseParentComment": []
        }
        ]
    },
    "commentRates": [],
    "inverseParentComment": []
}]
List<CommentDto> commentHierarchy = await _context.Comment
    .FromSqlInterpolated($"SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('post-id-here')")
    .ProjectTo<CommentDto>(_mapper.ConfigurationProvider)
    .ToListAsync();
[
{
    "commentId": "be02742a-9170-4335-afe7-3c7c22684424",
    "content": "Hello World!",
    "parentCommentId": null,
    "inverseParentComment": [
    {
        "commentId": "59656765-d1ed-4648-8696-7d576ab7419f",
        "content": "Are you a programmer?",
        "parentCommentId": "be02742a-9170-4335-afe7-3c7c22684424",
        "inverseParentComment": null
    }
    ]
},
{
    "commentId": "cfe126b3-4601-4432-8c87-445c1362a225",
    "content": "I wanna go to Mars too!",
    "parentCommentId": null,
    "inverseParentComment": [
    {
        "commentId": "ab6d6b49-d772-48cd-9477-8d40f133c37a",
        "content": "See you on the Moon :)",
        "parentCommentId": "cfe126b3-4601-4432-8c87-445c1362a225",
        "inverseParentComment": null
    }
    ]
},
{
    "commentId": "0bb77a43-c7bb-482f-9bf8-55c4050974da",
    "content": "Sure",
    "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
    "inverseParentComment": []
},
{
    "commentId": "b8d61cfd-d274-4dae-a2be-72e08cfa9066",
    "content": "What?",
    "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
    "inverseParentComment": []
},
{
    "commentId": "59656765-d1ed-4648-8696-7d576ab7419f",
    "content": "Are you a programmer?",
    "parentCommentId": "be02742a-9170-4335-afe7-3c7c22684424",
    "inverseParentComment": [
    {
        "commentId": "0bb77a43-c7bb-482f-9bf8-55c4050974da",
        "content": "Sure",
        "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
        "inverseParentComment": null
    },
    {
        "commentId": "b8d61cfd-d274-4dae-a2be-72e08cfa9066",
        "content": "What?",
        "parentCommentId": "59656765-d1ed-4648-8696-7d576ab7419f",
        "inverseParentComment": null
    }
    ]
},
{
    "commentId": "ab6d6b49-d772-48cd-9477-8d40f133c37a",
    "content": "See you on the Moon :)",
    "parentCommentId": "cfe126b3-4601-4432-8c87-445c1362a225",
    "inverseParentComment": []
}
]
SELECT [c].[CommentId], [c].[Content], [c].[ParentCommentId], [c0].[CommentId], [c0].[Content], [c0].[ParentCommentId]
FROM (
    SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('69f3ca3a-66fc-4142-873d-01e950d83adf')
) AS [c]
LEFT JOIN [Comment] AS [c0] ON [c].[CommentId] = [c0].[ParentCommentId]
ORDER BY [c].[CommentId], [c0].[CommentId]
List<CommentDto> commentFlatList = await _context.Comment
    .Where(c => c.PostId == Guid.Parse("post-id-here"))
    .ProjectTo<CommentDto>(_mapper.ConfigurationProvider)
    .ToListAsync();
                
Dictionary<Guid, CommentDto> commentDictionary = commentFlatList
    .ToDictionary(c => c.CommentId);

foreach (var comment in commentFlatList)
{
    if (comment.ParentCommentId == null)
    {
        continue;
    }

    if (commentDictionary.TryGetValue((Guid) comment.ParentCommentId, out CommentDto parent))
    {
        parent.Children.Add(comment);
    }
}

List<CommentDto> commentHierarchy = commentFlatList.Where(c => c.ParentCommentId == null);
List<CommentDto> commentFlatList = await _context.Comment
        .Where(c => c.PostId == Guid.Parse("post-id-here"))
        .ProjectTo<CommentDto>(_mapper.ConfigurationProvider)
        .ToListAsync();
exec sp_executesql N'SELECT [c].[CommentId], [c].[Content], [c].[ParentCommentId]
FROM [Comment] AS [c]
WHERE [c].[PostId] = @__request_PostId_0',N'@__request_PostId_0 uniqueidentifier',@__request_PostId_0='post-id-here'