我已经用C#和Java编程了一年多一点,并且对面向对象编程有很好的把握,但是我的新项目需要一个数据库驱动的模型。我正在使用C#和Linq,它似乎是一个非常强大的工具,但我在设计面向对象方法的数据库方面遇到了麻烦。
我的两个主要问题是:
如何处理数据库中的继承? 假设我正在建立一个员工排班应用程序,我有一个抽象类,事件。从Event I派生出抽象类ShiftEvent和StaffEvent。然后我有具体的类Shift(派生自ShiftEvent)和StaffTimeOff(派生自StaffEvent)。还有其他派生类,但为了论证,这些就足够了。
我是否应该为ShiftEvents和StaffEvents提供单独的表?也许我应该为每个具体类别都有单独的表格?这两种方法似乎都会在与数据库交互时给我带来问题。另一种方法可能是拥有一个Event表,并且该表对于我的任何具体类中的每种类型的数据都有可为空的列。所有这些方法都觉得它们可能阻碍可扩展性。很可能还有第三种方法我没有考虑过。
我的第二个问题:
如何以面向对象的方式处理集合和一对多关系?
假设我有一个Products类和一个Categories类。每个类别实例都包含一个或多个产品,但产品本身应该不了解类别。如果我想在数据库中实现它,那么每个产品都需要一个映射到类别表的类别ID。但是从OO的角度来看,这引入了比我更喜欢的耦合。产品甚至不应该知道类别存在,更不用说包含类别ID的数据字段!还有更好的方法吗?
答案 0 :(得分:9)
使用每个类解决方案的表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的损失,但在我看来,数据层不应该高度说到应用程序中影响高阶系统的设计,只是因为你使用了关系模型用于存储。
答案 1 :(得分:7)
我遇到了相反的问题:经过多年的数据库设计,如何让我的头脑清醒。谈到这一点,十年前,我遇到了在经过多年“结构化”平面文件编程后逐渐掌控SQL的问题。类和数据实体分解之间有足够的相似之处,误导你认为它们是等价的。他们不是。
我倾向于同意这样的观点:一旦你致力于存储关系数据库,那么你应该设计一个规范化的模型,并在不可避免的情况下妥协你的对象模型。这是因为你使用DBMS比使用自己的代码更受限制 - 构建一个受损的数据模型更有可能让你感到痛苦。
也就是说,在给出的示例中,您有以下选择:如果ShiftEvent和StaffEvent在属性方面大致相似并且通常作为事件一起处理,那么我倾向于使用类型列实现单个事件表。单表视图可以是分离子类的有效方法,并且在大多数数据库平台上都是可更新的。如果类在属性方面更加不同,那么每个类的表可能更合适。我不认为我喜欢三桌的想法:“有一个或没有”关系在关系设计中很少需要。无论如何,您始终可以创建一个事件视图作为两个表的并集。
对于产品和类别,如果一个类别可以包含许多产品,反之则不然,那么表示此类别的正常关系方式是产品包含类别ID。是的,它是耦合,但它只是数据耦合,而且它不是致命的罪。该列可能应该被编入索引,因此检索类别的所有产品是有效的。如果你对这个概念感到非常恐惧,那么假装它是一个多对多的关系,并使用一个单独的ProductCategorisation表。这并不是什么大不了的事,虽然它意味着一种潜在的关系并不存在,并且可能误导将来的应用程序中的某些人。
答案 2 :(得分:6)
在我看来,这些范例(关系模型和OOP)适用于不同的域,使得尝试在它们之间创建映射变得困难(并且毫无意义)。
关系模型是关于表示事实(例如“A是一个人”),即具有“独特”属性的无形事物。谈论同一事实的几个“实例”是没有意义的 - 只有 事实。
面向对象编程是一种编程范例,详细描述了构建计算机程序以满足某些标准(重用,多态,信息隐藏......)的方法。 对象通常是某些有形事物的隐喻 - 汽车,引擎,经理或人等。有形的东西不是事实 - 可能有两个不同的对象具有相同的状态而没有它们是相同的对象(例如,在Java中等于和==之间的区别)。
Spring和类似工具以编程方式提供对关系数据的访问,因此事实可以由程序中的对象表示。这并不意味着OOP和关系模型是相同的,或者应该与彼此混淆。使用Realational Model设计数据库(事实集合)和OOP来设计计算机程序。
TL; DR版本(对象 - 关系阻抗不匹配):
事实=冰箱上的食谱。 物体=冰箱的内容。
答案 3 :(得分:4)
等框架
答案 4 :(得分:3)
在处理面向对象的方法之前,我还要了解数据库设计,SQL,特别是以数据为中心的世界观。物体 - 关系 - 阻抗 - 不匹配仍然让我感到困惑。
我发现最接近它的方法就是:不是从面向对象的程序设计角度来看对象,或者从面向对象的设计角度来看,而是从面向对象的分析角度来看。我得到的关于OOA的最好的书是在90年代早期由Peter Coad写的。
在数据库方面,与OOA进行比较的最佳模型不是数据的关系模型,而是实体 - 关系(ER)模型。 ER模型不是真正的关系型,它没有指定逻辑设计。许多关系辩护者认为这是ER的弱点,但实际上是它的力量。 ER最好不用于数据库设计,而是用于数据库的需求分析,也称为数据分析。
ER数据分析和OOA令人惊讶地相互兼容。 ER反过来与关系数据建模完全兼容,因此与SQL数据库设计完全兼容。当然,OOA与OOD兼容,因此与OOP兼容。
这似乎还有很长的路要走。但是,如果你保持足够的抽象,你就不会在分析模型上浪费太多时间,你会发现克服阻抗不匹配的难易程度。
在学习数据库设计方面,最重要的是:在您的问题中反对的主键连接的外键等数据链接并不可怕。它们是将相关数据捆绑在一起的本质。
前数据库和面向对象系统中存在称为涟漪效应的现象。涟漪效应是对大型系统的看似微不足道的变化最终导致整个系统的所需变更。
OOP主要通过封装和信息隐藏来包含涟漪效应。
关系数据建模主要通过物理数据独立性和逻辑数据独立性克服了连锁反应。
从表面上看,这两者似乎是根本上相互矛盾的思维方式。最后,您将学习如何使用它们以获得良好的优势。
答案 5 :(得分:1)
我的猜测偏离了我的头脑:
关于继承的话题,我建议有3个表:Event,ShiftEvent和StaffEvent。事件的公共数据元素与最初定义的类似。
我想,最后一个可以走另一条路。您可以拥有一个包含类别ID和产品ID但没有其他列的表格,对于给定的类别ID,这将返回产品,但产品可能不需要将该类别作为其描述方式的一部分。
答案 6 :(得分:0)
最大的问题是:你怎么能理解它?它只需要练习。您尝试实现数据库设计,遇到设计问题,重构并记住下次有效和无效的内容。
要回答你的具体问题......这是一点点意见,如“我将如何做”,不考虑性能需求等。我总是开始完全标准化,并根据实际测试从那里开始:
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结构之间的摩擦。
答案 7 :(得分:0)
将继承树映射到关系模型有几种可能性。 例如,NHibernate支持“每个类层次结构的表”,每个子类的表和每个具体类策略的表: http://www.hibernate.org/hib_docs/nhibernate/html/inheritance.html
对于你的第二个问题: 您可以在数据库中创建1:n关系,其中Products表具有类别表的外键。 但是,这并不意味着您的Product Class需要引用它所属的Category实例。 您可以创建一个Category类,其中包含一组或一组产品,您可以创建一个产品类,该类没有它所属的类别的概念。 再次,您可以使用(N)Hibernate轻松完成此操作; http://www.hibernate.org/hib_docs/reference/en/html/collections.html
答案 8 :(得分:0)
听起来你正在发现Object-Relational Impedance Mismatch。
答案 9 :(得分:0)
产品甚至不知道 这些类别存在,更不用说了 包含类别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是关于我的,以及正确的数据。
如果您不得不关心数据库,那么您将需要修改对象或提出自定义的处理方式。
答案 10 :(得分:0)
我想这里的一些实用主义会很好。对象和表之间的映射在这里和那里总是有点奇怪。这是我的所作所为:
我使用Ibatis与我的数据库(Java to Oracle)交谈。每当我有一个inheretance结构,我想要一个子类存储在数据库中时,我使用“鉴别器”。这是一个技巧,你有一个表用于所有类(类型),并拥有你可能想要存储的所有字段。表中有一个额外的列,包含一个字符串,Ibatis使用该字符串来查看它需要返回的对象类型。
在数据库中看起来很有趣,有时可能会让你遇到与不在所有类中的字段有关的问题,但有80%的时间这是一个很好的解决方案。
关于类别和产品之间的关系,我会在产品中添加categoryId列,因为这将使生活变得非常简单,无论是SQL智能还是映射。如果你真的坚持做“理论上正确的事情”,你可以考虑一个额外的表,只有2个列,连接类别及其产品。它可以工作,但通常这种结构仅在你需要多对多关系时使用。
尽量保持简单。拥有“学术解决方案”很不错,但通常意味着有点矫枉造成并且更难以重构,因为它过于抽象(比如隐藏了类别和产品之间的关系)。
我希望这会有所帮助。