InvalidOperationException при сохранении дочернего объекта с коллекцией - PullRequest
1 голос
/ 10 января 2012

У меня есть класс Foo со свойством типа ISet.Класс Bar, в свою очередь, имеет свойство Foo и ISet MiniBars.Используя NHibernate, я хочу сохранить экземпляр Foo, и после этого, но все еще в рамках той же транзакции NHibernate, добавить экземпляр Bar в свойство Bars объекта Foo и объект Foo в свойство Foo объекта Bar, а затемсм. также, что объект Bar сохраняется при фиксации транзакции.

Однако я получаю исключение изнутри NHibernate: "System.InvalidOperationException: Collection была изменена; операция перечисления может не выполняться."

Я пришел к выводу, что это как-то связано с ISet MiniBars.В конструкторе Bar по умолчанию этот набор задается с помощью

MiniBars = new HashedSet<MiniBar>();

. Если я удаляю эту строку кода или удаляю сопоставление свойства MiniBars из Bar.hbm.xml, все работает как положено.

Неработающий код:

using (var tx = session.BeginTransaction())
{
  Foo foo = new Foo();
  Foo.Id = 1;
  session.Save(foo);

  Bar bar = new Bar
  {
    Foo = foo; // The setter for Foo also adds Bar to the set Foo.Bars
  }

  tx.Commit(); // I wish this to save both foo and bar
}

Трассировка стека сгенерированного исключения:

System.InvalidOperationException : Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
at System.Collections.Generic.List`1.Enumerator.MoveNext()
at NHibernate.Engine.Cascade.CascadeCollectionElements(Object child, CollectionType collectionType, CascadeStyle style, IType elemType, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs: line 231
at NHibernate.Engine.Cascade.CascadeCollection(Object child, CascadeStyle style, Object anything, CollectionType type) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs: line 201
at NHibernate.Engine.Cascade.CascadeAssociation(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs: line 185
at NHibernate.Engine.Cascade.CascadeProperty(Object child, IType type, CascadeStyle style, Object anything, Boolean isCascadeDeleteEnabled) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs: line 148
at NHibernate.Engine.Cascade.CascadeOn(IEntityPersister persister, Object parent, Object anything) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Engine\Cascade.cs: line 126
at NHibernate.Event.Default.AbstractFlushingEventListener.CascadeOnFlush(IEventSource session, IEntityPersister persister, Object key, Object anything) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs: line 207
at NHibernate.Event.Default.AbstractFlushingEventListener.PrepareEntityFlushes(IEventSource session) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs: line 195
at NHibernate.Event.Default.AbstractFlushingEventListener.FlushEverythingToExecutions(FlushEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs: line 48
at NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event) in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Event\Default\DefaultFlushEventListener.cs: line 18
at NHibernate.Impl.SessionImpl.Flush() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Impl\SessionImpl.cs: line 1472
at NHibernate.Transaction.AdoTransaction.Commit() in d:\CSharp\NH\NH\nhibernate\src\NHibernate\Transaction\AdoTransaction.cs: line 187
at Company.Product.Test.DatabaseDependentFixtureBase.FillDatabase() in DatabaseDependentFixtureBase.cs: line 121
at Company.Product.Test.DatabaseDependentFixtureExample.Temp() in DatabaseDependentFixture.cs: line 40 

Трассировка стека указывает на эту функцию из NHibernate.Engine.Cascade:

    private void CascadeCollectionElements(object child, CollectionType collectionType, CascadeStyle style, IType elemType, object anything, bool isCascadeDeleteEnabled)
    {
        // we can't cascade to non-embedded elements
        bool embeddedElements = eventSource.EntityMode != EntityMode.Xml
                                || ((EntityType) collectionType.GetElementType(eventSource.Factory)).IsEmbeddedInXML;

        bool reallyDoCascade = style.ReallyDoCascade(action) && embeddedElements
                               && child != CollectionType.UnfetchedCollection;

        if (reallyDoCascade)
        {
            log.Info("cascade " + action + " for collection: " + collectionType.Role);

            foreach (object o in action.GetCascadableChildrenIterator(eventSource, collectionType, child))
                CascadeProperty(o, elemType, style, anything, isCascadeDeleteEnabled);

            log.Info("done cascade " + action + " for collection: " + collectionType.Role);
        }

        var childAsPersColl = child as IPersistentCollection;
        bool deleteOrphans = style.HasOrphanDelete && action.DeleteOrphans && elemType.IsEntityType
                             && childAsPersColl != null; //a newly instantiated collection can't have orphans

        if (deleteOrphans)
        {
            // handle orphaned entities!!
            log.Info("deleting orphans for collection: " + collectionType.Role);

            // we can do the cast since orphan-delete does not apply to:
            // 1. newly instantiated collections
            // 2. arrays (we can't track orphans for detached arrays)
            string entityName = collectionType.GetAssociatedEntityName(eventSource.Factory);
            DeleteOrphans(entityName, childAsPersColl);

            log.Info("done deleting orphans for collection: " + collectionType.Role);
        }
    }

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

Полагаю, что может быть ошибкой вNHibernate, но я думаю, что более вероятно, что я делаю что-то не так при использовании NHibernate.Любые идеи будут оценены!

1 Ответ

2 голосов
/ 10 января 2012

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

List<string> someCollection = new List<string>();

someCollection.Add("Hello");
someCollection.Add("World");
someCollection.Add("Hello");
someCollection.Add("World");

// Enumerate the collection
foreach (string item in someCollection)
{
    // If the item is "World", remove it from the collection
    if ("World".Equals(item))
    {
        someCollection.Remove(item); // This will throw an InvalidOperationException.
    }
}

В приведенном выше примере выдается исключение, когда мы пытаемся удалить элемент из коллекции, в которой мы выполняем перечисление.Это недопустимая операция.Чтобы преодолеть это, вам нужно изменить способ изменения коллекции.Примерами альтернативных подходов будет перечисление копии коллекции (т. Е. foreach (var item in someCollection.ToArray()));или отложить изменение коллекции до тех пор, пока вы не закончите перечисление.

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

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