这个主题很有趣,但可能一开始不太清楚。请将我的回答视为一个概述,是从下面列出的所有来源中总结而来的。也许它会给你答案。
从C#的角度来看,你可以这样想这些“动态”属性。
public virtual IDictionary<keyType, valueType> Additional { get; set; }
// (e.g. <string, object>
public virtual IDictionary Additional { get; set; }
Both of these are dynamic. There is no compile-time checking for handling
IDictionary
. Better situation is the generic arguments checking for
IDictinary<,>
. But because we are talking about
dynamic mapping, the compile time check is what we can sacrifice...
To load data into one of these
dictionaries we have to (in most cases) do different mapping and have different structure of tables. The Generic would be handy for transposition of data contained in
rows. The Non-Generic could be used for
column mapping
(as the example in the question). Let's discuss both.
1) Generic
IDictionary<,>
- Dynamic rows
Let's start with a more type-safe scenario. Then touch solution close to the question.
1a) Ternary Associations
For example, let's map a few Persons to some Entity
(e.g. Contract) based on their roles/types. 1) Manager 2) Leader 3) Tester. If we know that there could be only one person per type/role (only one Tester or none), we can explain it as:
public virtual IDictionary<PersonType, Person> Persons { get; set; }
我们现在是动态的。与合同相关的人数可能为0、1或1+。每个人必须按照PersonType进行唯一标识。我们也可以在运行时引入新的PersonType,并扩展任何合同的相关人员集合...
映射应该如下:
<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)所示,更好的想法总是以某种方式管理要用作
键的值集。这就是为什么我们讨论了三元关联。因为我们迟早必须解释这个字典中的数据 - 如果有一些元数据,使用它们作为键也很方便...
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为我们运行时检查。只有正确类型的值才能插入到键中(Salary必须是十进制数)
- 通过一些
MetaData
模型,我们可以真正地对用户进行动态操作
- 任何映射属性都可以用于SELECT(投影)和ORDER BY(用户会喜欢它)
我们失去了:
- 一些性能,因为所有数据(例如session.Get<>(id))将被加载
- 我们不是动态的,如果添加或删除列。所有映射都是应用程序分发的一部分,不能在运行时更改。好吧,我们总是可以重新部署新的映射...
2b)IDictionary和MetaData
从C#的角度来看,IDictionary
非常动态(它可以包含任何键/值对),但由于NHibernate映射,内容是受管理的。只能将integer
值添加到映射为integer
的属性中。但是在运行时我们如何知道:我们有哪些键?可以放置哪些值并从中检索哪些值?再次,我们需要一些元数据……不扮演键的角色,但它们在运行时将非常重要。NHibernate检查是最后的防线。
3)如何在运行时更改映射(添加新列)?
那么文档中说了什么呢?7.5. Dynamic components:
这种映射的优点是能够通过编辑映射文档在部署时确定组件的实际属性。(还可以使用DOM解析器对映射文档进行运行时操作。)
...使用DOM解析器。说实话,我不知道这是什么意思,也不知道如何实现。
而且Adam Bar在Mapping-by-Code - dynamic component中指出(见评论)
我认为动态组件不能真正地在对象和数据库级别上实现动态性。请注意,组件部分被存储为普通列,因此我们需要知道它的列表...
但是有一个好主意来自Firo - NHibernate dynamic mapping (也可以看一下,对于某些摘录来说过于复杂)。如果真的需要,这可能是一个解决真正动态世界的方案...在运行时添加新列...在IDictionary
中添加新键映射
总结
通过<map>
映射,我们可以非常动态。运行时不同的键和值。无需重新部署。但我们无法在选择或排序中使用这些动态属性。很难通过这些值进行过滤。
使用
<dynamic-component>
,我们默认依赖于映射。但是,由于我们的数据以列的形式存在,因此我们可以使用连接来检索它们。易于过滤、排序。但/并且必须有一些元数据来指导我们所拥有的内容和可以执行的操作。
其他来源: