C# 针对实体允许自定义属性的最佳设计

C# 针对实体允许自定义属性的最佳设计,c#,wpf,nhibernate,C#,Wpf,Nhibernate,我对C#和WPF比较陌生,正在着手创建一个库存管理系统。该系统将有客户,所有客户都有不同的库存需求 库存将存储在一个表中,表中有列,如ProductId、CustomerId、BookQty、OnHandQty、OnOrderQty等。每个客户都希望针对这些产品存储不同的属性,如批号、使用日期、产品日期等。我希望使其尽可能动态,以便在设置客户时,我们定义他们希望如何管理库存 我的问题是,从数据库的角度来看,根据库存存储这些属性的最佳设计是什么 在股票表中有单独的字段,如TagString1、T

我对C#和WPF比较陌生,正在着手创建一个库存管理系统。该系统将有客户,所有客户都有不同的库存需求

库存将存储在一个表中,表中有列,如ProductId、CustomerId、BookQty、OnHandQty、OnOrderQty等。每个客户都希望针对这些产品存储不同的属性,如批号、使用日期、产品日期等。我希望使其尽可能动态,以便在设置客户时,我们定义他们希望如何管理库存

我的问题是,从数据库的角度来看,根据库存存储这些属性的最佳设计是什么

  • 在股票表中有单独的字段,如
    TagString1、TagString2、TagString3、TagDate1、TagDate2、TagDate3、TagInt1、TagIn2、TagInt3
  • 具有单个字段,只需对非字符串值执行强制转换,例如
    Tag1、Tag2、Tag3
    (并具有一个查找表,以定义哪些列以及列标题中存在哪些数据类型)
  • 把这些放在一张单独的桌子上
  • XML数据类型
  • 键/对值表
  • 动态更改表结构(认为这是一个坏主意,但将此想法抛诸脑后)
我见过以前的系统使用1和2。我认为2可能是最简单的实现方式,但是XML字段的概念正在我的脑海中成长

有人能解释一下上述方法的优点/缺点吗


解决方案需要是可索引的(这是一个词吗?),性能是值得关注的。大多数数据查询都将通过NHibernate完成,因此我并不十分担心通过原始SQL进行查询的复杂性。

首先,这与WPF无关

至于数据库设计,我将创建一个
CustomerAttributes
带有Id、AttributeName和AttributeType(日期、字符串、数字等)

然后创建一个包含
ProductAttributes
的表,其中包含ProductId、CustomerId、AttributeId和AttributeValue列


如果您希望解决方案可索引,请避免使用XML。

如果您确实在业务逻辑(搜索/显示和报告除外)中使用了这些值(批号、按日期使用、产品日期),则应将它们作为适当的列添加到表中

但是,如果它们只用于搜索/显示或报告,我更喜欢使用垂直表(stockId、key、value),这样它会更灵活,即使您可以走得更远一点,使用每种类型的列,例如(stockId、key、stringValue、DateValue等),这样您就可以优化查询,而不必进行强制转换或转换

  • 在库存表中有单独的字段
  • 这更简单,但最多只支持3个附加属性,并且您的搜索查询将非常复杂,必须确定哪个字段是另一个映射表中的字段,报告也是如此

  • 只有一个字段,只需对非字符串值执行强制转换
  • 除了1中的问题之外,您还必须在查询中进行动态转换,这几乎是使用NHibernate无法实现的

  • 把这些放在一张单独的桌子上
  • 没有任何优势,除非它是一个垂直的表,这是第五点

  • XML数据类型
  • 使用查询进行搜索可能不是那么容易,但是数据库中有用于XML搜索的内置函数,如果NHibernate支持,那么这个函数肯定值得考虑

  • 键/对值表

  • 在搜索、显示和报告方面,我能想到的最佳方法。

    这是一个非常常见的场景,在现实世界中,您将看到您提到的所有解决方案(叹气)。假设它不是一个玩具应用程序,那么您将拥有(或者某些客户可能拥有)许多自定义属性,并且您还需要围绕这些属性执行实际工作(查询、更新等)

    在库存表中有单独的字段

    这是最简单的解决方案(这是它与良好性能结合在一起的唯一好处)。但是,它有许多缺点:

    • 维护是一件痛苦的事情,当您的空闲列用完时,您将需要更改数据库模式
    • 如果需要新的列类型(例如,存储产品的仓库的地理位置),则需要添加更多列
    • 它无法解释自己。当您在查询中看到
      TagInt1
      时,您不了解其中包含的内容,这对于查询编写和调试来说很容易出错。不要低估这一点:您可以节省几天的开发时间,但每次自定义安装时都会支付这些费用
    • 如果每个客户对该列都有自己的用途,那么您就不能编写通用实用程序(例如迁移、更新和导入/导出)。对于每个客户,它们都需要定制(同样,在安装过程中您需要支付)
    • 它浪费了DB资源(但它们可能很少,可以忽略,DB引擎非常适合为空字段优化磁盘空间)
    • 您的源代码将具有更多配置点,例如,要构建查询,您需要读取映射表,将“批次代码”(用户在UI中看到的)转换为
      TagString1
      (您在查询中编写的内容)。您还需要执行类型检查(以验证用户输入),然后映射表还必须包含列类型。更多的代码需要编写,更多的代码需要测试,更多的bug需要修复
    除非只有一(或两)列具有几乎固定的含义集,否则我不会使用它。您在开发中节省的费用将是安装和支持费用的10倍

    有一个单一的领域,只是做一个演员

    所有这些都需要更难的调试器和更弱的类型检查。你更自由,因为你不必猜测你可能需要的类型,但你需要支付一个hi Id | KeyId | ValueInt | ValueString | ValueBit ---------------------------------------------- Id | Name | Type ----------------
    SELECT
      M.Name, A.Value
    FROM
      ProductAttribute AS A
    INNER JOIN
     ProductAttributeMetadata AS M ON M.Id = A.KeyId
    WHERE
     M.ProductId = @ProductId
    
    Id, KeyId, DisplayOrder, DisplayName, DbValue ---------------------------------------------
    -- Tags
    SELECT
      *
    FROM
      Product
    WHERE
      TagString3 LIKE '001-000-A%'
    
    -- Dynamic
    SELECT
      *
    FROM
      Product
    WHERE
      LotCode LIKE '001-000-A%'
    
    -- Key/Value
    SELECT
      P.*
    FROM
      Product AS P
    INNER JOIN
      ProductAttribute AS A ON P.Id = A.ProductId
    INNER JOIN
     ProductAttributeMetadata AS M ON A.KeyId = M.Id
    WHERE
      M.Name = 'LotCode' AND A.Value LIKE '001-000-A%'
    
    -- XML
    SELECT
      *
    FROM
      Product
    WHERE
      XmlAttributes.exist('/data[contains(LotCode,"001-000-A")]') = 1