This has been driving me nuts for the last 2 days. I have 3 pretty basic classes (well, reduced for readability)
public class Employee
{
public string Name { set; get; }
virtual public Employer Employer { set; get; }
public Employee(string name)
{
this.Name = name;
}
}
,
// this basically ties Employee and his role in a company.
public class EmployeeRole{
public int Id { set; get; }
virtual public Employee Employee { set; get; }
public string Role { set; get; }
public EmployeeRole(Employee employee, string role){
this.Employee = employee;
this.Role = role;
}
}
and
public class Employer{
public string Name { set; get; }
List<EmployeeRole> employees = new List<EmployeeRole>();
virtual public List<EmployeeRole> Employees { get { return this.employees; } }
public Employer(string name, Employee creator){
this.Name = name;
this.Employees.Add(new EmployeeRole(creator, "Creator"));
creator.Employer = this;
}
}
Seems pretty simple. Not doing any specific configuration for those classes in DbContext.
But, when I run following code
using (DbContext db = DbContext.GetNewDbContext()){
Employee creator = new Employee("Bob");
db.Employees.Add(creator);
db.SaveChanges();
Employer employer = new Employer("employer", creator);
db.Employers.Add(employer);
db.SaveC开发者_StackOverflow社区hanges();
// I know I can call SaveChanges once (and it actually works in this case),
// but I want to make sure this would work with saved entities.
}
It throws following exception:
Collection was modified; enumeration operation may not execute.
Stack trace:
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource) at System.Collections.Generic.List
at System.Data.Entity.DbSet`1.Add(TEntity entity)1.Enumerator.MoveNextRare() at System.Collections.Generic.List
1.Enumerator.MoveNext() at System.Data.Objects.ObjectStateManager.PerformAdd(IList1 entries) at System.Data.Objects.ObjectStateManager.AlignChangesInRelationships(IList
1 entries) at System.Data.Objects.ObjectStateManager.DetectChanges() at System.Data.Objects.ObjectContext.DetectChanges() at System.Data.Entity.Internal.InternalContext.DetectChanges(Boolean force) at System.Data.Entity.Internal.Linq.InternalSet1.ActOnSet(Action action, EntityState newState, Object entity, String methodName) at System.Data.Entity.Internal.Linq.InternalSet
1.Add(Object entity)
Anyone has an idea what's going on and maybe how to fix it ? Thanks !
For me this looks like a bug in Entity Framework. I've cooked down your example to a simpler one but with the same structure:
public class TestA // corresponds to your Employee
{
public int Id { get; set; }
public TestB TestB { get; set; } // your Employer
}
public class TestB // your Employer
{
public TestB()
{
TestCs = new List<TestC>();
}
public int Id { get; set; }
public ICollection<TestC> TestCs { get; set; } // your EmployeeRoles
}
public class TestC // your EmployeeRole
{
public int Id { get; set; }
public TestA TestA { get; set; } // your Employee
}
These are three entities with cyclic relationships:
TestA -> TestB -> TestC -> TestA
If I use now a corresponding code with the same structure than yours I get the same exception:
var testA = new TestA();
var testB = new TestB();
var testC = new TestC();
context.TestAs.Add(testA);
testA.TestB = testB;
testB.TestCs.Add(testC);
testC.TestA = testA;
context.ChangeTracker.DetectChanges();
Note that I have used DetectChanges
instead of SaveChanges
because the stack trace in the exception makes clear that actually DetectChanges
causes the exception (which is called internally by SaveChanges
). I also found that calling SaveChanges
twice is not the problem. The problem here is only the "early" adding to the context before the whole object graph is completed.
The collection which was modified (as the exception is complaining about) is not the TestB.TestCs
collection in the model. It seems to be a collection of entries in the ObjectStateManager
. I could verify this by replacing ICollection<TestC> TestCs
with a single reference by TestC TestC
in the TestB
class. This way the model doesn't contain any collection at all but it still throws the same exception about a modified collection. (SaveChanges
will fail though with three single references because EF doesn't know in which order to save the entities due to the cycle. But that is another problem.)
I would consider it as a bug that EF change detection (DetectChanges
) seems to modify its own internal collection it is just iterating through.
Now, the fix to this problem is easy: Just Add
entities to the context as your last step before you call SaveChanges
:
var testA = new TestA();
var testB = new TestB();
var testC = new TestC();
testA.TestB = testB;
testB.TestCs.Add(testC);
testC.TestA = testA;
context.TestAs.Add(testA);
context.ChangeTracker.DetectChanges();
EF will add the whole related object graph to the context. This code succeeds (also using SaveChanges
instead of DetectChanges
).
Or your example:
using (DbContext db = DbContext.GetNewDbContext()){
Employee creator = new Employee("Bob");
Employer employer = new Employer("employer", creator);
db.Employees.Add(creator);
db.SaveChanges();
}
Edit
This was the same exception: Entity Framework throws "Collection was modified" when using observable collection. Following the code in that example the situation was similar: Adding an entity to the context and then afterwards changing/adding relationships to that entity.
Edit2
Interestingly this throws the same exception:
var testA = context.TestAs.Find(1); // assuming there is already one in the DB
var testB = new TestB();
var testC = new TestC();
testA.TestB = testB;
testB.TestCs.Add(testC);
testC.TestA = testA;
context.SaveChanges(); // or DetectChanges, it doesn't matter
So, I want to add relationships with new entities to the existing entity. A fix to this problem seems to be less obvious.
I had the same problem. It looks like a bug in EF.
I changed my code to explicitly add the entity to EF context before setting it to any other entity property.
I've just recently come across this problem myself. What I found is that EF hates indirect circular references. In your case it seems Employer should own the relationship to employee. So take out the reference back to employer from the employee class.
I ran into the same issue this morning, in my case I had an "Employee-Manager" association defined with a circular relationship. For example:
public class Employee
{
public string Name { set; get; }
virtual public Employee Manager { set; get; }
public Employee()
{
}
}
The app was crashing with the error above when setting the Manager property. At the end it turned out to be a bad implementation of the GetHashCode()
method in the Employee class. It seemed that EF was not able to spot the modified entity since the comparison among the entities was failing; thus, EF was thinking the collection had been modified.
精彩评论