是否可以手动将现有对象分配给Entity Framework(db first)对象的导航属性?
问题的上下文是我在尝试带回一个(经过严格过滤的)对象列表时遇到问题,其中所有子项和后代都已连接,以便在处理上下文后内存中可以使用完整的图形。
我尝试通过.Include()
语句使用以下内容执行此操作:
using (var ctx = new MyEntities())
{
myParents = ctx.Parents
.Where(p => MyFilter(p))
.Include(p => p.Children)
.Include(p => p.Children.Select(c=>c.Grandchildren))
.Include(p => p.Children.Select(c=>c.Grandchildren.Select(g=>g.GreatGrandChildren)));
}
但生成的查询运行速度太慢,因为使用嵌套的include语句存在已知的性能问题(如许多地方所述,包括this blog)。
我可以在没有表现问题的情况下撤回父母,子女和孙子女 - 当我包括最后一个.Include()
对伟大的孩子的陈述时,我只会遇到麻烦。
通过从已经检索到的GrandChildren构建GrandChildrenId列表并执行以下操作,我可以通过第二个单独的查询轻松地从数据库中获取GreatGrandChildren对象:
greatGrandKids = ctx.GreatGrandChildren.Where(g=>ids.Contains(g.GrandChildId)).ToList();
但是现在,一旦我处理了上下文,我就不能做grandChildA.GreatGrandChildren
之类的事情而不会遇到对象上下文处理异常。
我可以拥有多达几千个GrandChildren对象,所以我真的想避免往返数据库以获取GreatGrandChildren,因为每个GrandChrandChildren在每个GrandChild对象上排除使用.Load()
,对吧?
我可以通过在我的后续代码中每次需要它时从greatGrandKids
查找所需的伟大的孩子,或者甚至通过添加新的(非映射的)属性(例如{{1})来解决这个问题。 } .GreatGrandChildrenLocal
课程并将它们全部分配到前面,但这些都感觉非常 kludgy&丑陋。我更愿意找到一种方法来访问每个GrandChild
对象上的现有.GreatGrandChildren
导航属性。
尝试使用类似这样的东西分配给导航属性:
GrandChild
当我尝试访问grandchild.GreatGrandChildren = greatGrandKids
.Where(g=>g.GrandChildId == grandChild.Id)
.ToList();
(仍然给对象处置异常)时,也失败了。
所以我的问题是:
有没有办法可以将我已经从数据库中检索到的现有GreatGrandChdildren对象分配给GrandChild对象上的grandchild.GreatGrandChildren
导航属性,以便使它们可用(仅用于处理上下文后的读取操作?
(或者确实存在不同的解决方案?)
答案 0 :(得分:0)
如果您使用以下命令禁用代理创建:
ctx.Configuration.ProxyCreationEnabled = false;
然后从/向导航属性读取和写入完全按预期工作,而不是试图懒惰加载实体并抛出对象处置异常。
所以我们有类似的东西:
using (var ctx = new MyEntities())
{
myParents = ctx.Parents
.Where(p => MyFilter(p))
.Include(p => p.Children)
.Include(p => p.Children.Select(c=>c.Grandchildren));
//skip the final GreatGrandChildren include statement
//get the associated grandchildren & their ids:
var grandKids = myParents.SelectMany(p=>p.Children)
.SelectMany(c=>c.Grandchildren)
.ToList();
var ids = grandKids.Select(g=>g.Id)).ToList();
//Get the great grandkids:
var greatGrandKids = ctx.GreatGrandChildren
.Where(g=>ids.Contains(g.GrandChildId)).ToList();
//Assign the greatgrandchildren to the grandchildren:
foreach (grandChild in grandKids)
{
grandChild.GreatGrandChildren = greatGrandKids
.Where(g=>g.GrandChildId == grandChild.Id)
.ToList();
}
}
现在我们可以访问上下文之外的.GreatGrandChildren
属性,而不会遇到上下文处理异常。虽然这仍然有点混乱,但它比使用原始Include()
语句或在每个GrandChild上调用.Load()
便宜得多。
N.B。由于这些对象仅用于读取操作,并且我不需要延迟加载,因此在我的环境中关闭代理创建没有任何负面影响。如果还需要写操作和/或延迟加载,那么我们还需要考虑为给定的EF上下文关闭它的含义。