NHibernate не выполняет каскадное сохранение, когда отключена автоматическая проверка и графы объектов загружаются вручную - PullRequest
0 голосов
/ 11 ноября 2009

Это длинный выстрел, который кто-нибудь имел бы дело с этим, но здесь идет.

Во-первых, у меня отключена автоматическая проверка NHibernate. Я сделал это потому, что не хочу, чтобы NHibernate сохранял каждый измененный объект, о котором он знает, когда я фиксирую транзакцию (FlushMode = Commit), потому что иногда он становится слишком усердным и пытается сохранить множество огромных графов объектов только потому, что я изменил что-то в объекте. Я следовал инструкциям, найденным здесь: http://fabiomaulo.blogspot.com/2009/03/ensuring-updates-on-flush.html

Во-вторых, у меня есть ситуация, когда мне нужно загружать объекты, используя пользовательский SQL, поэтому у меня есть динамический SQL, который загружает этот один граф объектов, используя ISession.CreateSQLQuery (). После этого я вызываю ISession.Lock () для загруженных объектов. Я действительно не знаю, что это делает, но если я этого не сделаю, изменения в загруженных пользовательских объектах никогда не будут сохранены.

Проблема теперь в том, что если я добавляю или удаляю объекты из коллекций внутри объектов, которые я загрузил с помощью своего пользовательского запроса SQL, NHibernate не сохраняет их. Если включена автоматическая проверка (по умолчанию), они сохраняются, поскольку она просто сохраняет весь граф объекта, поскольку все отношения помечены как cascade = "save-update". Но, поскольку я отключил авто-грязную проверку, она не проходит через все отношения.

Кажется, что так как я вызвал ISession.Lock (), NHibernate знает обо всех моих объектах, но что-то не так с коллекциями в этих объектах.

Если у кого-нибудь есть идеи, они будут оценены.

РЕДАКТИРОВАТЬ: Подробнее о том, что я делаю. У меня есть этот метод, где делает запрос. Как видно из сложности запроса, HQL - это не вариант (я всегда предпочитаю HQL или ISession.CreateCriteria (), а не CreateSQLQuery ()).

        public IList<MaterialGroup> GetResults(IList<long> takeOffItemIds)
        {
            if (takeOffItemIds == null || takeOffItemIds.Count == 0)
                return new List<MaterialGroup>();

            var query =
@"with MaterialGroupsByTakeOffItem (MaterialGroupId, TakeOffItemId) as
(
    select MaterialGroupId, ParentTakeOffItemId
    from MaterialGroups mg
    inner join TakeOffItems toi on toi.TakeOffItemId = mg.ParentTakeOffItemId
    where ParentTakeOffItemId is not null
    union all
    select grp.MaterialGroupId, parent.TakeOffItemId
    from MaterialGroups grp
    inner join MaterialGroupsByTakeOffItem parent on parent.MaterialGroupId = grp.ParentMaterialGroupId
)
select {mg.*}, {md.*}, {mi.*}, {mc.*} from MaterialGroupsByTakeOffItem mgbtoi
inner join MaterialGroups {mg} on {mg}.MaterialGroupId = mgbtoi.MaterialGroupId
left outer join MaterialDetails {md} on {md}.MaterialDetailsId = {mg}.MaterialGroupId
left outer join MaterialItems {mi} on {mi}.MaterialItemId = {md}.PartId
left outer join MaterialCodes {mc} on {mc}.MaterialCodeId = {mi}.CodeId
";

            if (takeOffItemIds.Count == 1)
                query += "where mgbtoi.TakeOffItemId = " + takeOffItemIds[0];
            else
                query += "where mgbtoi.TakeOffItemId in (" + takeOffItemIds.ToCommaDelimitedString() + ")";

            return _session.CreateSQLQuery(query)
                .AddEntity("mg", typeof(MaterialGroup))
                .AddJoin("md", "mg.MaterialDetails")
                .AddJoin("mi", "md.Part")
                .AddJoin("mc", "mi.Code")
                .List<MaterialGroup>();
        }

Этот метод возвращает список объектов MaterialGroup, которые будут принадлежать другому объекту под названием TakeOffItem. Я беру список объектов MaterialGroup и помещаю их в TakeOffItem.MaterialGroups.

    public override TakeOffItem Get(long id)
    {
        var materialGroups = _getMaterialGroupsForTakeOffItemQuery.GetResults(id);

        var fabricationNotesByMaterialDetailsId = _getFabricationNotesForTakeOffItemQuery.GetFabricationNotesForMaterialDetails(
            materialGroups.Where(mg => mg.MaterialDetails != null).Select(mg => mg.MaterialDetails.Id));

        var takeOffItem = base.Get(id);
        _assignMaterialGroupsService.AssignMaterialGroups(id, takeOffItem, materialGroups, fabricationNotesByMaterialDetailsId);
        _session.Lock(takeOffItem, LockMode.None);
        return takeOffItem;
    }        

    public void AssignMaterialGroups(long takeOffItemId, IHasMaterialGroups parent, IList<MaterialGroup> allMaterialGroups, 
        Dictionary<long, IList<FabricationNote>> fabricationNotesByMaterialDetailsId)
    {
        if (parent is TakeOffItem)
            parent.MaterialGroups = allMaterialGroups.Where(mg => mg.ParentTakeOffItem != null && mg.ParentTakeOffItem.Id == takeOffItemId).ToList();
        else if (parent is MaterialGroup)
        {
            var group = (MaterialGroup) parent;
            parent.MaterialGroups = allMaterialGroups.Where(mg => mg.ParentMaterialGroup != null && mg.ParentMaterialGroup.Id == parent.Id).ToList();
            if (group.MaterialDetails != null)
            {
                if (fabricationNotesByMaterialDetailsId.ContainsKey(group.MaterialDetails.Id))
                    group.MaterialDetails.FabricationNotes = fabricationNotesByMaterialDetailsId[group.MaterialDetails.Id];
                else
                    group.MaterialDetails.FabricationNotes = new List<FabricationNote>();
            }
        }
        else
            throw new NotSupportedException();

        foreach (var group in parent.MaterialGroups)
        {
            _session.Lock(group, LockMode.None);
            if (group.MaterialDetails != null)
                _session.Lock(group.MaterialDetails, LockMode.None);
            AssignMaterialGroups(takeOffItemId, group, allMaterialGroups, fabricationNotesByMaterialDetailsId);
        }
    }

Есть несколько проблем с этим. Во-первых, поскольку я вручную заполняю коллекцию в сущности TakeOffItem, NHibernate считает, что TakeOffItem теперь грязный. У меня есть cascade = "save-update" в TakeOffItem.MaterialGroups (и в нем есть больше каскадных отношений), и, поскольку TakeOffItem считается грязным, он сохранит весь граф объекта TakeOffItem при сохранении. Это нормально, если я действительно хочу сохранить TakeOffItem, но если я не хочу сохранять TakeOffItem, он завершает выполнение большого количества запросов, которые в принципе не нужны.

Чтобы обойти некоторые из этих проблем, я внедрил код Фабио, который отключит функцию автоматической проверки на грязные состояния в NHibernate. Теперь он будет сохранять только те вещи, которые я вызываю с помощью SaveOrUpdate () (вместе с каскадными отношениями), поэтому больше не имеет значения, что NHibernate считает, что все эти другие объекты являются грязными, потому что он не сохранит их при очистке сессия. Но теперь что-то еще не работает, потому что если я изменю коллекции в объекте MaterialGroup (например, TakeOffItem.MaterialGroups [0] .MaterialGroups.Add (что-то)), NHibernate не осознает, что ему нужно сохранить эти объекты. Если я удаляю весь свой код загрузки, код Фабио работает нормально. Но для оптимизации мне нужен пользовательский код загрузки.

Я думаю, что часть проблемы также связана с тем, что я не могу сказать NHibernate, что сущность НЕ грязная (если есть способ, я хотел бы знать!). Я действительно хотел бы иметь возможность выполнить свою пользовательскую загрузку, а затем сказать NHibernate: «Эй, весь этот граф объектов не грязный, притворись, будто ты только что загрузил его».

Еще раз спасибо за любую помощь.

Ответы [ 2 ]

1 голос
/ 11 ноября 2009

Похоже, вы делаете что-то смешное с CreateSql Я предполагаю, что вы не возвращаете сущности из него. Пожалуйста, опубликуйте все, что вы делаете с CreateSqlQuery Вы абсолютно должны не быть вынужденным использовать сеанс. Блокировка

0 голосов
/ 12 ноября 2009

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

Проблема с этим кодом:

public override TakeOffItem Get(long id)
{
    var materialGroups = _getMaterialGroupsForTakeOffItemQuery.GetResults(id);

    var fabricationNotesByMaterialDetailsId = _getFabricationNotesForTakeOffItemQuery.GetFabricationNotesForMaterialDetails(
        materialGroups.Where(mg => mg.MaterialDetails != null).Select(mg => mg.MaterialDetails.Id));

    var takeOffItem = base.Get(id);
    _assignMaterialGroupsService.AssignMaterialGroups(id, takeOffItem, materialGroups, fabricationNotesByMaterialDetailsId);
    _session.Lock(takeOffItem, LockMode.None);
    return takeOffItem;
}        

Проблема в том, что я делаю по пользовательскому запросу до Я загрузил TakeOffItem (родительский объект). Обычно, когда вы загружаете вещи с помощью NHibernate, родители загружаются первыми, а список известных сущностей в сеансе будет иметь родителей перед дочерними. Если у вас нет родителя в списке до потомков, код автоматической проверки не работает, потому что он последовательно проходит по списку известных объектов, помечая дочерние объекты как грязные. Что ж, если он пытается обработать дочерний объект до того, как родительский, дочерний объект помечается как грязный после NHibernate проверил, нужно ли его сохранить, поэтому дочерний элемент не будет сохранен.

Я не знаю, что делает ISession.Lock (), но, как сказала Айенде в своем ответе, мне больше не нужно этого делать. Поэтому я снял вызовы Lock ().

Вот как выглядит новый код:

public override TakeOffItem Get(long id)
{
    // Moved this line up
    var takeOffItem = base.Get(id);

    var materialGroups = _getMaterialGroupsForTakeOffItemQuery.GetResults(id);

    var fabricationNotesByMaterialDetailsId = _getFabricationNotesForTakeOffItemQuery.GetFabricationNotesForMaterialDetails(
        materialGroups.Where(mg => mg.MaterialDetails != null).Select(mg => mg.MaterialDetails.Id));

    _assignMaterialGroupsService.AssignMaterialGroups(id, takeOffItem, materialGroups, fabricationNotesByMaterialDetailsId);
    _session.Lock(takeOffItem, LockMode.None);
    return takeOffItem;
}        

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

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