HNibernate Отношение ко многим отношениям свободно с использованием только идентификатора внешнего ключа - PullRequest
0 голосов
/ 27 января 2011

Я хочу иметь отношение 1 ко многим в NHibernate, где таблица Child имеет доступ только к его parentId. Или внешний ключ в БД.

Я пробовал следующую настройку:

public class ParentTable
{
    public ParentTable()
    {
        _childRecords = new List<ChildTable>();
    }

    public virtual int ParentId { get; set; }

    private IList<ChildTable> _childRecords;

    public virtual IEnumerable<ChildTable> ChildRecords
    {
        get { return _childRecords; }
    }

    public void AddChildTable(string value)
    {
        _childRecords.Add(new ChildTable{ StringField = value });
    }
}

public class ChildTable
{

    public virtual int ChildTableId { get; set; }
    public virtual string StringField { get; set; }
    public virtual int ParentId { get; set; }
}

Отображения:

public class ParentTableMap : ClassMap<ParentTable>
{
    public ParentTableMap()
    {
        Not.LazyLoad();

        Id(x => x.ParentId);

        HasMany(x => x.ChildRecords)
            .Not.LazyLoad()
            .KeyColumn("ParentId").Cascade.All()
            .Access.ReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
    }
}

public class ChildTableMap : ClassMap<ChildTable>
{
    public ChildTableMap()
    {
        Not.LazyLoad();

        Id(x => x.ChildTableId);

        Map(x => x.StringField);
        Map(x => x.ParentId).Not.Nullable();
    }

}

Следующий тест не пройден, так как он пытается вставить 0 в столбец ParentId?

[TestFixture]
public class Tests
{
    [Test]
    public void SaveOrUpdate_ParentWithChildren_WillCreateParentWithChildRecordsHavingMatchingParentId()
    {

        int id;
        using (var sessionForInsert = SessionProvider.SessionFactory.OpenSession())
        {
            using (var trx = sessionForInsert.BeginTransaction())
            {
                //Assign
                var parent = new ParentTable();
                parent.AddChildTable("Testing");
                parent.AddChildTable("Testing2");
                sessionForInsert.SaveOrUpdate(parent); // Fails here with DB constraint error 
                id = parent.ParentId;
            }
        }

        using (var sessionForSelect = SessionProvider.SessionFactory.OpenSession())
        {
            //Action
            var result = sessionForSelect.Get<ParentTable>(id);
            Assert.AreEqual(id, result.ParentId);
            Assert.AreEqual(id, result.ChildRecords.First().ParentId);
            Assert.AreEqual(id, result.ChildRecords.Last().ParentId);
        }

    }
}

Вот что он пытается сделать:

exec sp_executesql N'INSERT INTO ChildTable (StringField, ParentId) VALUES (@p0, @p1); select SCOPE_IDENTITY()',N'@p0 nvarchar,@p1 int',@p0='Testing;,@p1=0

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

Кто-нибудь успешно установил отношения «один ко многим», как указано выше?

Спасибо

Dave

Ответы [ 2 ]

1 голос
/ 27 января 2011

Проблема в том, что вы пытаетесь вставить родителя и его потомков в одну операцию. Для этого NHibernate хочет вставить дочерние записи с нулевым ParentId, а затем обновить ParentId после вставки родительской записи. Это ограничение внешнего ключа приводит к сбою.

Лучшее решение - отобразить отношения от ребенка к родителю. Вам не нужно публично выставлять родителя, вы можете просто выставить его ParentId как int? при желании.

Если это неприемлемо, вы сможете выполнить это, изменив порядок операций. Во-первых, мне потребуется ParentId в конструкторе ChildTable. Затем измените порядок операций в тесте, чтобы он прошел.

public class ChildTable
{
    public ChildTable(int parentId) { ParentId = parentId; }
    public virtual int ChildTableId { get; set; }
    public virtual string StringField { get; set; }
    public virtual int ParentId { get; private set; }
}

using (var trx = sessionForInsert.BeginTransaction())
{
    //Assign
    var parent = new ParentTable();
    sessionForInsert.Save(parent);
    sessionForInsert.Flush(); // may not be needed
    parent.AddChildTable("Testing");
    parent.AddChildTable("Testing2");
    trx.Commit();
    id = parent.ParentId;
}

EDIT:

public class ChildTable
{
    private ParentTable _parent;

    public ChildTable(Parent parent) { _parent = parent; }

    public virtual int ChildTableId { get; set; }
    public virtual string StringField { get; set; }
    public virtual int? ParentId 
    {
        get { return _parent == null : null ? _parent.ParentId; }
    }
}

public class ChildTableMap : ClassMap<ChildTable>
{
    public ChildTableMap()
    {
        Not.LazyLoad();

        Id(x => x.ChildTableId);

        Map(x => x.StringField);
        // From memory, I probably have this syntax wrong...
        References(Reveal.Property<ParentTable>("Parent"), "ParentTableId")
            .Access.CamelCaseField(Prefix.Underscore);
    }
}
1 голос
/ 27 января 2011

Я думаю, вам нужно:

  • Сделать ParentId on ChildTable обнуляемым, или
  • Заменить ваши генераторы идентификаторов на то, что NHibernate может генерировать.

Второй вариант хорош.Переключитесь на Guid.Comb для ваших идентификаторов.Существует ограничение на то, что могут делать объектные реляционные картографы.В частности, рекомендуется разрешить NHibernate генерировать идентификаторы вместо базы данных.Я думаю, что это (длинное) сообщение в блоге объясняет это подробно: http://fabiomaulo.blogspot.com/2009/02/nh210-generators-behavior-explained.html.

Удачи!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...