使用 Fluent NHibernate 将一个实体的关系映射到另外两个实体

Mapping a relationship from an entity to two other entities with Fluent NHibernate

我有两个不同的class映射到 Fluent NHibernate

public class File1Map: ClassMap<File1> {
    CompositeId()
       .KeyProperty(x => x.IdFile)
       .KeyProperty(x => x.IdRow);

    HasMany(x => x.Errors).AsBag().KeyColumns.Add("IdFile", "IdRow");
}

public class File2Map: ClassMap<File2> {
    CompositeId()
       .KeyProperty(x => x.IdFile)
       .KeyProperty(x => x.IdRow);

    HasMany(x => x.Errors).AsBag().KeyColumns.Add("IdFile", "IdRow");
}

public class File1 {
    public int IdFile {get; set;}
    public int IdRow {get; set;}
    public List<Error> Errors {get; set;}
    // ...other properties different from File2
}

public class File2 {
    public int IdFile {get; set;}
    public int IdRow {get; set;}
    public List<Error> Errors {get; set;}
    // ...other properties different from File1
}

还有一个有错误的 class,包含每个 class 的描述。

public class ErrorMap: ClassMap<Error> {
    Map(p => p.IdFile);
    Map(p => p.IdRow);
    Map(p => p.Description);
}

public class Error {
    public int IdFile {get; set;}
    public int IdRow {get; set;}
    public string Description {get; set;}
}

如何将 Error 映射到 File1File2? 我可以使用 has many 来定义要用于关系的列吗? (Error 边。)

使用基数class

对于这种情况,映射一个基数 class 是通常的解决方案。这要求您的 ID 在 File1File2 中是唯一的。 (在 File2 中无法找到 File1 中存在的复合 ID,反之亦然。)对于 File1File2 有两个不同的 table 而没有table 为基础 class,你必须使用 "table per concrete class strategy".

public class FileBaseMap: ClassMap<FileBase> {
    CompositeId()
       .KeyProperty(x => x.IdFile)
       .KeyProperty(x => x.IdRow);

    HasMany(x => x.Errors).AsBag().KeyColumns.Add("IdFile", "IdRow");
    // One table per concrete class.
    UseUnionSubclassForInheritanceMapping();
}

public class File1Map: SubclassMap<File1> {
    // Other properties mapping
}

public class File2Map: SubclassMap<File2> {
    // Other properties mapping
}

public abstract class FileBase {
    public int IdFile {get; set;}
    public int IdRow {get; set;}
    public List<Error> Errors {get; set;}
}

public class File1 : FileBase {
    // ...other properties different from File2
}

public class File2 : FileBase {
    // ...other properties different from File1
}

然后您可以将 Error class 映射到 FileBase 属性。

使用单独的外键

如果您不想引入基数 class,或者如果您不能保证文件 ID 在 File1File2 之间的唯一性,则必须将它们映射为两个单独的实体集合。

您的 Error class 看起来像:

public class Error {
    public int? IdFile1 {get; set;}
    public int? IdRow1 {get; set;}
    public int? IdFile2 {get; set;}
    public int? IdRow2 {get; set;}
    public string Description {get; set;}
    public File1 File1 {get; set;}
    public File2 File2 {get; set;}
}

使用组件

您可以将其映射为 File1File2 中的 list of components ,而不是将 Error 映射为实体。我对 Fluent 了解不多,所以我只能用 hbm 语法来说明这一点。由您找到合适的 Fluent 调用。

这要求您的 ID 在 File1File2 中也是唯一的,否则可能会出现错误。 (File1 中不存在的组合 id 应该在 File2 中找到,反之亦然。)

<class name="File1">
  <!-- id and other properties here -->

  <bag name="Errors" table="Error">
    <key>
      <column name="IdFile" />
      <column name="IdRow" />
    </key>
    <composite-element class="Error">
      <property name="Description" />
    </composite-element>
  </bag>
</class>

File2也是如此。

Error class 不会保存文件 ID 属性或文件 属性,但只会保存其 Description 和其他属性(如果有的话)。

如果除外键外,您的错误 class 中只有 Description,您可能最好删除 class 并将 Errors 映射为a collection of element(你的字符串)。

如果您想使用 set 而不是 bag,您的组件将必须实现 EqualsGetHashCode 覆盖,以及 Description必须不可为空。

使用many-to-any

这里又一次,我不知道 Fluent 是否处理了这个问题。我也从未尝试过,而且文档非常简洁。无论如何,这是一个要尽可能避免的异国情调的映射。它需要一个额外的列来识别什么是 "any"。 它允许您将错误的文件引用映射为单个对象 属性,而没有基 class。它将支持 File1File2 具有共同的 ID。

备注:

如果您将 Error class 作为一个实体而不是一个组件来保存,您应该添加一个主键。技术性的,例如序列 ID 或其他。

最好避免在其组件和实体中使用 composite ids. Otherwise map them as composite identifier 并覆盖 EqualsGetHashCode

不需要映射Error中的外键id,这对于文件实体的映射有点多余。您可以让 Error class 仅包含其 Description 属性 和文件实体 属性.

所以对于基本 class 情况,它将是:

public class Error {
    public FileBase File {get; set;}
    public string Description {get; set;}
}