如果数据库中的列发生变化,有没有办法不破解代码?

时间:2009-12-18 16:18:36

标签: c#

假设我有一个声明的数据表,并且我已经分配了一个从调用存储过程返回的结果,所以现在,我的数据表在从中访问行时包含类似下面的内容:

string name = dr["firstname"];
int age = (int)dr["age"];

如果firstname更改为first_name并且删除了age,则代码显然会中断,因为现在架构已损坏,所以有没有办法在不手动执行的情况下始终使架构与代码保持同步?是否有某种元描述文件描述数据库表中的列并相应地更新它们?这是LINQ因其强类型特性而有用的情况吗?

9 个答案:

答案 0 :(得分:9)

  1. 如果按列名称选择好的旧式视图,它们总是以指定的顺序输出具有指定名称的列。如果下面的表需要更改,则视图会在必要时进行修改,但仍会像在基础表更改之前那样输出 - 就像对象的接口一样。应用程序引用视图而不是表,并继续正常工作。这归结为标准数据库应用程序设计,应该在任何(甚至基本的)数据架构师课程中讲授 - 但我很少看到这些实际应用于业务应用程序。事实上,我目前正在研究的项目是我第一次看到这种方法,并且实际上看到它使用得很清楚。

  2. 使用存储过程,如果表更改,则修改存储过程以使输出仍然相同 - 以类似的方式使用以屏蔽应用程序与基础表,从而使应用程序与任何表更改隔离。如果您希望在视图更合适的情况下进行动态连接,过滤和聚合,那就不够了。

  3. 如果要在应用程序端执行此操作,请在查询中指定要查询的字段的名称,而不是使用“select *”并依赖字段名称存在。但是,如果表格上的字段名称发生变化,或者某列被删除,那么您仍然会被卡住,您必须修改您的查询。

  4. 如果字段名称​​将更改,但所有字段将始终存在,则这些字段的内容将保持不变,并且字段将保持相同的顺序,可以按索引而不是按名称引用字段。

  5. 使用对象关系映射器,正如其他人指定的那样,但我不认为这必然会教导好的设计,而不是希望框架的设计足够好并适合您正在做的事情,这可能是可能并非如此。我并不认为这是一个很好的方法。

答案 1 :(得分:5)

关于防止这种情况的唯一方法是使用存储过程选择列并将它们重命名为返回给应用程序的标准名称。但是,这确实为数据库添加了另一层维护。

答案 2 :(得分:4)

这就是创建NHibernate等ORM解决方案的原因。

基于数据库架构的代码生成器。

答案 3 :(得分:4)

为什么不想更改代码?如果删除年龄,您为什么还想尝试在代码中抓取它?

Linq所做的是尝试将所有业务逻辑保留在一个位置(源代码),而不是在数据库和源代码之间进行拆分。

您应该在删除数据列时更改代码。

答案 4 :(得分:4)

正如您从所有给出的答案中可以看出的那样,您所寻找的东西并不存在。这样做的原因是您应该记住程序本质上是数据处理例程,因此您无法在不更改程序中的其他内容的情况下更改数据。如果它不是列的名称但它的类型正在改变怎么办?或者如果列被删除会发生什么?

总而言之,这些问题并没有很好的解决方案。数据是应用程序不可或缺的一部分 - 如果它发生变化,至少需要一些工作。但是,如果您希望更改名称(例如,数据库不是您的,并且主人已通知您将来可能会更改其名称),并且您不希望重新部署该应用程序,因为其中,替代重新编译您的源代码,如其他答案所述,包括:

  • 使用存储过程
    • 您可以使用存储过程向应用程序提供数据。在建议的更改(重命名列)的情况下,DBA或者负责数据库模式的人也应该更改存储过程。
    • 优点:由于数据库中的细微更改,无需重新编译
    • 缺点:现在成为应用程序设计一部分的更多工件,应用程序理解模糊不清。
  • 使用映射文件
    • 您可以创建一个映射文件,该文件为您提供应用程序期望某个列具有的名称以及该列具有的实际名称。这样既便宜又容易。
    • 优点:由于数据库中的细微更改,无需重新编译
    • 缺点:您的设计中的额外实体(类),应用程序理解模糊,您需要在更改时重新部署映射文件。
  • 使用列位置而不是列名
    • 使用位置参数(dr [1]),而不是引用列的名称。
    • 优点:让您免受名称更改的影响。
    • 缺点:其他一切。如果您更改表以容纳更多数据(新列),列的编号也可能会更改,如果删除任何列,您也会遇到编号问题等。

但是一个建议。不是直接通过文字访问列,而是使用具有一些良好命名标准的常量。所以

string name = dr["firstname"];
int age = (int)dr["age"];

变为

private const string CUSTOMER_COLUMN_FIRST_NAME = "firstname"
private const string CUSTOMER_COLUMN_AGE = "AGE"

string name = dr[CUSTOMER_COLUMN_FIRST_NAME];
int age = (int)dr[CUSTOMER_COLUMN_AGE];

这并不能解决您的问题,但它可以让您为代码添加更好的含义(即使您决定缩写常量的名称)并更容易更改名称,因为它是集中的。而且,如果需要,Visual Studio可以生成一个静态定义数据库行的类(继承自DataTable),这也使代码语义更加清晰。

答案 5 :(得分:2)

显然,您必须在数据库和应用程序之间引入另一层抽象。是的,这个层可以是Linq2Sql,Entity Framework,NHibernate或任何其他ORM(对象关系映射)框架。
现在关于'自动'......也许这种小改变(重命名列)可以由一些工具/框架自动处理。但我认为任何框架都不能保证自动正确处理更改。在许多情况下,您必须手动在数据库和新图层之间进行“映射”,以便您可以保持应用程序的其余部分不受影响。

答案 6 :(得分:0)

是,使用存储过程进行所有访问,并将表中的实际属性名称别名输出到客户端代码...然后,如果表中的实际列名更改,则只需更改存储中的sql proc,并保留别名与它们相同,客户端代码可以保持不变

答案 7 :(得分:0)

删除“age”时无效,但如果您知道列将始终以相同的顺序返回 - 即使名称发生更改,您也可以按列名引用它们,例如:

string name = dr[0];
int age = (int)dr[1];

根据您的数据库版本,您还可以查看数据访问生成器,例如SubSonic

答案 8 :(得分:0)

  

如果数据库中的列发生变化,有没有办法不破解代码?

没有

这是一个非常非常好的事情,你不能(完全)自动完成这个。

如果数据库发生更改,导致应用程序功能不再有效,则如果数据库不再支持该功能,则不希望应用程序继续公开该功能。

在最好的情况下,您希望数据库更改导致代码不再编译,因此您可以在编译时而不是运行时捕获问题。 Linq将帮助您在编译时捕获这些问题,还有许多其他方法可以提高代码库的敏捷性,以便可以在整个代码库中快速传播数据库更改。是的,ORM可以帮助解决这个问题。虽然视图和存储过程可能会使问题变得更好,但它们也可能通过增加需要对表中列的更改做出反应的代码的复杂性和数量而使问题变得更糟。

当您的应用程序和数据库不同步时,使用某种代码生成来生成(至少某些部分)数据层是获得编译时错误的最佳选择。您可能还应该在数据层周围进行单元测试,以便在编译时很难找到错误时检测尽可能多的运行时类型不一致(例如,列上的大小限制等)。