EF Core + Automapper ProjectTo 中的递归 CTE
Recursive CTE in EF Core + Automapper ProjectTo
在我的项目中我有类型 Comment
和 CommentDto
:
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
。这是 configuration:
cfg.CreateMap<Comment, CommentDto>();
我有以下递归 CTE,封装在 table 值函数中:
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
);
此函数允许获取给定 post 的评论层次结构(它采用 post 的 ID)。
数据库中存有评论:
为了更易读,我按层次顺序表示(仅Content
属性):
- 世界,您好!
- 你是程序员吗?
- 当然
- 什么?
- 我也想去火星!
- 月球见:)
使用 EF Core
调用函数 fn_PostCommentHierarchy
List<Comment> commentHierarchy = await _context.Comment
.FromSqlInterpolated($"SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('post-id-here')")
.ToListAsync();
EF Core 将以下 SQL-查询发送到 SQL-服务器:
SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('post-id-here')
上面的代码按预期工作(JSON-格式用于提高可读性):
[
<b>{
"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": []
}
]
}</b>,
{
"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": []
}]</pre>
注意:我做了大胆的评论,没有父评论(根评论)。
将 Comment
映射到 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();
注意:_mapper
是IMapper
.
类型的对象
我想,结果应该和我之前用ProjectTo
得到的结果差不多。但它看起来像:
[<b>
{
"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
}
]
}</b>,
{
"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 的说法:
Recursive CTE returns flat list, then you have to build hierarchy
again.
但为什么在这种情况下我应该使用递归 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
(see this example),但并没有改变思路
更新 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'
递归 CTE returns 平面列表,然后你必须重新构建层次结构。
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);
在我的项目中我有类型 Comment
和 CommentDto
:
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
。这是 configuration:
cfg.CreateMap<Comment, CommentDto>();
我有以下递归 CTE,封装在 table 值函数中:
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
);
此函数允许获取给定 post 的评论层次结构(它采用 post 的 ID)。
数据库中存有评论:
为了更易读,我按层次顺序表示(仅Content
属性):
- 世界,您好!
- 你是程序员吗?
- 当然
- 什么?
- 你是程序员吗?
- 我也想去火星!
- 月球见:)
使用 EF Core
调用函数fn_PostCommentHierarchy
List<Comment> commentHierarchy = await _context.Comment
.FromSqlInterpolated($"SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('post-id-here')")
.ToListAsync();
EF Core 将以下 SQL-查询发送到 SQL-服务器:
SELECT CommentId, Content, PostId, ParentCommentId FROM dbo.fn_PostCommentHierarchy('post-id-here')
上面的代码按预期工作(JSON-格式用于提高可读性):
[ <b>{ "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": [] } ] }</b>, { "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": [] }]</pre>
注意:我做了大胆的评论,没有父评论(根评论)。
将
Comment
映射到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();
注意:
类型的对象_mapper
是IMapper
.我想,结果应该和我之前用
ProjectTo
得到的结果差不多。但它看起来像:[<b> { "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 } ] }</b>, { "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 的说法:
Recursive CTE returns flat list, then you have to build hierarchy again.
但为什么在这种情况下我应该使用递归 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
(see this example),但并没有改变思路更新 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'
递归 CTE returns 平面列表,然后你必须重新构建层次结构。
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);