我应该在使用Entity Framework 4.1和MVC3时启用还是禁用动态代理?

68

有人可以提供一些建议或指出一些博客/文章来帮助做出这个决定吗?代理对我来说似乎非常陌生,我不敢使用它们。我喜欢使用模型中的虚拟属性来控制Lazy Loading,但这几乎是我能看到的唯一好处。我的应用程序是一个简单的MVC Web应用程序,我不需要为实体状态改变时连接任何钩子。

无论如何,这是我目前非常有限的优缺点清单,请让我知道如果我在任何方面都错了。

优点

  • 在“保存”或“更新”时,我可以无缝地与“应用”Changes配合使用。
  • 懒加载配置非常容易。

缺点

  • 以前从未使用过代理来处理我的实体,这是一种改变方法的方式,这对我和团队成员来说都感到不舒服。
  • 调试麻烦。
  • 如果想要序列化/反序列化,则需要额外的代码。
  • 在“保存”或“更新”时,代理必须是从上下文检索到的相同对象。

7
+1 - 序列化/反序列化问题真是个烦恼! - StuartLC
5个回答

103
如果讨论EF中的动态代理,有两种不同的类型需要区分:
  • 用于延迟加载的代理
  • 用于更改跟踪的代理
通常情况下,更改跟踪代理也可以作为延迟加载的代理。反过来则不成立。这是因为更改跟踪代理的要求更高,特别是所有属性 - 包括标量属性 - 必须是虚拟的。对于延迟加载来说,导航属性是虚拟的就足够了。
更改跟踪代理始终允许利用延迟加载的主要原因是DbContext具有此配置标志:
DbContext.Configuration.LazyLoadingEnabled

这个标志默认为true。如果将其设置为false,即使创建了代理,也会禁用延迟加载。如果您使用更改跟踪代理但不想将这些代理用于延迟加载,则这一点尤为重要。
选项...
DbContext.Configuration.ProxyCreationEnabled

...完全禁用代理创建-包括更改跟踪和延迟加载。

只有在您的实体类满足创建更改跟踪或延迟加载代理的要求时,这两个标志才有意义。

现在,您知道动态延迟加载代理的目的。那么,为什么要使用动态更改跟踪代理呢?

实际上,我所知道的唯一原因是性能。但这是一个非常强的理由。与基于快照的更改跟踪相比,基于代理的更改跟踪的性能差异巨大-根据我的测量,实际上可以达到50到100倍的差距(来自一个需要约一小时才能处理10000个实体的方法,在启用更改跟踪代理之后,所有属性变成虚拟属性,需要30到60秒)。如果您有一些处理和更改许多(例如超过1000个)实体的应用程序,则这将成为重要因素。在Web应用程序中,您可能仅对单个实体进行Create / Change / Delete操作,因此这种差异并不重要。

在几乎所有情况下,如果您不想使用延迟加载代理,您都可以利用急切或显式加载来实现相同的目标。基于代理的延迟加载或非代理的显式加载的性能是相同的,因为当导航属性加载时,基本上发生了相同的查询-在第一种情况下,代理执行查询,在第二种情况下,您编写的代码执行查询。因此,您可以在不失去太多的情况下放弃延迟加载代理。

但是,如果您想要处理许多实体并获得合理的性能,则除了在EF 4.0中使用 EntityObject 派生实体(在使用 DbContext 时禁止在EF 4.1中使用)或根本不使用Entity Framework之外,没有其他选择。

编辑(2012年5月)

与此同时,我了解到在某些情况下,更改跟踪代理并不比基于快照的跟踪更快,甚至性能更差。

由于使用更改跟踪代理时存在这些复杂性,首选方式是默认使用基于快照的更改跟踪,并仔细地使用代理(在进行一些测试后)仅在需要高性能且证明它们比基于快照的更改跟踪更快的情况下使用。


1
@Slauma - 我不知道你可以在运行时更改这些配置,我以为只有在创建域定义时才能使用。使用 if (DisableProxy) { context.Configuration.ProxyCreationEnabled = false; context.Configuration.LazyLoadingEnabled = false; },我能够允许这些动态代理可被序列化而不会导致循环引用错误。谢谢!+1 - Travis J

15

2
我建议不要使用代理。动态代理创建会破坏或给依赖于运行时类型检查的组件带来复杂性。
例如,Automapper在运行时会抛出类型不匹配/意外类型错误,因为您的实体在运行时将具有动态生成的代理类型,而不是在配置自动映射时传递的类型。

1
Automapper正是我开始研究这个的原因,但关闭动态代理可能会有一些不利影响。 - Jim Wolff
我已经开始厌恶动态代理的整个概念。首先,与代理相关的问题只会在运行时出现。通常,只有在生产环境中遇到非明显条件(这些条件通常会被测试人员忽略)时才会出现。其次,它是一个不完美的抽象,因为调用代码经常需要意识到代理的存在和特殊需求。我已经放弃了整个概念,关闭了它,并更新了我的设计——从此再也没有回头看过。动态代理应该被枪毙。 - CShark
3
朋友不应使用动态代理。 - CShark
我最终通常会在从数据库进行“只读”获取时关闭它,并在写入时保持打开(用于更改跟踪),但我测试了一下,如果没有代理,更改跟踪是否有效,结果是有效的。出于简单起见,可能会完全禁用它,以免在特殊的仅创建场景中使其他开发人员出现问题。 - Jim Wolff
你似乎朝着正确的方向前进。我以类似的方式在我的数据访问中采用了CQRS模式,而且效果很好。 - CShark

0

使用Automapper 4.2.1。新版本没有DynamicMap。

var parents = parentsRepo.GetAll().ToList();
Mapper.CreateMap<Parent,ParentDto>();
var parentsDto = Mapper.DynamicMap<List<ParentDto>>(parents);

0

虽然动态代理有一些不错的特性,但实际上它们可能会产生很多奇怪和难以理解的错误。

例如,在我的一个类中保留了一个实体的私有变量(它正在实现批处理),我正在循环遍历数百万条记录,批量处理并插入它们,每n条记录重新创建数据上下文以清除内存。尽管我从未使用过私有变量,EF仍将其链接到我的新对象上(通过导航属性引用),即使我只设置了引用ID。

这导致所有对象在整个进程运行期间都保留在内存中。我不得不使用AsNoTracking并禁用代理,以便进程按预期工作,并且内存和性能恢复到正常水平。请记住,代理还引用创建它们的上下文,这可能会保留大量的实体图形在内存中,几乎无法调试。

因此,我认为您应该全局禁用代理,并在小而受控制的代码片段中启用它们。当您有大型团队编码时,这种问题非常危险且无法调试。

更改跟踪很好,它可能会在某些地方证明使用的合理性。除非您知道自己在做什么,否则延迟加载可能会对性能和序列化造成巨大问题。我始终更喜欢急切或显式加载。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接