c# - 允许对实体使用自定义属性的最佳设计

标签 c# wpf nhibernate

我对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查询的复杂性。

最佳答案

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


  库存表中有单独的字段...


首先是最简单的解决方案(这是它具有的唯一好处以及良好的性能)。但是它有很多缺点:


维护起来很痛苦,当可用列用完时,您将需要更改数据库架构。
如果需要新的列类型(例如,用于存储产品的仓库的地理位置),则需要添加更多列。
它没有解释自己。当您在查询中看到TagInt1时,您不知道它包含的内容,这很容易在查询编写和调试时出错。不要刺激这方面:您可以节省几天的开发时间,但是每次自定义安装时都需要付费。
如果每个客户对该列都有自己的用途,那么您将无法编写通用实用程序(例如,迁移,更新和导入/导出)。对于每个客户,都需要对其进行自定义(同样,您将在安装过程中支付费用)。
这浪费了数据库资源(但是它们可能不足以被忽略,数据库引擎非常适合为空字段优化磁盘空间)。
您的源代码将具有更多的配置点,例如,要构建查询,您需要读取映射表以将“批号”(用户在UI中看到的内容)转换为TagString1(您在查询中写的内容)。您还需要执行类型检查(以验证用户输入),然后映射表还必须包含列类型。需要编写更多代码,要测试更多代码,要修复更多错误。


除非您只有一(或两)列具有几乎固定的含义,否则我不会使用它。您节省的开发费用将是安装和支持费用的10倍。


  只有一个领域,只做演员...


所有这些都带有更难的调试器和较弱的类型检查。您更加自由,因为您不必猜测可能需要的类型,但是您需要为此付出高昂的代价。


  XML数据类型?


这是一个不错的主意,数据是结构化的,有分析和验证工具的工具,您的数据库架构是干净的。请记住,这些事情会更困难:


一些数据库引擎对XML列提供了一些支持,但是查询将更难编写和理解。
至于第一个解决方案,代码将更难编写,因为某些数据将保留在普通列中,而某些数据将保留在XML中。用户应该看不到任何区别,然后您必须隐藏此实现细节,对他们来说更容易,但对您来说却更难。
导入和导出数据。由于其双重性质,您的工具需要以两种方式处理所描述的事情。您可能会使用用于SQL的常用工具,但是它们需要与XML工具顺利集成以用于自定义属性(并且用户再也看不到任何区别)。


此外,请不要忘记XML会影响性能(由于查询语法,其冗长性以及由于它对数据库索引不太友好-有些引擎甚至不支持它们)。 NHibernate支持(它允许您使用XQuery,而不考虑内置支持...),但是语法丑陋而复杂(但这只是我的观点)。

除非没有其他适用条件,否则我不会使用它(或者您必须管理无法即时转换的旧XML数据)。


  动态更改表结构...


至少在我看来,这不是一个坏主意。在安装过程中,您使用可视工具创建数据库的描述(假设是这样)。然后,另一个工具将生成数据库模式,并使用每个站点的值配置所有内容。

性能将非常出色,表将是索引友好的,并且每个用户都将拥有自己的架构(如果您使用的是Team Foundation Server,请考虑一下,即使它不相同,也可以针对错误/任务等进行配置)。它不会因其他解决方案中前面提到的问题而遭受损失,最好是为每个客户提供最大的灵活性。

这有什么缺点?这是一项艰巨的任务。为了做一个体面的工作,您将需要使您的代码非常灵活。您应该做很少的假设(使用代码),并且必须配置所有内容。此外,您必须编写所需的所有工具(用于配置的配置器和配置生成器)。

可以分步完成(一个固定部分,占应用程序的99%)和一个可配置部分(占1%),但是您仍然需要工具(否则,在部署过程中,您将付出的开发费用是开发费用的10倍)。

还要注意,工具必须足够灵活以更新未知的内容(例如,当您发布新的实用程序/功能时),并且它们必须处理更新(当您以较新的版本更新数据库架构时)。

对我来说,这是最好的解决方案,但是只有在您有资源进行投资时,半完成的工作才会带来痛苦。


  将它们一起存储在单独的表中吗?
  
  键/对值表


让我一起讨论。这是我经常看到的解决方案,并且效果很好。

这些属性必须保留在单独的表中,并在其中存储为键/值对。为此,您需要模拟一个变量类型,如下所示:

Id | KeyId | ValueInt | ValueString | ValueBit
----------------------------------------------

Note that it's not the only way to simulate a variant then check your DB engine documentation to see what it offers (for example Microsoft SQL Server 2014 introduced a sql_variant type), if you have DB engine support then use it.

How this differs from first and second solution you mentioned? Everything is inside DB. Let's imagine you have attributes for a Product table. You will store them in a table named ProductAttribute (suppose with Id, ProductId, KeyId and Value columns). You'll describe them in a table named ProductAttributeMetadata where Id columns links to KeyId in ProductAttribute:

Id | Name | Type
----------------

What's advantage of this? Everything can be done in SQL, for example to query all attributes you just need to write:

SELECT
  M.Name, A.Value
FROM
  ProductAttribute AS A
INNER JOIN
 ProductAttributeMetadata AS M ON M.Id = A.KeyId
WHERE
 M.ProductId = @ProductId


同样,您的用户界面也不需要了解这些详细信息,它们可以用盲查询填充列。当您需要更多时,此技术将变得强大。假设您需要以特定顺序显示这些列:只需在DisplayOrder表中添加一个ProductAttributeMetadata列。假设您需要将输入限制为一组固定的值,您有一个Type列可用于此目的:让我们介绍一个List类型并添加一个新表ProductAttributeMetadataValueList

Id, KeyId, DisplayOrder, DisplayName, DbValue
---------------------------------------------

You need to group values? Just add a Group column. When code is written then UI will be unaware of any customization.

Drawbacks? Of course it has.

  • You don't need to write much code to support this technique but it's harder to test than - let's say - your first solution.
  • Queries may be hard to write (because you will need more JOINs) and for complex queries you may even need a tool (but much better than your first and second solutions).
  • Performance won't be as good as you may wish if you need to query attributes often (because of JOINs).

However it has some nice benefits:

  • It's incredibly flexible (2nd only to dynamically change DB schema).
  • It needs more code than other solutions (but less than solution with dynamic change of DB schema) and it's not too hard to test.
  • Import, export and merge and updates will be easier than any other solution.

Anything else?

If so far you didn't consider them I think you should take a look to No-SQL databases. Some of them are inherently schema-less (I know I oversimplify) and you may have some nice ideas from them.

Conclusions

I try to summarize a little bit. If you want to keep queries readable then I'd go with (partially) dynamically generated DB schema. You will need to write tools and tons of tests but it has best performance and it's only solution that keep your queries readable. Compare these fake (untested/unoptimized/ugly) examples:

-- 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


我将要做什么((当然仅从我个人的POV和经验来看)?如果产品足够复杂并且我有足够的资源进行投资,那么我绝对会使用动态生成的架构。我看到的唯一严重缺点是基础架构开发工作(和较难的更新)。第二个最好的是键/值对表,它有缺点,但是很容易实现并且非常灵活。

关于c# - 允许对实体使用自定义属性的最佳设计,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31999380/

相关文章:

c# - 如何在用户配置文件中创建自定义附加字段

c# - 将自定义数据插入从 DataModel 序列化的 JSON

c# - 在 WPF DataGrid 中删除一行

c# - 在 WPF 窗口中嵌入 win32 窗口,无需 HwndHost

nhibernate - 使用 NHibernate LINQ 提供程序连接查询的唯一结果

c# - nvarchar(max) 和 sql express 2005 的流畅 NHibernate 问题

c# - 在 C# 中将 SQLite 数据库读取为文件

c# - 生成 Paypal 表格

wpf - 哪个更好?在使用图像文件和绘制矢量形状之间

nhibernate - 在 nhibernate 中,当我提交事务时,sql 更新语句不会显示在控制台中。为什么?