12

我正在使用 Entity Framework 5 构建我的第一个 MVC 4/Razor Web 应用程序,并在做出任何设计决策之前做一些功课。

我看到 EF 对象来自EntityObject,它似乎有很多有用的最佳实践东西构建它,尤其是乐观并发处理。换句话说,如果两个人同时加载 123 Maple Street 的 Jane Doe 的记录,第一个将她的名字更改为 Jane Smith,第二个将她的地址更改为 321 Maple Street,那么很容易让两个更改合并没有冲突的情况下进入记录,而第二个用户尝试修改与第一个用户相同的字段将导致错误。

另一方面,创建轻量级数据传输对象以在服务器和客户端之间传递数据并用作 MVC 框架的模型或在 MVC 框架中充当模型似乎是相当标准的做法。这对于确保客户端的最小流量非常有用,但它会破坏并发检查。

所以我质疑使用 DTO 的理由。使用 DTO 的原因是什么?在 MVC 模型中使用EntityObjectas 或在 MVC 模型中使用有多糟糕?如上所述,您会提出什么其他解决方案来启用乐观并发处理?

4

5 回答 5

10

=发表评论作为答案=

EF 对象自几个版本以来就是 POCO(不确定是哪个版本)。如果你想要一个“EntityObject”,你必须使用某种适配器(我相信有一个适配器可以促进应用程序迁移,但我不建议将它用作新项目的一部分)。

如果您的模型类具有与 EF 相关的方法,那么这样做真的很糟糕。但 EF 5 不应该。我相信从 4.1 开始,他们使用 Proxies 而不是扩展 EntityObject 正是出于这个原因 - 将它们用作模型是一种很好的做法。

只需查看您的 .tt 和生成的 .cs 文件。它们是普通的 POCO。没有接口,没有基类。如果您从实体框架中获取一个对象并检查该对象的类型,您会发现类似System.Data.Entity.DynamicProxies.Employee_5E43C6C196[...]. 这是代理生成的类。但是,如果您执行完全相同的操作,但在 ( dbContext.Configuration.ProxyCreationEnabled = false;) 之前更改了数据库上下文配置,那么您已经为自己赢得了一个不错的 Employee 实体!

因此,要回答原始问题,将 EF POCO 用作模型是完全可以接受/良好的做法,但请确保将它们用作非持久对象。

附加信息

您应该考虑 DDD 概念和 DDD 兼容模式的实现,例如存储库或任何您觉得可以使用的东西。

您永远不应该在视图中直接使用这些实体,无论是持久的还是非持久的。

您应该阅读 AutoMapper 以使您的生活更轻松(与存储库或独立的很好地配合)。它将促进从 ProxyEmployee -> Employee -> ViewModel 的转移,反之亦然。

EF 实体的可怕用法示例:

return View(dbContext.employees.First());

EF 实体的错误 #1用法示例:

Employee e = dbContext.employees.First();
return View(new Employee { name = e.name, [...] });

EF 实体的错误 #2用法示例:

Employee e = dbContext.employees.First();
return View(new EmployeeViewModel{ employee = e });

EF 实体的正确用法示例:

Employee dbEmploye = dbContext.employees.First();
Employee e = new Employee { name = dbEmploye.name, [...] };
return View(new EmployeeViewModel { employee = e });

良好使用 EF 实体的示例:

Employee e = dbContext.employees.First();
EmployeeViewModel evm = Mapper.Map<Employee, EmployeeViewModel>(e);
return View(evm);

EF 实体的绝妙用法示例:

Employee e = employeRepository.GetFirstEmployee();
EmployeeViewModel evm = Mapper.Map<Employee, EmployeeViewModel>(e);
return View(evm);

Chuck Norris是如何做到的:

return View(EmployeeViewModel.Build(employeRepository.GetFirstEmployee()));
于 2012-10-11T13:39:32.033 回答
7

EntityObject直接将 an 传递给视图时,我只看到坏点:

  • 您需要手动添加白名单或黑名单,以防止过度发布和批量分配
  • 很容易不小心从视图中延迟加载额外数据,导致选择 N+1 问题
  • 在我个人看来,模型应该与视图上显示的信息非常相似,并且在大多数情况下(基本的 CRUD 内容除外),视图包含来自多个EntityObject
于 2012-10-11T11:46:17.127 回答
3
On the other hand, it seems pretty standard practice to create lightweight Data Transfer Objects to pass data between the server and the client, and which serve as or in models for the MVC framework. This is great for ensuring minimal traffic to the client, but it screws up concurrency checking

在这里,您似乎在谈论通过网络流向浏览器的内容。从这个意义上说,即使您在控制器中使用 EntityObject 类,数据仍然必须呈现给客户端并以更基本的形式回传。因此,一旦您在客户端上,任何并发支持都不是真正相关的。

于 2012-10-11T11:46:33.560 回答
1

Dtos 有几个优点,但取决于您的计划。

如果您将它们设计为扁平的,那么它们对于以下情况会更好、更容易:

  • 映射到视图模型
  • 将这些 dto 对象缓存为您的域对象可能有一个大图并且不适合放入缓存。Dtos 可以为您提供关于缓存内容和方式的良好粒度
  • 简化api 签名并防止代理对象意外延迟加载
  • 通过网络发送数据,阅读关于剥离器模式的信息

但是,如果您不需要任何这些,那么您可能就不需要了。

于 2012-10-11T11:45:38.357 回答
0

基于到目前为止的所有答案似乎都表明:

  • 将 s推EntityObject送到客户端是一种不好的做法,主要原因是您冒着意外延迟加载大量相关数据的风险
  • DTO 破坏了您进行乐观并发检查的能力

我将提供我自己的解决方案,并邀请您评论您是否认为这是一个好主意。

我们没有将一个普通的 DTO 传递给客户端,而是创建一个抽象GenericDTO<T>类,其中T表示一个EntityObject. 我们还创建了一个GenericDTOProperty<T>类,它有两个属性:

public class GenericDTOProperty<T> {
  public T LoadValue { get; internal set; }
  public T Value { get; set; }
}

现在,假设我有一个EntityObject名为“客户”,具有“ID”和“名称”的属性。我会像这样创建一个 DTO 类:

public class CustomerDTO : GenericDTO<Customer> {
  public GenericDTOProperty<int> ID;
  public GenericDTOProperty<string> Name;
}

在这里不涉及太多推测性代码,我将在类中封装代码,该类GenericDTO使用反射将值复制到. 如果您确定每个表都有一个 ID 字段,您可以更聪明地将属性放在基类中。EntityObjectLoadValueID

这看起来像是一个合理的模式吗?看起来很简单——几乎太简单了……这让我担心它可能有问题,否则它实际上应该是框架的一部分?

于 2012-10-11T12:41:15.773 回答