我有一个类Foo,其属性类型为ISet。 Bar类又有一个Foo属性和一个MiniBars的ISet。使用NHibernate我希望保留一个Foo实例,然后,但仍然在同一个NHibernate事务中,将一个Bar实例添加到Foo对象的Bars属性,将Foo对象添加到Bar对象的Foo属性,然后当我提交事务时,也会看到Bar对象被保留。
但是,我从NHibernate内部的某处获得了一个Excepetion:“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时我更有可能做错了。任何想法将不胜感激!
答案 0 :(得分:2)
当您正在对集合进行枚举并继续尝试修改集合时,会出现您所描述的错误。这是一个例子:
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())
);或者在完成枚举之后延迟收集修改。
我建议查看异常的来源(我将假设指向一些集合枚举),然后环顾这一点以对该集合进行一些修改。