【问题标题】:Which pattern most closely matches scenario detailed and is it good practice?哪种模式与详细的场景最匹配,这是一种好的做法吗?
【发布时间】:2011-03-06 10:23:56
【问题描述】:

在过去的几年里,我已经多次看到一种特定的模式。请让我描述一下。

在 UI 中,每条新记录(例如,新客户详细信息)都存储在表单中,而不保存到数据库中。这显然已经完成,因此不会使数据库混乱或导致不必要的数据库命中。

在 UI 状态下,这些对象使用 Guid 进行标识。当这些被保存到数据库时,它们关联的 Guid 不会被存储。相反,它们被分配了一个数据库 Int 作为它们的主键。

表单可以处理从数据库中检索到的项目(使用 Int)以及尚未提交的项目(使用 Guid)的混合。

在检查表单(使用 Firebug)以查看使用了哪个键时,我们发现使用了一个由两部分分隔的组合键。第一部分是 guid(如果从数据库中提取,则为空 guid),第二部分是整数(如果不是从数据库中提取,则存储零)。由于组合键的一部分将始终唯一标识一条记录,因此效果很好。

这是好的做法吗?任何人都可以告诉我模式名称或建议一个(如果尚未命名)吗?

【问题讨论】:

  • 如果您可以就您的问题提供更多细节和清晰度,以便更好地理解。你让我在“检查时”那一行。 :)
  • 哦,是的,“检查时”是指使用萤火虫检查表单上的隐藏字段。在没有从数据库中检索到记录的情况下,字段值将读取例如“00000000-0000-0000-0000-000000000000,66”。而当数据库中已经存在一条记录时,它会显示一个类似“C4200000-0000-0000-0000-F00000000000,0”的值。
  • 哪些 ASP.NET 控件会产生此标记?
  • 哦,它只是一个 ASP.Net DropDownList。这些是值示例,而相关文本显示客户名称。该值被拆分为一个键值对,该键值对填充到两个属性中,ID (Int32) 和 Identity (Guid)。

标签: asp.net design-patterns webforms


【解决方案1】:

这里有几种模式在起作用。

Identity Field Pattern

在 EAA 的 P 中定义为“在对象中保存一个数据库 ID 字段,以维护内存中对象和数据库行之间的身份。”这部分很明显。

Transaction ScriptMetadata Mapping

一般来说,ASP.NET DataBound 控件使用类似于 Transaction Script 模式和 元数据映射 模式的东西。 Fowler 将元数据映射定义为“在元数据中保存对象-关系映射的详细信息”。如果您曾经编写过数据源控件,那么这种模式的元数据映射方面似乎很明显。

事务脚本模式“按程序组织业务逻辑,其中每个程序处理来自表示的单个请求。”为了封装维护表示状态和数据状态的逻辑,中间对象需要指明:

  1. 如果存在数据库记录
  2. 如何识别后端数据记录,填充 UI 控件
  3. 如果没有当前数据记录,如何识别数据和 UI 控件,以便可以从后端数据存储更新演示数据。

新客户数据条目Guid 和数据记录integer Id 的存在提供了足够的信息,只需一次调用数据库即可确定所有这些。这可以通过仅使用整数来完成(并且可能为每个未持久化的 UI 数据项提供一个唯一的负整数),但拥有两个单独的字段可能更明确。

好的做法还是坏的做法?

这取决于。 ASP.NET 是一个相当成功的软件项目,而且这种模式似乎一直有效。但是,这种类型的 ASP.NET Web 控件具有非常特定的应用范围 - 使用 简单映射 封装 UI 和数据库之间关于数据对象的交互。这些担忧似乎有点模糊,但对于许多适用的场景,这仍然是可以接受的。只要Row Data Gateway 可以接受,该模式就有效。如果有多个数据库行受 Web 控件影响,则此方法将不起作用。在这些更复杂的情况下,Active Record 实现或Domain ModelRepository 实现的组合将更适合。

一种模式是好的还是坏的做法实际上取决于应用它的场景。似乎人们倾向于提倡更复杂的设计结构,因为它们可以应用到更多的场景中而不会失败。 但是,在数据记录和 UI 之间直接映射的非常简单的应用程序中,这种模式非常有用,因为它创建了预期的结果,同时最大限度地减少了性能和开发开销。

【讨论】:

  • 这正是我所追求的那种回应。它强调了与模式的关系,而不是批评一种行之有效的务实方法。
  • @Carnotaurus,很高兴我能帮上忙。如果您有任何问题,请告诉我。
【解决方案2】:

我认为没有特定的模式。

这是好的做法吗?我不这么认为。首先,它不是非常面向对象的。怎么样:

interface ICommittable
{
    /// <summary>
    /// Gets or sets a value indicating whether the entity was already committed to the database.
    /// </summary>
    bool IsCommitted;

    /// <summary>
    /// Gets or sets the ID of the entity, used either in database or generated by UI or an underlying BL.
    /// </summary>
    Guid Id;
}

相反,他们所做的是以一种不明显的方式将三个单独的数据条目混合为一个

  • 身份证
  • 另一个 ID(为什么?)
  • 实体是否已提交的事实。

特别是,拥有两个单独的 ID 会非常令人困惑,不仅需要良好的文档,还需要一些时间让新开发人员了解这里发生的事情。

如果目的是在不向数据库查询新 ID 的情况下创建新实体,他们可以在任何地方使用 GUID:当创建新实体时,您 Guid.CreateNew 它的 ID,然后,如果需要,您提交所有内容,这GUID 也是数据库中的标识符(已经保存的 GUID 和新的 GUID 之间发生冲突的可能性很小,所以我不会在意)。

更简单,不是吗?

做一些事情也不容易。例如,您如何比较两个实体?请记住:

  • 具有不同 GUID 的两个已提交实体不相等,
  • 具有不同 ID 的两个未提交实体不相等,
  • 承诺实体可能等于未承诺实体,即使它们的 GUID 它们的 ID 不同。

总之,似乎缺乏重构。可能他们正在修改一个项目,其中实体已通过其id (int) 唯一键在数据库中标识,因此他们没有重构它,而是添加了 GUID,从而使整体事情:

  • 比较难理解,
  • 未来很难使用和修改。

【讨论】:

  • 它需要另一个 ID 来唯一标识它与已经提交的 ID。我认为使用一个 Int 类型的 ID 会比原来的 ID 更好,其中 ID 的符号将确定它是否已提交。
  • @Carnotaurus:使用有符号整数,这将是同样的事情,同样的问题:你将再次将三个数据条目混合为一个,我已经描述了所有问题。跨度>
  • 这个场景可以任意使用Int或者Guid类型。因为 EITHER 可以存储唯一值。
  • 我认为您在不了解业务用例的情况下谴责这种方法有点仓促(这是不可能的,因为 OP 不包含此信息)。如果您的目标是领域驱动的方法,那么肯定这行不通。但是,并非所有用例都如此复杂,这种模式在具有简单 UI 数据映射的应用程序中是有效的。此外,使用这种方法可以直接进行实体的相等比较,因为客户端 Guid 和数据库 int Id 在每个事务上都会更新。
  • 您似乎在提倡类似 Row Data Gateway 之类的东西,但这需要您的ICommittable 的一系列强类型实现,这会毫无理由地产生更多工作。断言它“不是非常面向对象是没有根据的”,因为我们对底层模型一无所知,只知道通过 HTTP 请求从 UI 发送的数据。据我们所知,适配器(Web 控件对象)可以实现您定义的接口。它只是没有在数据模型类中实现,因为映射很简单,没有必要。
【解决方案3】:

如果我没记错的话,就是存储库模式:http://martinfowler.com/eaaCatalog/repository.html

这在 Evans 领域驱动设计一书中对此进行了很好的描述,并且已被证明在特定情况下运行良好。

【讨论】:

  • 我看不出有任何理由将所描述的场景归类为Repository 模式的实现。存储库处理域模型,这里没有。 UI 和数据存储之间存在直接通信,这从根本上与域驱动方法相反。
猜你喜欢
  • 2021-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-14
相关资源
最近更新 更多