In this post let's see how we can remove/delete an item from a collection in terms of Entity Framework.
It's always better to go with an example, consider the following scenario.
I have an entity, Department which has Contacts.
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Department Department { get; set; }
}
I have my DbContext as follows.
public class MyDbContext : DbContext
{
public DbSet<Department> Departments { get; set; }
}
Here you can see that I have exposed only Departments as a DbSet, because I will only be accessing Contacts through their Department.
Now I have my DbContext initializer which seeds some data. Please note that I am inheriting from DropCreateDatabaseAlways.
public class MyDbContextInitializer : DropCreateDatabaseAlways<MyDbContext>
{
protected override void Seed(MyDbContext context)
{
context.Departments.AddOrUpdate(d => d.Name, new Department()
{
Name = "Microsoft Visual Studio",
Contacts = new List<Contact>()
{
new Contact() { Name = "Jaliya Udagedara" },
new Contact() { Name = "John Smith" }
}
});
context.SaveChanges();
Department department = context.Departments.FirstOrDefault();
Contact contact = department.Contacts.FirstOrDefault();
department.Contacts.Remove(contact);
context.SaveChanges();
}
}
Here you can see that after adding a Department with some set of Contacts, I am removing a Contact.
Now from the Main method, let's try to see who are the Contacts for my Department (here please note that I am not following best practices (null handling etc.), as this is only for demonstration purposes).
static void Main(string[] args)
{
Database.SetInitializer(new MyDbContextInitializer());
using (MyDbContext context = new MyDbContext())
{
foreach (Contact contact in context.Departments.FirstOrDefault().Contacts)
{
Console.WriteLine(contact.Name);
}
}
}
Output would be as follows.
|
Contacts of Department |
It’s just like we expected, initially I have added two Contacts and later I have removed one. So now there is only one Contact in the Department.
Out of curiosity, let’s check the Contacts table in the Database to see whether the Contact is deleted from the back end as well.
|
Contacts in the table |
Here you can see that the Contact is still there, only the relationship has been deleted. So this approach will cause orphan records.
And this was even possible because the Department_Id foreign key allowed null. Now let’s modify the code so that the Contact should always belong to a Department (actually that’s why I initially exposed only Departments as a DbSet in MyDbContext).
I can easily do that change by modifying the Contact class as follows.
public class Contact
{
public int Id { get; set; }
public string Name { get; set; }
public int Department_Id { get; set; }
[ForeignKey("Department_Id")]
public virtual Department Department { get; set; }
}
Now let’s run the application.
This time I am thrown with an error.
|
Error |
The error is, System.InvalidOperationException: The operation failed: The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
This is a very obvious error. That is because when we are removing a Contact from the Department, as you just saw, only the relationship is getting deleted which causes Department_Id of Contact to be null. And we are no longer allowing nulls there.
Now let’s see how we can get this fixed. That is deleting both the relationship and the Contact.
We have two options.
1. Update the state of the entity we want to remove to Deleted
Department department = context.Departments.FirstOrDefault();
Contact contact = department.Contacts.FirstOrDefault();
// Marking entity state as deleted
context.Entry(contact).State = EntityState.Deleted;
context.SaveChanges();
Here we don't want to remove the Contact from the Department, we can directly update the state of the entity to Deleted. So upon context.SaveChanges(), Contact will get deleted.
2. ObjectContext.DeleteObject
Department department = context.Departments.FirstOrDefault();
Contact contact = department.Contacts.FirstOrDefault();
// ObjectContext.DeleteObject
((IObjectContextAdapter)context).ObjectContext.DeleteObject(contact);
context.SaveChanges();
Here also we can skip department.Contacts.Remove(contact), we can directly call DeleteObject(). Again upon context.SaveChanges(), Contact will get deleted.
Using any of these approaches, we can remove/delete a Contact from the Department. And most importantly that's without exposing Contacts as DbSet in MyDbContext.
|
Contacts of Department |
|
Contacts in the table |
I am uploading the full sample code to OneDrive.
Hope this helps.
Happy Coding.
Regards,
Jaliya