Mou Mou - 1 month ago 6
C# Question

Entity Framework: Not being able to remove child data

Error is throwing when control iterate in foreach loop.

foreach (var existingAddress in existingCustomer.Addresses.Where(a => a.AddressID == 5).ToList())
{
foreach (var CustContacts in existingAddress.Contacts.Where(a => a.ContactID == 5))
{
CurrentContacts = CustContacts;
existingAddress.Contacts.Remove(CurrentContacts);
//CurrentAddress.Contacts.ToList().ForEach(r => db.Contacts.Remove(CurrentContacts));
}

CurrentAddress = existingAddress;
existingCustomer.Addresses.Remove(CurrentAddress);
//existingCustomer.Addresses.ToList().ForEach(r => db.Addresses.Remove(CurrentAddress));
}


When this line
existingAddress.Contacts.Remove(CurrentContacts);
executes, then error message is


Collection was modified; enumeration operation may not execute.


If I execute this line
CurrentAddress.Contacts.ToList().ForEach(r => db.Contacts.Remove(CurrentContacts));
instead of this line
existingAddress.Contacts.Remove(CurrentContacts);
then error message i am getting


Additional information: Object reference not set to an instance of an object.


I am new in EF so I am not being able to figure out how to remove data from child table.

My entity relationship is Customer > Address > Contacts

A customer may have multiple address and each address may have multiple contact details.

My full code as follows which I used to update parent customer object and trying to remove specific data from address and contact child table and also insert two new data in child table.

private void button3_Click(object sender, EventArgs e)
{
Addresses CurrentAddress = null;
Contacts CurrentContacts = null;

using (var db = new TestDBContext())
{
var existingCustomer = db.Customer
.Include(a => a.Addresses.Select(x => x.Contacts))
.FirstOrDefault(p => p.CustomerID == 5);

existingCustomer.FirstName = "Test Customer122";

// selecting address
foreach (var existingAddress in existingCustomer.Addresses.Where(a => a.AddressID == 5).ToList())
{
foreach (var CustContacts in existingAddress.Contacts.Where(a => a.ContactID == 5))
{
CurrentContacts = CustContacts;
existingAddress.Contacts.Remove(CurrentContacts);
//CurrentAddress.Contacts.ToList().ForEach(r => db.Contacts.Remove(CurrentContacts));
}

CurrentAddress = existingAddress;
existingCustomer.Addresses.Remove(CurrentAddress);
//existingCustomer.Addresses.ToList().ForEach(r => db.Addresses.Remove(CurrentAddress));
}


Addresses oAdrModel = new Addresses();
oAdrModel.Address1 = "test add2";
oAdrModel.Address2 = "test add2";
oAdrModel.SerialNo = 3;
oAdrModel.IsDefault = true;
oAdrModel.CustomerID = existingCustomer.CustomerID;
db.Addresses.Add(oAdrModel);

Contacts ContactModel = new Contacts();
ContactModel.Phone = "1111111-33";
ContactModel.Fax = "1-1111111";
ContactModel.SerialNo = 4;
ContactModel.IsDefault = true;
ContactModel.AddressID = CurrentAddress.AddressID;
db.Contacts.Add(ContactModel);


db.SaveChanges();
}
}


Entity related classes



public class CustomerBase
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

[NotMapped]
public string Address1 { get; set; }

[NotMapped]
public string Address2 { get; set; }

[NotMapped]
public string Phone { get; set; }

[NotMapped]
public string Fax { get; set; }

}

public class Customer : CustomerBase
{
public virtual List<Addresses> Addresses { get; set; }
}

public class Addresses
{
[Key]
public int AddressID { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public bool IsDefault { get; set; }
public int SerialNo { get; set; }
public virtual List<Contacts> Contacts { get; set; }

public int CustomerID { get; set; }
public virtual Customer Customer { get; set; }
}

public class Contacts
{
[Key]
public int ContactID { get; set; }

public string Phone { get; set; }
public string Fax { get; set; }
public bool IsDefault { get; set; }
public int SerialNo { get; set; }

public int AddressID { get; set; }
public virtual Addresses Customer { get; set; }

}


Full Working code



using (var db = new TestDBContext())
{
//db.Database.Log = s => MyLogger.Log("EFApp", s);

var existingCustomer = db.Customer
.Include(a => a.Addresses.Select(x => x.Contacts))
.FirstOrDefault(p => p.CustomerID == 5);

existingCustomer.FirstName = "Test Customer123";

existingCustomer.Addresses.Where(a => a.AddressID == 5).ToList().ForEach(r => db.Addresses.Remove(r));
existingCustomer.Addresses.Where(a => a.AddressID == 5).SelectMany(ad => ad.Contacts).Where(c=> c.ContactID==5).ToList().ForEach(r => db.Contacts.Remove(r));

Addresses oAdrModel = new Addresses();
oAdrModel.Address1 = "test xxx";
oAdrModel.Address2 = "test xxx";
oAdrModel.SerialNo = 3;
oAdrModel.IsDefault = true;
oAdrModel.CustomerID = 5;
db.Addresses.Add(oAdrModel);
db.SaveChanges();
int CurAddressID = oAdrModel.AddressID;

Contacts ContactModel = new Contacts();
ContactModel.Phone = "XX-1111111-33";
ContactModel.Fax = "XX-1-1111111";
ContactModel.SerialNo = 4;
ContactModel.IsDefault = true;
ContactModel.AddressID = CurAddressID;
db.Contacts.Add(ContactModel);

db.SaveChanges();
}

Answer

Your problem isn't directly related to EF, but to Enumerables. You can't call Remove, Add or anything else that modifies the collection on a collection that you are currently enumerating. (Which is why the error message says "Collection was modified; enumeration operation may not execute.")

This part for example:

foreach (var CustContacts in existingAddress.Contacts.Where(a => a.ContactID == 5))
{
    CurrentContacts = CustContacts;
    existingAddress.Contacts.Remove(CurrentContacts);
}

You are enumerating Contacts and within the loop you are removing contacts. A simple workaround is to call ToList (as you have done in the outer loop) to make sure you are working with a different Enumeration.

e.g.

foreach (var CustContacts in existingAddress.Contacts.Where(a => a.ContactID == 5).ToList())
{
    CurrentContacts = CustContacts;
    existingAddress.Contacts.Remove(CurrentContacts);
}

I'm not entirely sure what you are trying to achieve but from the looks of it you might be better off if you had a clean implementation of cascade delete in your database (remove related/connected entries automatically).

Comments