我遇到使用ValueInjecter创建EntityFramework POCO深度克隆到类似DTO类的问题。
如果我从具有导航属性的多个相关实体/子实体的复杂POCO对象注入更简单的DTO,则ValueInjecter似乎仍然触及多个属性值并导致从数据库延迟加载此数据。
我相信ValueInjecter会获取特定源对象中每个属性的值,因为它准备将值注入指定的目标。
我的实际项目相当复杂,但作为一个例子,我采用了NerdDinner示例并以更简单的方式复制了该问题。 (NerdDinner是使用EF4(ScottGu NerdDinner Example)的第一个代码示例。
所以我有两个模型类。
public class Dinner
{
public int DinnerId { get; set; }
public string Title { get; set; }
public DateTime EventDate { get; set; }
public string Address { get; set; }
public string HostedBy { get; set; }
public virtual ICollection<RSVP> Rsvps { get; set; }
}
和
public class RSVP
{
public int RsvpID { get; set; }
public int DinnerID { get; set; }
public string AttendeeEmail { get; set; }
public virtual Dinner Dinner { get; set; }
}
我还创建了一个DTO类:
public class DinnerDTO
{
public int DinnerId { get; set; }
public string Title { get; set; }
public DateTime EventDate { get; set; }
public string Address { get; set; }
public string HostedBy { get; set; }
}
注意我的Dinner
DinnerDTO
中没有找到Rsvps集合。
同样重要的是,我使用CloneInjection约定深度克隆对象。此处的代码既可以在SO上提供,也可以在许多其他站点中作为执行深度克隆注入的方法。 此代码位于此处:CloneInjection Code
现在,为了强调发生的延迟加载,我去插入10,000个RSVP以进行Id = 1的晚餐。
然后我执行以下代码:var dinner = nerdDinners.Dinners.Where(x => x.DinnerId == 1).FirstOrDefault();
DinnerDTO dinnerDTO = new DinnerDTO();
dinnerDTO.InjectFrom<CloneInjection>(dinner);
如果我在InjectFrom
的行上设置一个断点,然后跳过它,那么懒惰加载10,000 RSVP就会有相当大的延迟。如果我还在Match
和SetValue
方法的CloneInjection代码中设置断点,则在加载延迟解决之后才会触发这些断点。这告诉我,它必须是ValueInjecter内部的东西,它会导致RSVPs
属性的延迟加载。
现在,如果我将上述代码修改为:(在查询中添加Include
)
var dinner = nerdDinners.Dinners.Where(x => x.DinnerId == 1).Include("RSVPs").FirstOrDefault();
DinnerDTO dinnerDTO = new DinnerDTO();
dinnerDTO.InjectFrom<CloneInjection>(dinner);
此更改会强制“急切负载”#34; RSVP的列表,正如预期的那样,滞后与查询一致,InjectFrom
行在过去时没有任何延迟。
我已经阅读了StackOverflow上一些含糊不清的帖子,有些人建议禁用,然后在datacontext上启用LazyLoading。我试过了,虽然它确实有效但感觉很脏。
我仔细阅读了这篇文章(Copying NHibernate POCO to DTO without triggering lazy load or eager load)和相关代码,他的方法似乎是使用一些NHibernate方法来确定属性是否是未初始化的代理,并以某种方式将它们删除。我在EF4中找不到类似的东西。
真正让我失望的部分是Rsvps系列甚至不在我的DTO对象中,我甚至对它的价值感兴趣。 这对我来说似乎不对。我不认为ValueInjecter代码应该询问目标对象可能不关心的属性值。
是否有一些方法可以在ValueInjecter中覆盖此行为?以某种方式推迟评估财产价值,直到我绝对确定我想要价值,例如在SetValue
的{{1}}方法中?那么至少它不会评估我的DTO甚至不想要的属性。
我能想到的最佳解决方案是ValueInjecter或自定义约定,以某种方式能够检测到卸载的延迟加载属性,而不是评估它,而只是将该属性设置为null目标。我不认为这是可能的。
我应该使用EF更好的方法吗?我不希望Eager加载数据库中的所有内容。
我完全离开了,问题根本不在ValueInjecter中吗?
*编辑* 我找到了一个解决方案并回答了这个问题,如果我做错了,或者还有更好的方法,我仍然很好奇。
答案 0 :(得分:5)
我觉得我对自己的问题找到了一个满意的答案,所以我只想自己解决这个问题。 我最后做了两件事。
首先,我完全在dbContext上禁用了Lazy Loading。 dbContext构造函数中类似于此的东西。
this.Configuration.LazyLoadingEnabled = false;
我并没有真正使用EF的延迟加载功能,因此将其关闭并没有太大的损失。这只是意味着如果我想要填充相关实体,我必须在我的查询的Include
中指定它们。没什么大不了的。
我做的另一件事是重做深度克隆注入约定,以使用此处SmartConventionInjection source中的SmartConventionInjection
代码。除了比基本注入更快的注入之外,它在SetValue
调用之前也不触及属性值,所以即使我确实有一些延迟加载属性,除非DTO也有,否则它们不会被触及该财产。