【问题标题】:Model design in ASP.NET MVCASP.NET MVC 中的模型设计
【发布时间】:2011-04-21 13:07:24
【问题描述】:

我在 MVC 中的模型设计经常遇到问题。大多数情况下,我的所有项目都需要一些要创建、编辑和删除的实体。这些实体中的大多数都有一些共同的属性,如创建日期、修改日期、作者、修改者,在某些情况下甚至更具描述性的属性是常见的。因此,我有一个单独的数据库表来存储它们。例如,文档表,我们在其中存储发票、报价单和其他业务文档。我正在为 ORM 使用 Entity Framework v4,我们最终得到了 Document 实体。

我如何修改这个实体,或者我为它创建一个单独的 DocumentViewModel 类来支持具有公共属性的多种文档类型(因此应该实现某种形式的继承或接口实现)?除了识别不同的文档类型外,我还需要一些类型来拥有不同的数据注释规则(属性)。

例如,Document 表有 PayDate 列。文档类型 Invoice 需要提供 PayDate,但文档类型 Quotation 不需要。

这是我在使用 MVC 时反复面临的一个问题,到目前为止,我每次都在处理不同的问题,但无法决定如何正确处理它以实现最大的应用程序可维护性和易于开发。

【问题讨论】:

  • 请记住,您的模型不需要模仿您的数据库/表。事实上,模型的优点之一是它可以比数据库布局更接近地表示领域。仅仅因为您对数据库中的某些内容进行了规范化并不意味着它将继承自模型上的同一类。这通常是错误的方法。如果您的某些对象之间唯一的共同点是它们都有 5 个共同的属性,但规则和行为却大不相同,您可能会考虑让它们成为独立的类,而不是从同一个根继承。
  • 这是一个有效的观点,Hector,但我试图在遵循最佳实践和设计简单性之间取得平衡。似乎关注点分离和 DRY(不要重复自己)这两个原则经常相互矛盾。
  • 我不确定这里的任何人都可以为您提供有关如何设计域层的说明。只有对业务有充分了解的人才能做到这一点。这是你第一次做领域驱动设计吗?你过去做过OO吗?我对 Stackoverflow 的经验是,这里的小组非常擅长回答范围狭窄的问题,但对于广泛的需求来说却不是那么好。如果这是您第一次设计领域层,我会建议您从同事那里获得一些帮助或阅读有关该主题的内容。
  • 我希望我不会失礼 :) 我会推荐这本书:amazon.com/Domain-Driven-Design-Tackling-Complexity-Software/dp/…
  • 这不是我的第一次,但它是第一次使用 EF4,有些事情也依赖于此。我认为 RPM1984 我们已经走了很远,可能会接受他的回答。

标签: asp.net-mvc datamodel


【解决方案1】:

您可以多次在 EF 模型中放置一个表,然后将它们重命名为您需要的实体。只需从这些列中删除您不需要的列即可。

例如,放入 Documents 实体...现在将其重命名为 Invoice。现在添加另一个,并将其命名为 Quotation... 在 Quotation 上,单击 PayDate 并按删除键。您可以根据需要自定义这些!你可以对其他 ORM 做同样的事情,只是需要做更多的工作。使用 NHibernate,您将手动创建模型,然后将它们映射到同一个表,但只映射您需要的。

【讨论】:

  • 添加另一个是什么意思?我只能“从数据库更新模型..”并将每个表的一个实例添加为实体并建立关联。如果我想要它两次,我必须复制粘贴它然后重命名它。然后我可以照你说的做,但是这个实体没有建立关联,所以我必须手动建立它们,这对于很多相关实体来说是相当麻烦的。请解释
  • 好吧,我以为 EF 让这一切变得简单,但我错了……我想的是 Linq to SQL。在 EF 中,您必须打开 XML 并进行编辑才能完成这项工作。我知道这不是你想听到的,但是嘿,这东西并不简单,拥有一个拖放 GUI 来完成你所有的 ORM 映射将花费 MS 太多的钱。这就是为什么 EF 进展缓慢的原因。
  • 你在说什么,EF 被广泛接受为比 L2SQL 更成熟的 ORM,并且 GUI 与 L2SQL 相同(实际上更好)。映射是在 GUI 上完成的——我从来没有接触过 XML 文件(除了我在做一些棘手的 POCO 事情时)。
  • 好吧,我并不是说Linq2Sql更成熟,我只是指如何添加更多实体。你说 EF 比 Linq2Sql 好一点是对的,但恕我直言。
  • 在这种情况下,如果评论的作者想要实现这一点,他们将需要打开 XML。如果那不是真的......我知道这是因为我尝试了两种方式......那么请告诉我如何从同一个表中添加两个实体并设置 FK 关系。在 EF4 和 VS2010 中,您可以添加一个新实体并进行映射,但它似乎无法创建 FK .. 您必须为此打开 XML。这很臭……或者我做的不对……在你插话之前自己试试。如果你让它工作,请告诉步骤,因为这将有助于教育我和作者。
【解决方案2】:

您是否考虑过制作Documents实体抽象

从数据库方面,您将拥有 Documents 表,其中仅包含所有 Invoices/Quoations/etc 共享的字段。该字段将有一个 IDENTITY PK - 例如 DocId。

在其他表中,可以存储特定于该文档的附加元数据,并且 PK 是(非 IDENTITY)字段 DocId,它也是 Documents 表的 FK。

在 EF 端,Documents 变成了一个abstract 实体,其他实体继承自这个实体。这允许存在一个很好的 OO 范例,并使您的代码更加健壮。

我们目前正在使用这种方案(EF4/SQL Server)。

您的场景听起来与我们的非常相似 - 考虑使用抽象类。

编辑

我想我会添加更多信息来说明我是如何实际实现这个场景的,让你走上正确的轨道。

作为您 Q 状态的 cmets,我们对您的领域知之甚少,因此很难做出明智的意见。就个人而言,我选择让我的实体抽象,因为某些功能需要一次性返回一个“混合包”的项目。当然还有其他方法可以做到这一点(例如存储过程),但这允许在我的 UI(顺便说一下是 MVC)和我的服务层之间建立一个流畅的界面。

像这样工作 - 这是我获得单个帖子的方式:

// var is strongly-typed to a "Post"
var somePost = repository.FindSingle(10); 

这是我获得混合包帖子的方法:

// var is strongly-typed to a "ICollection<Post>".
// "Title" is a property on my "Post" abstract POCO
var mixedBagOfPosts = repository.FindAll<Post>(p => p.Title = "Some Title"); 

这是我获得“评论”集合(帖子的子项)的方法:

// var is strongly-typed to a "ICollection<Review>"
// "Rating" is a property on my "Review" POCO (derived from Post)
var reviews = repository.FindAll<Review>(r => r.Rating == 5.00);

Kicker 是我的存储库是用泛型实现的,类型参数确保类型安全:

ICollection<T> FindAll<T>(Expression<Func<T,bool>> predicate) where T : Post

它是这样实现的:

return myContext.Posts.OfType<T>.Where(predicate).ToList();

OfType 会导致 T(即子表)的内部连接,因此只返回那些记录。

当然,我也有一个服务层在我的 UI 和存储库之间进行调解,但这应该会让您走上正轨。

此外,您不必使用整个表达式谓词,我喜欢这样,因为它最大限度地减少了我的界面上的方法数量,并为我的控制器提供了完整的查询能力,同时确保查询是 延迟到服务层,但不会更远。

如果你不喜欢这样,你当然可以有常规参数(字符串标题等)。

正如我所说,这种架构适合我的领域需求,所以它可能不一定适合你,但希望它能给你一些见解。

【讨论】:

  • 1.因此,您通过将 Abstract 设置为 true 在 EDM 设计器中将实体设置为抽象? 2.您如何围绕这种设计构建数据访问存储库?您是否直接使用继承的类/实体?
  • 是的 - 您将“文档”设置为摘要(属性窗口),并将其他“发票”、“报价”的“基础”设置为“文档”实体。我已经使用泛型实现了一个存储库模式。需要解释很多(我的意思是很多),也许看看我问过的一些问题(因为我最近一直在处理这个问题),并在谷歌上窥探“EF Abstract Classes”。不过可以做到 - 我们现在正在做 - EF4、SQL 2008、存储库模式、POCO(带摘要)等。
  • 回答您的问题: 1. 是的。因为我使用 POCO,所以我的抽象实体也是一个抽象类。 2. 我的存储库方法看起来像这样ICollection&lt;T&gt; Find&lt;T&gt;(Expression&lt;Func&lt;T,bool&gt;&gt; predicate) where T : Post。 (帖子是我的抽象类)。所以我使用泛型。这使我可以返回一个“混合包”的类(因此您可以使用一个方法返回所有类型的文档)。它让您的应用更加面向对象。
  • 还有一个问题 - 考虑到这种设计,当您更改 DB 中的 Documents (Posts) 表的设计并在 EF 设计器中启动更新模型时,如果您有其他实体通过继承连接到文件(帖子)抽象实体?我可以(并且将)自己尝试一下,但目前我无法尝试,所以如果你能提供答案的话。有什么坏的吗?设计者是否将实体改回 abstract=False?谢谢
  • 不,它不会“破坏” :) 我已经多次更改了抽象表(在数据库端),并勾选了“更新模型” - 很好。请记住 - 这是一个 .NET/EF 的东西 - 与 SQL Server 无关。
猜你喜欢
  • 2013-05-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多