当代码中的业务规则依赖于数据库查找值时的良好软件设计

时间:2011-11-30 14:49:37

标签: c# database design-patterns

有时我需要从数据库查找表中获取类似于以下内容的值:

Id | Description
----------------
1  | descriptive text for 1
2  | descriptive text for 2

Id vlaue通常存储在变量中:

private int DB_LookUp_Id = GetIdFromDB();

此DB_LookUp_Id值随后用于执行验证或其他业务逻辑,如:

if (DB_LookUp_Id == 1)
    return true

如果Id值从1更改为3

,此代码将中断

消除这个问题的好方法是什么?

由于

4 个答案:

答案 0 :(得分:2)

有一个基于代码的密钥和静态的数据库ID,我们倾向于使用Guids或枚举值:

Id | Key | Description
----------------
1  | 1   | descriptive text for 1
2  | 2   | descriptive text for 2

然后在代码中你可以:

enum DescriptiveTextParts
{
    Part1 = 1,
    Part2 = 2,
}

if (DB_LookUp_Key == DescriptiveTextParts.Part1)
    return true;

不确定这是否是“最佳做法”,但它对我们有用。应用程序并不总是需要知道或使用数据库主键,尽管它们大部分时间都是。

或者,如果它只是文本,则将其全部发送到资源文件中。这为您提供了本地化,编译时优势和性能优势。不利于数据库访问资源字符串(几乎没有访问权限),以及在不重新编译的情况下释放新字符串的能力。

答案 1 :(得分:2)

我见过的最佳实践需要数据库中更多的语义信息。将所有 reference data 放入数据库中,并使用多个表来规范化数据。

想象一下,在线订购系统需要了解客户所处的状态,并且需要知道哪些州是有效的送货地点。

1)您可能有一个名为STATES

的数据库表
  KEY | NAME
 --------------------
  AL  | Alabama
 --------------------
  AK  | Alaska
 --------------------
  PR  | Puerto Rico
 --------------------

2)创建一个引用数据对象以在数据库中包装一行。

class StateReference {
    string key
    string name
}

3)创建一个名为SHIP_LOC的运输地点关系表,并按州代码('WA','OR等)键入

  KEY | VALID_SHIP
 --------------------
  AL  | true
 --------------------
  AK  | true
 --------------------
  PR  | false
 --------------------

4)现在创建一个这样的方法:

bool isValidShippingLocation(StateReference object) {

     return dbLookup('SHIP_LOC', object.key)
}

这种方法的第一个优点是它将硬编码的值从代码中移出到数据库中,在数据库中,它们更容易找到并且更容易更改。此外,可以强制执行表之间的引用完整性,这有助于保证有效的结果。

此外,参考数据现在具有语义上有意义的名称。消除猜测源代码中的'1'或代码'X'意味着什么。

答案 2 :(得分:0)

这是一个糟糕的模式,因为它会对数据库中的C#代码产生依赖性,使得对数据库进行更改会对代码的运行方式产生意外后果。

有很多不同的方法,但在某些情况下它们是最合适的。例如,如果DB_LookUp_Id是主键并且存在有限数量的条目,则它可以很好地映射到枚举和派生类,并且最好通过工厂模式提供。如果DB_LookUp_Id可以跨越许多行并且将在应用程序的生命周期中继续增长,则包含该逻辑的类结构可能比派生类更通用。有很多可能性。

作为一般观点,我首先要看一下数据代表什么的性质以及随着时间推移该数据会发生什么。一旦您开始确定数据的行为方式(与其使用方式相反),您就可以更好地处理应用程序的这一方面。

答案 3 :(得分:0)

显然,如果这个数据库表只是作为一个字符串表来单独保存一些文本,那么另一个人就将其推送到资源等方面的说法是正确的答案。

但我怀疑情况并非如此。无论我们是否愿意,数据都可以并且确实对业务具有特殊意义。什么数据库没有名为STATUS的列,其中值为“P”表示挂起,“O”表示打开,“C”表示已关闭。您可以将这些值放入带有CODE和DESCRIPTION列的STATUS_CODES表中(您可能应该这样做),但它不会改变应用程序中的某个地方(或者更可能是在几十个应用程序中)是做出逻辑决策的代码这一事实反对“P”,“O”和“C”。

你的问题是

  

如果Id值从1变为3,则此代码将中断   是消除这个问题的好方法吗?

答案是我们称之为“滴滴涕”。不要那样做。 P必须始终为P,并且绝不能更改为P. Period,句末。不要误解我,这些东西,这些神奇的cookie应该被最小化,记录,并在你的应用程序中使用一层间接(更多的是)。但我们都生活在现实世界中。

在为这些类型的值编写代码时,您不需要在方法中对这些值进行硬编码。在最低限度,这是一个常数或枚举。我个人喜欢在我的代码对象模型中将它们视为一等公民。例如,我们之前看到的STATUS值成为OrderStatus对象。

class OrderStatus

   public static readonly OrderStatus ClosedStatus = new OrderStatus("C");

   public static readonly OrderStatus OpenStatus = new OrderStatus("O");

   public static readonly OrderStatus PendingStatus = new OrderStatus("P");

   public static OrderStatus FromCode(string code)
   {
      if (code == "C")
         return ClosedStatus;
      else if (code == "O")
         return OpenStatus;
      else if (code == "P")
         return PendingStatus;
      else
         throw something;
   }

   private string _code;

   private OrderStatus(string code)
   {
      // private so cannot be created externally
      _code = code;
   }

   public string StatusCode { get { return code; } }

   // etc, etc.  Helpful to make ToString() return the inner status code.

}

然后在Order对象中,您将拥有OrderStatus类型的属性。您可以执行逻辑测试,而无需对DBA似乎喜欢的那些讨厌的单字符列值进行硬编码。

if (someOrder.Status == OrderStatus.Closed)
   MessageBox.Show("Cannot modify closed orders.");