【问题标题】:InvalidOperationException when saving child object with collection使用集合保存子对象时出现 InvalidOperationException
【发布时间】:2012-01-10 14:47:10
【问题描述】:

我有一个类 Foo,它有一个 ISet 类型的属性。 Bar 类又具有一个 Foo 属性和一个 MiniBar 的 ISet。使用 NHibernate 我希望持久化 Foo 的实例,然后,但仍在同一个 NHibernate 事务中,将 Bar 的实例添加到 Foo 对象的 Bars 属性,并将 Foo 对象添加到 Bar 对象的 Foo 属性,然后看到当我提交事务时,Bar 对象也被持久化了。

但是,我从 NHibernate 内部的某个地方收到异常:“System.InvalidOperationException : Collection 已修改;枚举操作可能无法执行。”

我得出的结论是,这与 MiniBars 的 ISet 有关。在 Bar 的默认构造函数中,这个集合是使用

设置的
MiniBars = new HashedSet<MiniBar>();

如果我删除这行代码,或者从 Bar.hbm.xml 中删除 MiniBars 属性的映射,一切都会正常运行。

不工作的代码:

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 时做错了什么。任何想法将不胜感激!

【问题讨论】:

  • 有什么解决办法吗?我有完全相同的问题。
  • 不,很遗憾没有。在我写完这个问题后不久,我就带着这个问题离开了这个项目,所以恐怕我不记得我们是如何解决这个问题的,或者即使我们这样做了。

标签: c# nhibernate


【解决方案1】:

当您在枚举集合的过程中并在这样做时继续尝试修改集合时,会发生您描述的错误。这是一个例子:

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()));或者将集合修改延迟到您完成枚举之后。

我建议查看异常的来源(我将假设它指向某些集合枚举),然后查看该点以对该集合进行一些修改。

【讨论】:

  • 谢谢。我想我应该写下 NHibernate 在tx.Commit() 上抛出了异常。我没有在自己的代码中列举任何集合。
  • 您可能没有自己进行枚举,您仍然可能在同一集合的某些内部枚举期间导致集合修改。查看异常中的堆栈跟踪并查看确切是哪段代码引发了异常。然后,您可以使用.NET Reflector 查看 NHibernate 代码(如果它正在抛出它)以查看相关代码的反汇编视图。这可能有助于找出原因。
猜你喜欢
  • 1970-01-01
  • 2014-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多