Отношения один к одному в NHibernate с использованием составного ключа

Я пытаюсь выяснить, как правильно смоделировать отношение «один к одному» (или «один к нулю») в NHibernate, или действительно категорически узнать, можно ли это сделать.

В настоящее время у меня есть две модели, и между ними должна быть двунаправленная связь, определяемая составным первичным ключом, состоящим из двух свойств, которые совместно используются/дублируются в обеих таблицах. A может иметь ноль или один связанный файл, и каждый ScriptDocument будет иметь связанный файл . Оба они имеют общий первичный ключ, состоящий из двух свойств: строки («ключ») и int («пользовательская ссылка»).

В настоящее время я настроил свои модели и сопоставления следующим образом:

          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, кажется, доволен этими моделями и определениями сопоставления, но проблема в том, что отношения, по-видимому, фактически игнорируются. При загрузке одного или нескольких объектов все они имеют нулевое свойство, и то же самое верно для собственность на любой сущности.

Насколько я могу судить, NHibernate ни в коем случае даже не пытается заполнить эти свойства. Поэтому я предполагаю, что происходит одно из двух:

  • Я сделал что-то не так (вероятно, в маппингах). Я вроде как надеюсь, что есть одна или две мелочи, которые я пропустил, но я не могу понять, что это может быть.
  • Это на самом деле не может быть сделано. Я понимаю, что этот подход должен быть просто прекрасным, если бы у нас был один общий первичный ключ, но я не уверен, что общий составной ключ — это то, что мы можем сделать. Не могу найти сопоставимых примеров.

Обратите внимание на этот подход: вам определенно не нужно говорить мне, насколько это неортодоксально, я болезненно осознаю. Но я работаю в рамках ограничений уже существующих систем. Если это абсолютно, категорически невозможно, это подход, который я хотел бы продолжить на данном этапе.

1 ответ

Таким образом, ключом к решению этой проблемы, казалось, было использование составного идентификатора компонента.

Я добавил следующий класс для определения составного первичного ключа для обеих таблиц:

      [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}";
    }
}

Затем он смог обновить классы моделей сущностей и связанные сопоставления следующим образом, используя определение фактических полей базы данных для идентификаторов для каждого из двух классов/таблиц:

      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 ...
    }
}

Я не совсем уверен, почему это сработало, но наличие идентификатора документа, выраженного как экземпляр объекта, а не просто комбинация двух полей в каждом классе, казалось, было ключевым заклинанием, которое позволило NHibernate понять, что я получаю в.

Примечание: в этом решении я добавил а также звонки к двум отношения. Они не являются частью этого решения, но вместо этого были добавлены, чтобы лучше проинструктировать NHibernate, какое поведение при загрузке было бы предпочтительным.

Другие вопросы по тегам