0

在我的项目中,我有类型CommentCommentDto

public class Comment
{
    public Guid CommentId { get; set; }
    public string Content { get; set; }

    public Guid PostId { get; set; }
    public virtual Post Post { get; set; }

    public Guid? ParentCommentId { get; set; }
    public virtual Comment ParentComment { get; set; }
    public virtual ICollection<Comment> InverseParentComment { get; set; }
}
class CommentDto
{
    public Guid CommentId { get; set; }
    public string Content { get; set; }
    public Guid? ParentCommentId { get; set; }
    public ICollection<CommentDto> InverseParentComment { get; set; }
}

Comment将映射到CommentDto. 这是配置

cfg.CreateMap<Comment, CommentDto>();

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

FUNCTION [dbo].[fn_PostCommentHierarchy] (@postId UNIQUEIDENTIFIER)
RETURNS TABLE
AS
RETURN
(
    WITH cte AS
    (
        SELECT CommentId, Content, PostId, ParentCommentId 
        FROM dbo.Comment
        WHERE ParentCommentId IS NULL and PostId = @postId

        UNION ALL

        SELECT child.CommentId, child.Content, child.PostId, child.ParentCommentId
        FROM dbo.Comment child
        INNER JOIN cte parent
        ON parent.CommentId = child.ParentCommentId
        WHERE parent.PostId = @postId
    )

    SELECT * FROM cte
);

此函数允许获取给定帖子的评论层次结构(它需要帖子的 ID)。

数据库中存储有评论:

为了使其更具可读性,我以分层顺序(仅Content属性)表示它:

  • 你好世界!
    • 你是程序员吗?
      • 当然
      • 什么?
  • 我也想去火星!
    • 月球上见:)

fn_PostCommentHierarchy使用 EF Core调用函数

List<Comment> commentHierarchy = await _context.Comment
    .FromSqlInterpolated($"SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('post-id-here')")
    .ToListAsync();

EF Core 将以下 SQL 查询发送到 SQL-Server:
SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('post-id-here')

上面的代码按预期工作(使用 JSON 格式来增加可读性):

[
{
    "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": []
}]

注意:我做了大胆的评论,没有父母(根评论)。

映射CommentCommentDto

上面的代码适用于 entity-type 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();

注意_mapper是一个对象类型IMapper

我认为,结果应该与我使用之前得到的结果相似ProjectTo。但它看起来像:

[
{
    "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": []
}
]

注意:我做了大胆的评论,没有父母(根评论)。
比较使用前后的结果ProjectTo。为什么它们不同?

对于上面的代码,EF Core 将以下 SQL 查询发送到 SQL 服务器:

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

注意:我使用Dictionary了代替Lookup参见这个例子),但它并没有改变这个想法。

更新 2

让我们看一下更新 1 中的代码:

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

它将由 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'
4

1 回答 1

1

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

var commentHierarchy = await _context.Comment
    .FromSqlInterpolated($"SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('post-id-here')")
    .ProjectTo<CommentDto>(_mapper.ConfigurationProvider)
    .ToListAsync();

var lookup = commentHierarchy.ToLookup(x => x.commentId);

foreach (var c in commentHierarchy)
{
    if (lookup.Contains(c.commentId))
        c.inverseParentComment.AddRange(lookup.Item[c.commentId]);
}

var result = commentHierarchy.Where(c => c.parentCommentId == null);
于 2021-05-07T16:33:46.233 回答