这个话题很有趣。乍一看可能不太清楚。把我的回答作为一个概述,一个来自下面列出的所有来源的总结。也许它会给你答案。
从 C# 的角度来看,您可以这样考虑这些动态属性
public virtual IDictionary<keyType, valueType> Additional { get; set; }
// (e.g. <string, object>
public virtual IDictionary Additional { get; set; }
这两个都是动态的。没有针对处理的编译时检查IDictionary
。更好的情况是检查IDictinary<,>
. 但是因为我们在谈论动态映射,所以编译时检查是我们可以牺牲的......
要将数据加载到这些字典之一中,我们必须(在大多数情况下)进行不同的映射并具有不同的表结构。Generic 可以方便地转换rows中包含的数据。Non-Generic 可用于列映射(如问题中的示例)。让我们讨论两者
1) 通用IDictionary<,>
- 动态行
让我们从更多类型安全的场景开始。然后触摸接近问题的解决方案
1a) 三元关联
例如,让我们根据角色/类型将一些人映射到某个实体(例如合同) 。1) 经理 2) 领导 3) 测试员。如果我们知道每个类型/角色可能只有一个人(只有一个测试人员或没有),我们可以将其解释为:
public virtual IDictionary<PersonType, Person> Persons { get; set; }
我们现在充满活力。可能有 0、1 或 1+ 个人与合同相关。它们中的每一个都必须是唯一的PersonType
。而且我们还可以PersonType
在运行时引入 new ,并扩展任何 Contract 的相关 Person 集......
映射应该是这样的
<map name="Persons" table="ContractPerson" >
<key column="ContractId"/>
<index-many-to-many column="PersonTypeId" class="PersonType"/>
<one-to-many class="Person"/>
</map>
这是6.9 的示例。三元协会。涉及三个实体,我们在运行时仍然具有灵活性。如前所述,我们可以插入新的 PersonTypes 并修改这些 Contract-Person 关系。
这种情况(与 相比IDictiniary<string, object>
)仍然提供大量编译时检查。
1b)更接近问题
在上面描述的场景中,如果我们想从行<map>
的角度使用并且是动态的- 我们需要一个像这样的表:
ElemntId| TheKey | TheValue (e.g. nvarchar)
1 | "name" | "Element A"
1 | "time" | "20:02"
1 | "date" | "2013-05-22"
1 | "value" | "11.22"
C# 看起来像这样
public class Elements
{
...
public virtual IDictionary<string, string> Values { get; set; }
}
映射:
<map name="Values" table="DynamicElementValues" >
<key column="ElementId"/>
<index column="TheKey" type="string"/>
<element column="TheValue" type="string"/>
</map>
因为我们使用IDictionary<string, string>
的所有值都是字符串。我们需要一些MetaData
来正确解释它的价值
我们获得了:
我们失去了:
- 将任何值放入 SELECT 或 ORDER BY 子句的能力
- 必须转换数据类型(从字符串到任何其他)
1c) 密钥作为string
实际上,带string
键的字典是动态的,但是太多了。如 1a) 所示,以某种方式管理要用作key的值集总是更好的主意。这就是我们讨论三元关联的原因。因为我们迟早必须解释这本字典中的数据——使用一些元数据,将它们也用作键可能会很方便......
2) 非通用IDictionary
-动态列
这次我们将真正尝试对列进行动态求解。
我们可以在这里使用的 NHibernate 功能是:
这种映射非常接近问题,符合我们的要求。我们将有这个 C# 表示
public class Elements
{
...
public virtual IDictionary DynamicValues { get; set; }
}
这可能是映射:
<join table="ElemntValues" >
<key column="ElementId" />
<dynamic-component name="DynamicValues" >
<property name="Time" type="TimeSpan" />
<property name="Date" type="DateTime" />
<property name="Salary" type="decimal" />
<property name="Color" type="string" />
<property name="WorkingDays" type="integer" />
<many-to one....
...
</dynamic-component>
</join>
在这种情况下,我们确实有分隔表ElementValues
,连接到我们的父实体(作为其<class>
映射的一部分)。
这不是唯一的映射。可能还有其他映射类型,例如4.4。动态模型
<class entity-name="DynamicValues"...
这些可能需要一些更特殊的处理(插入、更新、删除)
加入会简化很多东西,但总是会在 SQL 语句中使用(即使只需要父核心属性)
是动态的吗?
好吧,我们获得了:
- 在 C# 上,我们只有属性
IDictionary ElementValues
- NHibernate 为我们做运行时检查。只能将正确类型的值插入到键中(工资必须是十进制)
- 使用某些
MetaData
模型,我们可以真正对用户动态
- 任何映射属性都可以在 SELECT(投影)和 ORDER BY(用户会喜欢它)中使用
我们失去了:
- 一些性能,因为将加载所有数据(例如 session.Get<>(id))
- 在添加或删除列的情况下,我们不是动态的。所有映射都是应用程序分发的一部分,不能在运行时更改。好吧,我们总是可以重新部署新的映射......
2b) 字典和元数据
虽然IDictinary
从 C# 的角度来看是非常动态的(它可以包含任何键/对值),但由于 NHibernate 映射,内容是可管理的。只能integer
将值添加到映射为 的属性
integer
。但是我们如何在运行时知道:我们有什么键?什么值可以放在那里并从那里检索?同样,我们需要一些元数据……不充当键的角色,但它们在运行时至关重要。NHibernate 检查是最后一道防线。
3) 如何在运行时更改映射(添加新列)?
那么,文档中说明了什么?7.5。动态组件:
这种映射的优点是能够在部署时确定组件的实际属性,只需编辑映射文档即可。(也可以使用 DOM 解析器对映射文档进行运行时操作。)
...使用 DOM 解析器。老实说,我不知道那是什么意思,如何实施。
以及Mapping-by-Code中的 Adam Bar - 动态组件说明(见评论)
我认为动态组件在对象和数据库级别都不能真正动态。注意组成部分存储为普通列,所以我们需要知道它的列表...
但是Firo - NHibernate 动态映射有一个好主意 (看看,对于某些提取来说太复杂了)。如果真的需要,这可能是现实动态世界的解决方案......运行时的新列......新的键映射IDictionary
概括
通过<map>
映射,我们可以非常动态。运行时不同的键和值。无需重新部署。但是我们不能在 select 或 order by 中使用这些动态属性。很难按这些值过滤
有了 a<dynamic-component>
我们(开箱即用)依赖于映射。但是我们确实在列中有数据,因此我们可以使用连接来检索它们。易于过滤、排序。和/但必须有一些metadata
来指导我们我们拥有什么以及我们可以做什么。
其他来源: