【问题标题】:How can an object-oriented programmer get his/her head around database-driven programming?面向对象的程序员如何才能理解数据库驱动的编程?
【发布时间】:2010-10-03 15:41:16
【问题描述】:

我已经使用 C# 和 Java 编程一年多了,并且对面向对象编程有相当的了解,但是我的新项目需要一个数据库驱动的模型。我正在使用 C# 和 Linq,这似乎是一个非常强大的工具,但是我在围绕面向对象的方法设计数据库时遇到了麻烦。

我的两个主要问题是:

如何处理数据库中的继承问题? 假设我正在构建一个员工排班应用程序,并且我有一个抽象类 Event。从 Event 我派生出抽象类 ShiftEvent 和 StaffEvent。然后我有具体的类 Shift(派生自 ShiftEvent)和 StaffTimeOff(派生自 StaffEvent)。还有其他派生类,但为了论证,这些就足够了。

我应该为 ShiftEvents 和 StaffEvents 设置一个单独的表吗?也许我应该为每个具体类设置单独的表格?这两种方法似乎都会在与数据库交互时给我带来问题。另一种方法可能是拥有一个 Event 表,并且该表对于我的任何具体类中的每种类型的数据都有可为空的列。所有这些方法都感觉可能会阻碍未来的可扩展性。很可能还有第三种我没有考虑过的方法。

我的第二个问题:

如何以面向对象的方式处理集合和一对多关系?

假设我有一个 Products 类和一个 Categories 类。类别的每个实例都将包含一个或多个产品,但产品本身不应该知道类别。如果我想在数据库中实现这一点,那么每个产品都需要一个映射到类别表的类别 ID。但是从 OO 的角度来看,这引入了比我更喜欢的耦合。产品甚至不应该知道类别存在,更不用说包含类别 ID 的数据字段了!有没有更好的办法?

【问题讨论】:

  • 这是一个很好的问题。你会得到很好的答案,但很少有人赞成。这就是我们在 SO 上滚动的方式。
  • 他是对的.. 4 年后,只有 22 票。这个问题值得更好。

标签: sql linq database-design oop


【解决方案1】:
【解决方案2】:

Linq to SQL 使用每个类的表解决方案:

http://blogs.microsoft.co.il/blogs/bursteg/archive/2007/10/01/linq-to-sql-inheritance.aspx

其他解决方案(例如我最喜欢的 LLBLGen)允许其他模型。就个人而言,我喜欢带有鉴别器列的单表解决方案,但这可能是因为我们经常查询继承层次结构,因此将其视为普通查询,而查询特定类型只需要“where”更改。

总而言之,我个人认为将 OO 映射到表中是本末倒置。一直有人声称 OO 和关系之间的阻抗不匹配已经解决......并且已经有大量的 OO 特定数据库。它们都没有取代这种关系的强大简单性。

相反,我倾向于在设计数据库时考虑到应用程序,将这些表映射到实体并从那里构建。有些人认为这是在设计过程中失去了 OO,但在我看来,数据层不应该对您的应用程序进行足够高的讨论以影响高阶系统的设计,因为您使用了关系模型 用于存储

【讨论】:

    【解决方案3】:

    我的猜测是我的头顶:

    关于继承的话题,我建议有 3 个表:Event、ShiftEvent 和 StaffEvent。事件具有类似于它最初定义的通用数据元素。

    我认为最后一个可以走另一条路。您可以有一个包含类别 ID 和产品 ID 的表,其中没有其他列,对于给定的类别 ID,这将返回产品,但产品可能不需要获取类别作为其描述自身的一部分。

    【讨论】:

      【解决方案4】:

      一个大问题:你怎么能理解它?只需要练习。您尝试实现数据库设计,遇到设计问题,重构并记住下一次哪些有效,哪些无效。

      回答您的具体问题...这是一些意见,例如“我将如何做”,没有考虑性能需求等。我总是从完全标准化开始,然后根据实际测试从那里开始:

      Table Event
      EventID
      Title
      StartDateTime
      EndDateTime
      
      Table ShiftEvent
      ShiftEventID
      EventID
      ShiftSpecificProperty1
      
      ...
      
      Table Product
      ProductID
      Name
      
      Table Category
      CategoryID
      Name
      
      Table CategoryProduct
      CategoryID
      ProductID
      

      同时重申 Pierre 所说的 - 像 Hibernate 这样的 ORM 工具可以更好地处理关系结构和 OO 结构之间的摩擦。

      【讨论】:

        【解决方案5】:

        为了将继承树映射到关系模型有多种可能性。 例如,NHibernate 支持“每个类层次结构的表”、每个子类的表和每个具体类的表策略: http://www.hibernate.org/hib_docs/nhibernate/html/inheritance.html

        关于你的第二个问题: 您可以在数据库中创建一个 1:n 关系,其中 Products 表具有指向 Categories 表的外键。 但是,这并不意味着您的 Product Class 需要引用它所属的 Category 实例。 您可以创建一个 Category 类,其中包含一组产品或产品列表,并且您可以创建一个产品类,它没有所属 Category 的概念。 同样,您可以使用 (N)Hibernate 轻松做到这一点; http://www.hibernate.org/hib_docs/reference/en/html/collections.html

        【讨论】:

          【解决方案6】:

          听起来您正在发现Object-Relational Impedance Mismatch

          【讨论】:

          • 这里有医生再次描述您的病情!
          【解决方案7】:

          产品甚至不应该知道这一点 类别存在,更不用说有 包含类别 ID 的数据字段!

          我不同意这里,我认为不是提供类别 ID,而是让你的 orm 为你做。然后在代码中你会有类似的东西(借用 NHib 和 Castle 的 ActiveRecord):

          class Category
            [HasMany]
            IList<Product> Products {get;set;}
          

          ...

          class Product
            [BelongsTo]
            Category ParentCategory {get;set;}
          

          然后,如果您想查看您的产品属于哪个类别,您只需执行以下简单操作:

          Product.ParentCategory
          

          我认为您可以以不同的方式设置 orm,但是对于继承问题,无论哪种方式,我都会问...您为什么在乎?要么处理对象而忘记数据库,要么以不同的方式进行。可能看起来很傻,但除非你真的不能拥有一堆表,或者出于某种原因不想使用单个表,否则你为什么要关心数据库?例如,我有几个继承对象的相同设置,我只是去做我的事。我还没有查看实际的数据库,因为它与我无关。底层 SQL 是我所关心的,以及返回的正确数据。

          如果您必须关心数据库,那么您将需要修改您的对象或提出自定义的处理方式。

          【讨论】:

          • "底层 SQL 是我所关心的,并且正确的数据会返回" 你这是什么意思?你在看 ORM 生成的 SQL 吗?不是自动生成的吗?
          【解决方案8】:

          我想在这里有点实用主义会很好。对象和表之间的映射总是在这里和那里有点奇怪。这是我的工作:

          我使用 Ibatis 与我的数据库通信(Java 到 Oracle)。每当我有一个希望将子类存储在数据库中的继承结构时,我都会使用“鉴别器”。这是一个技巧,您可以为所有类(类型)提供一个表,并拥有您可能想要存储的所有字段。表中有一个额外的列,包含一个字符串,Ibatis 使用该字符串来查看它需要返回哪种类型的对象。

          它在数据库中看起来很有趣,有时可能会让您陷入与并非所有类中的字段的关系的麻烦,但在 80% 的情况下,这是一个很好的解决方案。

          关于类别和产品之间的关系,我会在产品中添加一个 categoryId 列,因为这会让生活变得非常轻松,无论是 SQL 还是 Mapping。如果你真的坚持做“理论上正确的事情”,你可以考虑一个额外的表,它只有 2 个列,连接类别及其产品。它会起作用,但通常这种结构仅在您需要多对多关系时使用。

          尽量保持简单。有一个“学术解决方案”很好,但通常意味着有点矫枉过正,而且因为太抽象而难以重构(比如隐藏类别和产品之间的关系)。

          我希望这会有所帮助。

          【讨论】:

            【解决方案9】:

            我遇到了相反的问题:经过多年的数据库设计,我如何理解 OO。谈到这一点,十年前,经过多年的“结构化”平面文件编程,我遇到了对 SQL 的理解问题。类和数据实体分解之间有足够多的相似之处,从而误导您认为它们是等价的。他们不是。

            我倾向于同意这样一种观点,即一旦您致力于使用关系数据库进行存储,那么您应该设计一个规范化模型并在不可避免的情况下破坏您的对象模型。这是因为与使用自己的代码相比,DBMS 对您的限制更大 - 构建受损数据模型更容易让您感到痛苦。

            也就是说,在给出的示例中,您可以选择:如果 ShiftEvent 和 StaffEvent 在属性方面大多相似并且通常作为事件一起处理,那么我倾向于实现一个带有类型列的单个事件表.单表视图可以是分离子类的有效方法,并且在大多数数据库平台上都是可更新的。如果这些类在属性方面更加不同,那么每个类的表可能更合适。我不认为我喜欢三表的想法:在关系设计中很少需要“有一个或没有”关系。无论如何,您始终可以创建一个事件视图作为两个表的并集。

            对于产品和类别,如果一个类别可以有多个产品,但反之不行,那么表示这一点的正常关系方式是产品包含一个类别 ID。是的,是耦合,但也只是数据耦合,不是死罪。该列可能应该被索引,以便检索一个类别的所有产品是有效的。如果你真的被这个概念吓坏了,那就假装它是一个多对多的关系,并使用一个单独的 ProductCategorisation 表。这没什么大不了的,尽管它暗示了一种实际上并不存在的潜在关系,并且可能会误导将来使用该应用程序的人。

            【讨论】:

              【解决方案10】:

              在我看来,这些范式(关系模型和 OOP)适用于不同的领域,因此很难(而且毫无意义)尝试在它们之间创建映射。

              关系模型是关于表示事实(例如“A 是一个人”),即具有“独特性”属性的无形事物。谈论同一事实的几个“实例”是没有意义的 - 只有 事实。

              面向对象编程是一种编程范式,详细说明了一种构建计算机程序以满足特定标准(重用、多态性、信息隐藏......)的方法。 object 通常是一些有形事物的隐喻——汽车、引擎、经理或人等。有形事物不是事实——可能有两个具有相同状态的不同对象,但它们不是相同的对象(例如,Java 中 equals 和 == 之间的区别)。

              Spring 和类似工具以编程方式提供对关系数据的访问,因此事实可以由程序中的对象表示。这并不意味着 OOP 和关系模型是相同的,或者应该相互混淆。使用 Realational Model 设计数据库(事实集合)和 OOP 设计计算机程序。

              TL;DR 版本(对象-关系阻抗失配提炼):

              事实 = 冰箱上的食谱。 物品 = 你冰箱里的东西。

              【讨论】:

              • Eek 在这里写的一切都是真实的,但我还要指出,某些数据库(如 PostGre)允许定义自定义类型,让您将对象存储为这些事实中的“名词”。跨度>
              • 事实可能没有实例,但表有。
              【解决方案11】:

              在处理面向对象的方法之前,我还必须了解数据库设计、SQL,尤其是以数据为中心的世界观。对象关系阻抗不匹配仍然让我感到困惑。

              我发现最接近它的方法是:不是从面向对象编程的角度,甚至从面向对象的设计角度,而是从面向对象的分析角度来看待对象。我得到的关于 OOA 的最好的书是 Peter Coad 在 90 年代初写的。

              在数据库方面,与 OOA 比较的最佳模型不是数据的关系模型,而是实体关系 (ER) 模型。 ER 模型不是真正的关系模型,也没有指定逻辑设计。许多关系辩护者认为这是 ER 的弱点,但实际上是它的优势。 ER 最好不要用于数据库设计,而是用于数据库的需求分析,也称为数据分析。

              ER 数据分析和 OOA 令人惊讶地相互兼容。 ER 反过来与关系数据建模相当兼容,因此也与 SQL 数据库设计兼容。当然,OOA 与 OOD 兼容,因此也与 OOP 兼容。

              这似乎是一个漫长的过程。但是如果你把事情保持得足够抽象,你就不会在分析模型上浪费太多时间,而且你会发现克服阻抗不匹配非常容易。

              在学习数据库设计方面要克服的最大问题是:您在问题中反对的外键到主键链接之类的数据链接一点也不可怕。它们是将相关数据捆绑在一起的本质。

              在预数据库和预面向对象系统中存在一种现象,称为涟漪效应。连锁反应是对大型系统的看似微不足道的更改最终导致整个系统所需的更改。

              OOP 主要通过封装和信息隐藏来包含连锁反应。

              关系数据建模主要通过物理数据独立性和逻辑数据独立性来克服连锁反应。

              从表面上看,这两种思维方式似乎是根本矛盾的。最终,您将学会如何充分利用这两者。

              【讨论】:

                猜你喜欢
                • 2013-01-08
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-02-20
                • 2020-07-17
                • 1970-01-01
                • 2011-05-25
                相关资源
                最近更新 更多