多列组成数据库关键字的对象最合适的设计是什么?

3
假设我在数据库中有一张表格,由以下列组成,其中3列唯一标识行:
CREATE TABLE [dbo].[Lines]
(
    [Attr1] [nvarchar](10) NOT NULL,
    [Attr2] [nvarchar](10) NOT NULL,
    [Attr3] [nvarchar](10) NOT NULL,
    PRIMARY KEY (Attr1, Attr2, Attr3)
)

现在,我的应用程序中有一个对象代表其中一行。它有三个属性,分别对应数据库中的三个Attr列。

public class Line
{
   public Line(string attr1, string attr2, string attr3) 
   {
        this.Attr1 = attr1;
        this.Attr2 = attr2;
        this.Attr3 = attr3;
   }

   public Attr1 {get; private set;}
   public Attr2 {get; private set;}
   public Attr3 {get; private set;}
}

该应用程序中还有第二个对象,存储了这些线条对象的集合。

问题是:当引用此集合中的单个线条时(从调用者的角度),最适合的设计是什么?调用者是否应负责跟踪正在更改的线的索引,然后只使用该索引直接修改集合中的线?或者......对象上是否应该有一些方法,以类似以下方式发出命令:

public GetLine(string attr1, string attr2, string attr3)
{
     // return the line from the collection
}

public UpdateLine(Line line)
{
     // update the line in the collection
}

我们的团队正在进行一场辩论,因为我们中有些人认为,在集合中引用一行时使用其内部索引更有意义,而其他人则认为没有必要引入另一个内部键,因为我们已经可以根据三个属性唯一地识别一行。
你怎么想?

2
如果我有选择并且有意义的话,我会选择“更改数据库以不具有复合主键”。 - Fredy Treboux
@Fredy - 我完全同意你的观点。对于具有聚簇外键的表进行维护教会了我,这种所谓的优雅并不值得。特别是如果其中任何字段具有用户指定的值。 - overslacked
有趣,那么你们也许会推荐使用自增整数键? - The Matt
@The Matt - 在你有聚集的外键的情况下,特别是如果任何字段值都是用户指定的,我完全同意。然而,这有点偏离了你实际问题的主题,所以我不想花太多时间在这上面。但是,主要思想是如果存在外键,你不能真正轻松地更新任何这些属性。此外,根据字段类型,如果你经常基于所有这些字段连接许多表,如果一个公共值集具有单个关键字段,你可能会看到改进。 - overslacked
6个回答

5
你的对象模型应该被设计得对对象消费者有意义。在最大程度上,它不应该与数据模型绑定。
听起来,以三个属性为基础思考更符合对象消费者的直觉。如果没有性能方面的问题阻碍,我会让对象消费者使用这些属性并不用关心数据存储的内部工作(即不要求他们知道或关心内部索引)。

3
我认为你遇到的基本问题是API用户对数据有多少控制权以及你要公开什么。这取决于你想做什么,两种方法都可以适用。
问题是,谁负责更新你想要更新的信息。从你发布的内容来看,Line对象负责信息,因此我建议使用Collection.GetLine(attr1, attr2, attr3).UpdateX(newX)等语法。
然而,可能集合实际上对该信息承担更大的责任,在这种情况下,Collection.UpdateX(line, newX)会更有意义(或者将“line”参数替换为“attr1,attr2,attr2”)。
第三,虽然可能性很小(并且在我看来很少是最佳设计),但API用户最负责信息,因此你提到的一种方法是用户处理Line索引并直接修改信息。

2
你不希望调用对象“跟踪他正在更改的行的索引” - 永远不要这样做。这会使你的设计过于相互依赖,将对象级别的实现决策推给对象的用户,使测试变得更加困难,并且当你意外更新一个对象(由于键重复)而实际上你想要更新另一个对象时,可能会导致难以诊断的错误。
回到面向对象的纪律:从 GetLine 方法返回的 Line 对象应该像一个真正的、一流的“东西”一样工作。
当然,问题在于如果更改了用作索引的行对象中的一个字段,则无法在数据库中找到原始值进行更新。好吧,这就是对象中的数据隐藏的全部含义,不是吗?
这是我的建议,对象中有三个与其在数据库中的状态相对应的不可变字段("originalAttr1"、“originalAttr2"、“originalAttr3")。同时有三个可设置的属性("attr1"、“attr2"、“attr3"),它们的初始值与原始值相同。你的 Getter 和 Setter 只适用于 attr 属性。当你“更新”(或执行其他返回底层源的操作)时,使用 originalAttrX 值作为你的键(连同唯一性检查等)。
这可能看起来有点麻烦,但与将所有这些实现决策推给对象的消费者相比,这算不了什么!然后你就会发现所有各种各样的消费者都试图以一致的方式(重复地)应用正确的逻辑 - 连同许多更多的测试路径。
还有一件事:在数据访问库中经常做这种事情,因此这是一种非常常见的编码模式。

1
当引用此集合中的单独一行时,从调用者的角度来看,最适合的设计是什么?
如果呼叫者正在以这三个属性为基础进行“思考”,我会考虑在您的集合类中添加一个索引器,该索引器以这三个属性作为键,例如:
public Line this[string attr1, string attr2, string attr3] {
   get { 
      // code to find the appropriate line...
   }
}

索引器是“如何从此集合中获取数据”的首选位置,而且在我看来,它们是任何集合的最直观访问器。


0

即使存在可以使用复合键的情况下,我总是更喜欢只使用单个列ID。我会在表中添加一个身份列,并将其用于查找。此外,由于查询单个int列比跨越三个文本列的键更快,因此查询速度更快。

让用户维护某种行索引来查找行对我来说似乎不太好。因此,如果我必须在两个选项之间选择,我会使用复合键。


0

如果客户端使用三个 string 值检索 Line 对象,那么您应该将这些值传递给 getter 方法。从那时起,更新对象在数据库中所需的所有内容(例如唯一行 ID)都应该隐藏在 Line 对象本身中。

这样,所有繁琐的细节都对客户端隐藏,这可以保护客户端免受损害,并且还可以保护客户端免受您在 Line 对象内进行的任何未来更改的影响。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接