使用nHibernate接受接口到集合(Covariance)的麻烦

时间:2011-01-31 14:19:59

标签: nhibernate fluent-nhibernate c#-4.0

我在ASP.NET MVC应用程序中使用Fluent nHibernate作为持久层,我遇到了一些问题。

我有一种情况需要使用抽象来将对象存储到集合中,在这种情况下,如果您正在查看纯C#透视图,那么接口是最合理的选择。

基本上,对象(Item)可以有Requirements。要求可以是很多东西。在本地C#情况下,我只需使用以下代码完成此操作。

interface IRequirement
{
 // methods and properties neccessary for evaluation
}

class Item
{
 virtual int Id { get; set; }
 virtual IList<IRequirement> Requirements { get; set; }
}

一个粗略的例子。这在本机C#中工作正常 - 但是因为对象必须存储在数据库中,所以它变得有点复杂。实现IRequirement的每个对象可以是完全不同的对象。由于nHibernate(或我发现的任何其他ORM)无法真正理解如何序列化接口,因此在我的生活中,我无法想到如何处理这种情况。我的意思是,我理解这个问题。

这对数据库/ orm没有意义。我完全理解为什么。

class SomeKindOfObject
{
 virtual int Id { get; set; }
 // ... some other methods relative to this base type
}
class OneRequirement : SomeKindOfObject, IRequirement
{
 virtual string Name { get; set; }
 // some more methods and properties
}
class AnotherKindOfObject
{
 virtual int Id { get; set; }
 // ... more methods and properties, different from SomeKindOfObject
}
class AnotherRequirement : AnotherKindOfObject, IRequirement
{
 // yet more methods and properties relative to AnotherKindOfObject's intentive hierarchy
}

class OneRequirementMap : ClassMap<OneRequirement>
{
 // etc
 Table("OneRequirement");
}
class AnotherRequirementMap : ClassMap<AnotherRequirement>
{
 //
 Table("OtherRequirements");
}
class ItemMap : ClassMap<Item>
{
 // ... Now we have a problem.
 Map( x => x.Requirements ) // does not compute... 
 // additional mapping
}

那么,有没有人有任何想法?我似乎也无法使用泛型,因此建立一个基本的Requirement<T>类似乎。我的意思是代码工作和运行,但ORM无法掌握它。我意识到我在这里问的可能是不可能的,但我能做的就是问。

我还想补充一点,我对nHibernate没有太多经验,只有Fluent nHibernate,但我已经意识到两个社区都非常好,因此我将这两个标记为。但我目前的映射是100%'流利'。

修改

我实际上发现Programming to interfaces while mapping with Fluent NHibernate触及了这一点,但我仍然不确定它是否适用于我的场景。任何帮助表示赞赏。

更新(02/02/2011)

我正在添加此更新以回复发布的部分答案,因为我的结果有点尴尬。

接受建议并做更多研究,我设计了一个基本界面。

interface IRequirement
{
 // ... Same as it always was
}

现在我建立了我的类映射..

class IRequirementMap : ClassMap<IRequirement>
{
 public IRequirementMap()
 {
    Id( x => x.Id );
    UseUnionSubclassForInheritanceMapping();
    Table("Requirements");
 }
}

然后我映射实现它的东西。这就是非常怪异的地方。

class ObjectThatImplementsRequirementMap : ClassMap<ObjectThatImplementsRequirement>
{
 ObjectThatImplementsRequirementMap()
 {
  Id(x => x.Id); // Yes, I am base-class mapping it.
  // other properties
  Table("ObjectImplementingRequirement");
 }
}

class AnotherObjectThatHasRequirementMap : ClassMap<AnotherObjectThatHasRequirement>
    {
     AnotherObjectThatHasRequirementMap ()
     {
      Id(x => x.Id); // Yes, I am base-class mapping it.
      // other properties
      Table("AnotheObjectImplementingRequirement");
     }
}

这不是人们的建议,但这是我的第一个方法。虽然我这样做是因为我得到了一些非常怪异的结果。结果确实让我感到没有

它实际上有效... 排序 运行以下代码会产生意外结果。

// setup ISession
// setup Transaction
var requirements = new <IRequirement>
{
 new ObjectThatImplementsRequirement
 {
  // properties, etc.. 
 },
 new AnotherObjectThatHasRequirement
 {
  // other properties.
 }
}
// add to session.
// commit transaction.
// close writing block.

// setup new session
// setup new transaction
var requireables = session.Query<IRequirable>();
foreach(var requireable in requireables)
   Console.WriteLine( requireable.Id );

现在事情变得怪异了。我得到了结果......

1 1

这对我没有意义。它应该不起作用。我甚至可以查询每个对象的各个属性,并且它们保留了它们的类型。即使我运行插入,关闭应用程序,然后运行检索(以避免缓存的可能性),他们仍然有正确的类型。但以下有效。

class SomethingThatHasRequireables
{ 
 // ...
 public virtual IList<IRequirement> Requirements { get; set; }
}

尝试添加到该集合失败(正如我所期望的那样)。这就是为什么我感到困惑。

  • 如果我可以在会话中添加通用IList<IRequirement>,为什么不在对象中添加?
  • nHibernate如何理解具有相同Id的两个实体之间的差异, 如果它们都被映射为同一种对象,在一种情况下,而不是另一种?

有人可以向我解释一下世界上发生了什么吗?

  

建议的方法是使用SubclassMap<T>,但问题是身份的数量和表的大小。如果多个对象(最多约8个)引用一个表中的标识,我会担心可伸缩性和性能。有人可以给我一些具体的见解吗?

1 个答案:

答案 0 :(得分:3)

请参阅参考文档中的Inheritance mapping一章。在章节Limitations中,您可以看到哪种映射策略可行。

据我所知,你已经选择了“每个具体课程表”的策略之一。您可能需要<one-to-many>inverse=true<many-to-any>进行映射。

如果要避免这种情况,则需要将IRequirement作为基类映射到表中,然后可以为该表提供外键。这样做可以将其转换为“每个类层次表”或“每个子类表”映射。如果已经映射了另一个基类,这当然是不可能的。例如。 SomeKindOfObject


修改<one-to-many>inverse=true<many-to-any>的更多信息。

当您使用<one-to-many>时,外键实际上位于指向Item的需求表中。到目前为止,这种方法效果很好,NH联合所有需求表来查找列表中的所有项目。反向是必需的,因为它强制您从需求引用项目,NH使用它来构建外键。

<many-to-any>更灵活。它将列表存储在附加链接表中。该表有三列:

  • 项目的外键,
  • 实际需求类型的名称(.NET类型或实体名称)
  • 和需求的主键(不能是外键,因为它可以指向不同的表)。

当NH读取此表时,它从类型信息(以及相应的需求映射)中了解需求所在的其他表。这就是任何类型的工作方式。

它实际上是一个多对多的关系不应该打扰你,它只意味着它将关系存储在一个额外的表中,该表在技术上能够将需求链接到多个项目。


编辑2 :怪异的结果:

您映射了3个表:IRequirementObjectThatImplementsRequirementAnotherObjectThatHasRequirement。他们都是完全独立的。你仍然在“具有隐式多态性的每个具体类的表”。您刚刚添加了包含IRequirements另一个表,当NH尝试查找正确的表时,这也可能导致一些歧义。

当然,你得到的结果为1,1。有独立的表,因此独立的ID都以1开头。

有效的部分:当你查询它时,NHibernate能够找到在整个数据库中实现接口的所有对象。尝试session.CreateQuery("from object"),即可获得整个数据库。

不起作用的部分:另一方面,您无法仅通过ID和界面或object获取对象。所以session.Get<object>(1)不起作用,因为有许多id为1的对象。同样的问题在于列表。还有一些问题,例如,使用隐式多态,没有指定外键,从实现IRequirement的每个类型指向Item。

任何类型:这是任何类型映射的来源。任何类型都与数据库中的其他类型信息一起存储,并且由存储外部的<many-to-any>映射完成附加表中的密钥和类型信息。通过这种附加类型信息,NH能够找到存储记录的表。

怪异的结果:考虑到NH需要找到两种方式,从对象到单个表,从记录到单个类。因此,在独立映射接口和具体类时要小心。 NH可能会使用一个或另一个表,具体取决于您访问数据的方式。这可能是原因或您的怪异结果

另一个解决方案:使用任何其他继承映射策略,您可以定义一个NH可以开始读取并查找类型的表。

ID范围:如果您使用Int32作为ID,则可以在68年内每秒创建1条记录,直到用完ID为止。如果这还不够,只需切换到long,你就会得到......可能更多,然后数据库能在未来几千年内存储......