NHibernate动态列数

5
在我的C#项目中,我需要在代码中创建一个具有动态列数的数据库和表。就像这样:
------------------------------------------------------------------------------     
| Time        ¦ Element #1       | Element#2        ¦ ... ¦ Element#N        ¦
------------------------------------------------------------------------------
¦ TimeValue#1 ¦ Element#1Value#1 ¦ Element#2Value#1 ¦ ... ¦ Element#NValue#1 ¦
¦ TimeValue#2 ¦ Element#1Value#2 ¦ Element#2Value#2 ¦ ... ¦ Element#NValue#2 ¦
                                      ...
¦ TimeValue#M ¦ Element#1Value#M ¦ Element#2Value#M ¦ ... ¦ Element#NValue#M ¦
------------------------------------------------------------------------------

我使用简单的SQL查询“SQL CREATE TABLE”

public void AddTable(string TableName, Dictionary<string,Type> columns)
{        
    ...
    string query = @"CREATE TABLE " + TableName + "(";

    foreach (var c in columns)
    {
        SqlParameter temp = new SqlParameter();
        string t = SetSQLType(ref temp, c.Value).ToString();
        query += c.Key + " " + (t == SqlDbType.NVarChar.ToString() ? (t + "(200)") : (t)) + ",";
    }

    query = query.Substring(0, query.Length - 1);
    query += " )";
    ...
}

但我认为,也许我可以使用更舒适的ORM,例如NHibernate。我可以吗?我阅读了关于mapping-by-code和dynamic component的内容,但我不确定在我的情况下需要做什么。

我想,我需要像这样的东西:

public class Elements
{
    public virtual DateTime Timestamp { get; set; }
    public virtual IEnumerable<double> Values { get; set; }
}

我可以映射这个类吗?
1个回答

11
这个主题很有趣,但可能一开始不太清楚。请将我的回答视为一个概述,是从下面列出的所有来源中总结而来的。也许它会给你答案。
从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>,我们默认依赖于映射。但是,由于我们的数据以列的形式存在,因此我们可以使用连接来检索它们。易于过滤、排序。但/并且必须有一些元数据来指导我们所拥有的内容和可以执行的操作。

其他来源:


谢谢你的回答。非常有帮助。现在我可以动态生成列,真是太好了。但我还有一个问题。在NHibernate中是否可以创建一个类,然后为它创建多个映射。 - WarFollowsMe
如果我需要从“Elements”类中获取10个表格,那么在NHibernate中是否可以实现? - WarFollowsMe
NHibernate对表格、列等数量没有限制。 - Radim Köhler
但是关于“重复的类/实体映射错误”呢?只为一个类而存在一个表吗? - WarFollowsMe
好的,现在我明白了如何做到这一点,只需要创建许多配置,每个表格一个配置。非常感谢您的帮助。 - WarFollowsMe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接