NHibernate 中使用复合键的一对一关系

One-to-one relationship in NHibernate using composite key

我正在尝试找出在 NHibernate 中模拟一对一(或一对零)关系的正确方法,或者确实明确地了解是否可以完成这样的事情。

目前我有两个模型,DocumentScriptDocument,两者之间应该存在双向关系,由一个由两个属性组成的复合主键定义 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 在任何情况下都不会尝试填充这些属性。因此,我假设正在发生以下两种情况之一:

关于这种方法的注意事项:你绝对不需要告诉我这是多么的非正统,我已经痛苦地意识到了。但我在现有系统的限制下工作。除非这绝对是绝对不可能的,否则这是我现在想继续采用的方法。

所以解决这个问题的关键似乎是使用组件复合 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 关系添加了 FetchLazy 调用。这些不是该解决方案的特定部分,而是添加以更好地指导 NHibernate 首选哪种加载行为。