使用ValueInjecter将EntityFramework POCO复制到DTO而不触发延迟加载列表和属性

时间:2013-02-10 01:45:58

标签: c# entity-framework entity-framework-4 valueinjecter

我遇到使用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就会有相当大的延迟。如果我还在MatchSetValue方法的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中吗?

*编辑* 我找到了一个解决方案并回答了这个问题,如果我做错了,或者还有更好的方法,我仍然很好奇。

1 个答案:

答案 0 :(得分:5)

我觉得我对自己的问题找到了一个满意的答案,所以我只想自己解决这个问题。 我最后做了两件事。

首先,我完全在dbContext上禁用了Lazy Loading。 dbContext构造函数中类似于此的东西。

this.Configuration.LazyLoadingEnabled = false;

我并没有真正使用EF的延迟加载功能,因此将其关闭并没有太大的损失。这只是意味着如果我想要填充相关实体,我必须在我的查询的Include中指定它们。没什么大不了的。

我做的另一件事是重做深度克隆注入约定,以使用此处SmartConventionInjection source中的SmartConventionInjection代码。除了比基本注入更快的注入之外,它在SetValue调用之前也不触及属性值,所以即使我确实有一些延迟加载属性,除非DTO也有,否则它们不会被触及该财产。