TLDR:我想知道在EF核心中是否可以在单个查询中为一个实体类型使用不同的“包含逻辑”。
编辑:为了清楚,在标题中我说跟踪实体,因为我认为这是EF的作用,但.AsNoTracking()
在这里没有任何用处,在任何人建议之前。
手头的问题是在一个相对较小的React应用程序上,该应用程序支持ASP.NET Core web api应用程序。我想要完成的是,当我调用api/parents
时,我希望应用程序给我一个看起来像这样的json:
[
{
"id": "parentid1",
"extraProperty": "value",
"children": [
{
"id": "childid1",
"parent": {
"id": "parentid1",
"extraProperty": "value"
}
}
]
}
]
我的设置如下:
EF查询:
(from p in _context.Parents
select p)
.Include(p => p.Children)
.ThenInclude(c => c.Parent)
.ToList();
之后,我将AutoMapper映射到dto的实体,那里没有太多进展。我还使用默认的Json Serializer(Newtonsoft)作为应用程序。它配置有SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore
。
通过此设置,api调用将返回此响应:
[
{
"id": "parentid1",
"extraProperty": "value",
"children": [
{
"id": "childid1"
}
]
}
]
你可以看到“忽略”父母的自我引用。
我想出的解决方案是,我应该将Newtonsoft配置为“序列化”参考循环,然后我尝试了。并抛出,因为上面提供的EF查询返回一个如下所示的实体列表:
[
{
"id": "parentid1",
"extraProperty": "value",
"children": [
{
"id": "childid1",
"parent": {
"id": "parentid1",
"extraProperty": "value",
"children": [
{
"id": "childid1",
"parent": {
"id": "parentid1",
"extraProperty": "value"
...
}
}
]
}
}
]
}
]
如果你查看我的Include调用,它会明确地说明对于我的子对象,我只想要父对象而在该父对象中没有其他引用。
据我所知,EF在遇到该查询的任何Parent对象时使用初始设置.Include(child).ThenInclude(parent)
。因此,当它在Parent
对象中找到Child
对象时,而不是使用no includes(我在ThenInclude
之后没有任何包含),它使用.Include(child).ThenInclude(parent)
。
我不想通过使用映射器或序列化器来解决这个问题,如果我不需要的话。我想知道我所寻求的是否可能:在单个查询中为一个实体类型使用不同的“包含逻辑”。
ThenInclude(parent)
调用是多余的,因为你从父母开始 - 物化的孩子已经填充了他们的Parent
属性。您需要将序列化自定义为@IvanStoev在第一条评论中说明。
另一种方法是按父级查询子项,然后投影所需的结果:
var parents = _context.Children
.Include( c => c.Parent )
.GroupBy( c => c.Parent )
.ToArray()
.Select( g =>
{
// assign children to g.Key (the parent object)
g.Key.Children = g.ToArray(); // I don't know type of `Children` property
// select parent
return g.Key;
} );
我决定在前端解决问题因为其他解决方案对我不起作用。为了将来的参考,我将发布我的旅程:
EF
起初,在数据访问层执行技巧似乎是合乎逻辑的,因为我只是错过了一个非常基本的东西。我意识到在评论之后这样做是不合逻辑的。基本上,当您从EF获取实体并且您的对象具有引用时,每次您有一个对象不止一次时,它们都是相同的对象(通过引用而不是值)。所以期望他们在其中有不同的数据(在我的情况下,其中一个有一些细节,另一个没有)是不合逻辑的。
在那之后,我想也许我可以在绘图阶段解决这个问题。我尝试为不同的场景做不同的配置文件/配置,它变得非常难看。然后我想可能使用一个Profile并做一些AfterMap()
逻辑可以工作(最初加载所有数据,然后剥离不需要的数据)。但是,除非你做一些丑陋的东西,否则同样的原则也适用。 AutoMapper还保留引用,因此当您修改child.Parent
对象时,原始Parent也会被修改。我本来可以使用克隆,也许还有其他一些技巧,但就像我说的那样,在我看来它变得丑陋。
现在我在React应用程序上手动完成任务。当我从服务器获取数据时,我就这样做了
parent.children.forEach(c => c.parent = parent);
请记住,通常parent.children.parent
是null
。实体对象或DTO不是这种情况,它是序列化后的情况。因为我为序列化器设置了ReferenceLoopHandling.Ignore
。
这解决了我的应用程序的所有问题,除了我的api似乎不完整。返回的JSON看起来像是缺少一些东西(也许只是我,IDK)。
在一天结束时,我正在考虑为不同的场景设置相同对象类型的多个DTO,或者为了完整性而在api中返回id字段。
感谢所有评论和回复。