NHibernate 中使用复合键的一对一关系
One-to-one relationship in NHibernate using composite key
我正在尝试找出在 NHibernate 中模拟一对一(或一对零)关系的正确方法,或者确实明确地了解是否可以完成这样的事情。
目前我有两个模型,Document
和 ScriptDocument
,两者之间应该存在双向关系,由一个由两个属性组成的复合主键定义 shared/duplicated 在两个表中。一个 Document
可能有零个或一个关联的 ScriptDocument
,每个 ScriptDocument 都有一个关联的 Document
。它们都有一个由两个属性组成的共享主键:字符串 ("key") 和 int ("userref")。
目前我的模型和映射设置如下:
public class Document
{
public virtual string Key { get; set; }
public virtual int UserRef { get; set; }
public virtual ScriptDocument ScriptDocument { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is Document document &&
Key == document.Key &&
UserRef == document.UserRef;
}
public override int GetHashCode()
{
return HashCode.Combine(Key, UserRef);
}
}
public class DocumentMap : ClassMapping<Document>
{
public DocumentMap()
{
Schema("Documents");
Table("Documents");
ComposedId(m =>
{
m.Property(x => x.Key);
m.Property(x => x.UserRef, m => m.Column("User_Ref"));
// the PK fields are named slightly differently across the two tables. Same data types though and same names in the models.
});
OneToOne(x => x.ScriptDocument, m => {
m.Cascade(Cascade.All);
m.Constrained(false);
});
// ... other property mappings ...
}
}
public class ScriptDocument
{
public virtual string Key { get; set; }
public virtual int UserRef { get; set; }
public virtual Document Document { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is ScriptDocument sd &&
Key == sd.Key &&
UserRef == sd.UserRef;
}
public override int GetHashCode()
{
return HashCode.Combine(Key, UserRef);
}
}
public class ScriptDocumentMap : ClassMapping<ScriptDocument>
{
public ScriptDocumentMap()
{
Table("Script_Document");
ComposedId(m =>
{
m.Property(x => x.Key, m => m.Column("DocKey"));
m.Property(x => x.UserRef);
});
OneToOne(x => x.Document, m => m.Constrained(true));
// ... other property mappings ...
}
}
此时,NHibernate 似乎对这些模型和映射定义很满意,但问题是这些关系似乎被有效地忽略了。当加载一个或多个 Document
实体时,它们都有一个空 ScriptDocument
属性 并且任何 ScriptDocument
上的 Document
属性 也是如此] 个实体。
据我所知,NHibernate 在任何情况下都不会尝试填充这些属性。因此,我假设正在发生以下两种情况之一:
- 我做错了什么(可能是在映射中)。我有点希望我错过了一两件小事,但我一辈子也想不出那可能是什么。
- 这实际上是做不到的。我的理解是,如果我们有一个共享主键,这种方法应该没问题,但我不确定共享 composite 键是否是我们可以做的。我找不到任何可比较的例子。
关于这种方法的注意事项:你绝对不需要告诉我这是多么的非正统,我已经痛苦地意识到了。但我在现有系统的限制下工作。除非这绝对是绝对不可能的,否则这是我现在想继续采用的方法。
所以解决这个问题的关键似乎是使用组件复合 ID。
我添加了以下 class 来定义两个表的复合主键:
[Serializable]
public class DocumentIdentifyingKey
{
public virtual string Key { get; set; }
public virtual int UserRef { get; set; }
public override bool Equals(object obj)
{
return obj is DocumentIdentifyingKey key &&
Key == key.Key &&
UserRef == key.UserRef;
}
public override int GetHashCode()
{
return HashCode.Combine(Key, UserRef);
}
public override string ToString()
{
return $"{UserRef}/{Key}";
}
}
然后能够如下更新实体模型 classes 和相关映射,使用 ComponentAsId
为两个 classes/tables 中的每一个定义身份的实际数据库字段:
public class Document
{
public virtual DocumentIdentifyingKey Identity { get; set; }
public virtual ScriptDocument ScriptDocument { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is Document document &&
Identity == document.Identity;
}
public override int GetHashCode()
{
return Identity.GetHashCode();
}
}
public class DocumentMap : ClassMapping<Document>
{
public DocumentMap()
{
Schema("Documents");
Table("Documents");
ComponentAsId(x => x.Identity, m => {
m.Property(i => i.Key);
m.Property(i => i.UserRef, m => m.Column("User_Ref"));
});
OneToOne(x => x.ScriptMetadata, m => {
m.Cascade(Cascade.All);
m.Constrained(false);
m.Fetch(FetchKind.Join);
m.Lazy(LazyRelation.NoLazy);
});
// ... other property mappings ...
}
}
public class ScriptMetadata
{
public virtual DocumentIdentifyingKey Identity { get; set; }
public virtual Document Document { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is ScriptMetadata sd &&
Identity == sd.Identity;
}
public override int GetHashCode()
{
return Identity.GetHashCode();
}
}
public class ScriptDocumentMap : ClassMapping<ScriptMetadata>
{
public ScriptDocumentMap()
{
Table("Script_Document");
ComponentAsId(x => x.Identity, m =>
{
m.Property(i => i.Key, m => m.Column("DocKey"));
m.Property(i => i.UserRef);
});
OneToOne(x => x.Document, m => {
m.Constrained(true);
m.Fetch(FetchKind.Join);
m.Lazy(LazyRelation.NoLazy);
});
// ... other property mappings ...
}
}
我不完全确定为什么会这样,但是将文档的标识表示为一个对象的实例,而不仅仅是每个 class 上两个字段的组合似乎是关键咒语这让 NHibernate 能够理解我的意思。
注意:在此解决方案中,我向两个 OneToOne
关系添加了 Fetch
和 Lazy
调用。这些不是该解决方案的特定部分,而是添加以更好地指导 NHibernate 首选哪种加载行为。
我正在尝试找出在 NHibernate 中模拟一对一(或一对零)关系的正确方法,或者确实明确地了解是否可以完成这样的事情。
目前我有两个模型,Document
和 ScriptDocument
,两者之间应该存在双向关系,由一个由两个属性组成的复合主键定义 shared/duplicated 在两个表中。一个 Document
可能有零个或一个关联的 ScriptDocument
,每个 ScriptDocument 都有一个关联的 Document
。它们都有一个由两个属性组成的共享主键:字符串 ("key") 和 int ("userref")。
目前我的模型和映射设置如下:
public class Document
{
public virtual string Key { get; set; }
public virtual int UserRef { get; set; }
public virtual ScriptDocument ScriptDocument { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is Document document &&
Key == document.Key &&
UserRef == document.UserRef;
}
public override int GetHashCode()
{
return HashCode.Combine(Key, UserRef);
}
}
public class DocumentMap : ClassMapping<Document>
{
public DocumentMap()
{
Schema("Documents");
Table("Documents");
ComposedId(m =>
{
m.Property(x => x.Key);
m.Property(x => x.UserRef, m => m.Column("User_Ref"));
// the PK fields are named slightly differently across the two tables. Same data types though and same names in the models.
});
OneToOne(x => x.ScriptDocument, m => {
m.Cascade(Cascade.All);
m.Constrained(false);
});
// ... other property mappings ...
}
}
public class ScriptDocument
{
public virtual string Key { get; set; }
public virtual int UserRef { get; set; }
public virtual Document Document { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is ScriptDocument sd &&
Key == sd.Key &&
UserRef == sd.UserRef;
}
public override int GetHashCode()
{
return HashCode.Combine(Key, UserRef);
}
}
public class ScriptDocumentMap : ClassMapping<ScriptDocument>
{
public ScriptDocumentMap()
{
Table("Script_Document");
ComposedId(m =>
{
m.Property(x => x.Key, m => m.Column("DocKey"));
m.Property(x => x.UserRef);
});
OneToOne(x => x.Document, m => m.Constrained(true));
// ... other property mappings ...
}
}
此时,NHibernate 似乎对这些模型和映射定义很满意,但问题是这些关系似乎被有效地忽略了。当加载一个或多个 Document
实体时,它们都有一个空 ScriptDocument
属性 并且任何 ScriptDocument
上的 Document
属性 也是如此] 个实体。
据我所知,NHibernate 在任何情况下都不会尝试填充这些属性。因此,我假设正在发生以下两种情况之一:
- 我做错了什么(可能是在映射中)。我有点希望我错过了一两件小事,但我一辈子也想不出那可能是什么。
- 这实际上是做不到的。我的理解是,如果我们有一个共享主键,这种方法应该没问题,但我不确定共享 composite 键是否是我们可以做的。我找不到任何可比较的例子。
关于这种方法的注意事项:你绝对不需要告诉我这是多么的非正统,我已经痛苦地意识到了。但我在现有系统的限制下工作。除非这绝对是绝对不可能的,否则这是我现在想继续采用的方法。
所以解决这个问题的关键似乎是使用组件复合 ID。
我添加了以下 class 来定义两个表的复合主键:
[Serializable]
public class DocumentIdentifyingKey
{
public virtual string Key { get; set; }
public virtual int UserRef { get; set; }
public override bool Equals(object obj)
{
return obj is DocumentIdentifyingKey key &&
Key == key.Key &&
UserRef == key.UserRef;
}
public override int GetHashCode()
{
return HashCode.Combine(Key, UserRef);
}
public override string ToString()
{
return $"{UserRef}/{Key}";
}
}
然后能够如下更新实体模型 classes 和相关映射,使用 ComponentAsId
为两个 classes/tables 中的每一个定义身份的实际数据库字段:
public class Document
{
public virtual DocumentIdentifyingKey Identity { get; set; }
public virtual ScriptDocument ScriptDocument { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is Document document &&
Identity == document.Identity;
}
public override int GetHashCode()
{
return Identity.GetHashCode();
}
}
public class DocumentMap : ClassMapping<Document>
{
public DocumentMap()
{
Schema("Documents");
Table("Documents");
ComponentAsId(x => x.Identity, m => {
m.Property(i => i.Key);
m.Property(i => i.UserRef, m => m.Column("User_Ref"));
});
OneToOne(x => x.ScriptMetadata, m => {
m.Cascade(Cascade.All);
m.Constrained(false);
m.Fetch(FetchKind.Join);
m.Lazy(LazyRelation.NoLazy);
});
// ... other property mappings ...
}
}
public class ScriptMetadata
{
public virtual DocumentIdentifyingKey Identity { get; set; }
public virtual Document Document { get; set; }
// ... other properties ...
public override bool Equals(object obj)
{
return obj is ScriptMetadata sd &&
Identity == sd.Identity;
}
public override int GetHashCode()
{
return Identity.GetHashCode();
}
}
public class ScriptDocumentMap : ClassMapping<ScriptMetadata>
{
public ScriptDocumentMap()
{
Table("Script_Document");
ComponentAsId(x => x.Identity, m =>
{
m.Property(i => i.Key, m => m.Column("DocKey"));
m.Property(i => i.UserRef);
});
OneToOne(x => x.Document, m => {
m.Constrained(true);
m.Fetch(FetchKind.Join);
m.Lazy(LazyRelation.NoLazy);
});
// ... other property mappings ...
}
}
我不完全确定为什么会这样,但是将文档的标识表示为一个对象的实例,而不仅仅是每个 class 上两个字段的组合似乎是关键咒语这让 NHibernate 能够理解我的意思。
注意:在此解决方案中,我向两个 OneToOne
关系添加了 Fetch
和 Lazy
调用。这些不是该解决方案的特定部分,而是添加以更好地指导 NHibernate 首选哪种加载行为。