将EntityObjects用作模型(MVC)是一种好习惯吗?

时间:2012-10-11 11:38:37

标签: c# asp.net-mvc entity-framework asp.net-mvc-4 entity-framework-5

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

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

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

所以我质疑使用DTO的理由。使用DTO的原因是什么?在MVC模型中使用EntityObject有多糟糕?如上所述,您建议采用哪种其他解决方案来实现乐观并发处理?

5 个答案:

答案 0 :(得分:10)

=发表评论为answer =

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转移 - >员工 - > 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实体的 ok 使用示例:

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实体的 awesome 使用示例:

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

查克诺里斯如何做到:

return View(EmployeeViewModel.Build(employeRepository.GetFirstEmployee()));

答案 1 :(得分:7)

我只会在将EntityObject直接传递给视图时看到错误点:

  • 您需要进行手动白名单或黑名单以防止over-posting and mass assignment
  • 从视图中意外延迟加载额外数据变得非常容易,导致选择N + 1个问题
  • 在我个人看来,模型应该密切关注视图上显示的信息,在大多数情况下(基本的CRUD内容除外),视图包含来自多个EntityObject的信息

答案 2 :(得分: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类,数据仍然必须呈现给客户端并以更基本的形式发回。因此,一旦您在客户端上,任何并发支持都不是真正相关的。

答案 3 :(得分:1)

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

如果您将它们设计为平坦的,那么它们会更好,更容易实现以下目标:

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

但是如果你不需要任何这个,那么你可能就是没有。

答案 4 :(得分:0)

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

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

我将提供自己的解决方案,并就您是否认为这是一个好主意征求您的意见。

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

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

现在,假设我有一个名为“Customer”的EntityObject,其中包含“ID”和“名称”的属性。我会像这样创建一个DTO类:

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

在这里没有涉及过多的推测代码,我会将代码封装在GenericDTO类中,该类使用反射将值复制到EntityObject和从LoadValue复制值,在加载时设置ID,并验证保存时它没有改变。如果您确定每个表都有一个ID字段,那么您可以更聪明地将{{1}}属性放在基类中。

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