The DbContext of Entity Framework lets us add, remove, attach and detach entities to and from it. Calling SaveChanges on the DbContext the Entity Framework performs different actions on entities dependant on their EntityState. So an added entity gets inserted, a removed entity gets deleted and so on.
In this example we use the AssemblyItem entity from a previous post to perform some of the most common actions on the entities and DbContext. All the tests below passed.
Add an Entity
To add an entity to the DbContext we instantiate an object of the entity type and call the DbSet Add method. The added entity gets an ‘added’ state. Once we call SaveChanges the entity is saved to the database. The entity now has an ‘unchanged’ state. We call the Entry method on the DbContext to find the state for a given entity.
using System.Data; using System.Data.Entity; using CodeFirstData; using CodeFirstPocos; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CodeFirstTests { [TestClass] public class BomTests { [TestMethod] public void AddingEntitySetStateToAdded() { Database.SetInitializer(new DropBomContextIfModelChanges()); var bomContext = new BomContext(); AssemblyItem assemblyItem = CreateAssemblyItemStub(); bomContext.AssemblyItems.Add(assemblyItem); Assert.IsTrue( bomContext.Entry(assemblyItem) .State == EntityState.Added); } [TestMethod] public void CallingSaveChanesOnAddedEntitySetStateToUnchanged() { Database.SetInitializer(new DropBomContextIfModelChanges()); var bomContext = new BomContext(); AssemblyItem assemblyItem = CreateAssemblyItemStub(); bomContext.AssemblyItems.Add(assemblyItem); bomContext.SaveChanges(); Assert.IsTrue( bomContext.Entry(assemblyItem) .State == EntityState.Unchanged); } private AssemblyItem CreateAssemblyItemStub() { const int bomId = 1; var assemblyItem = new AssemblyItem { BillOfMaterialsId = bomId, Description = "Painter's putty", PartNumber = "Glib043", Price = 15.48m, Quantity = 40, }; return assemblyItem; } } }
Remove an Entity
If we have an entity we do not longer wish to store in the database we can call Remove on the DbContect for this entity. After this operation the state changes to ‘deleted’. To really delete the entity from the database we need to finally call SaveChanges. After that the entity state is ‘detached’ from the DbContext.
using System.Data; using System.Data.Entity; using CodeFirstData; using CodeFirstPocos; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Linq; namespace CodeFirstTests { [TestClass] public class BomTests { [TestMethod] public void RemoveEntitySetStateToDeleted() { Database.SetInitializer(new DropBomContextIfModelChanges()); var bomContext = new BomContext(); AssemblyItem assemblyItem = bomContext.AssemblyItems.First(); Assert.IsTrue( bomContext.Entry(assemblyItem) .State == EntityState.Unchanged); bomContext.AssemblyItems.Remove(assemblyItem); Assert.IsTrue( bomContext.Entry(assemblyItem) .State == EntityState.Deleted); bomContext.SaveChanges(); Assert.IsTrue( bomContext.Entry(assemblyItem) .State == EntityState.Detached); } } }
Attach an Entity
If we have an entity which already exists in the database but is somehow unattached from the DbContext we can call Attach on the DbSet to let Entity Framework get back to work with tracking changes. The state after attaching an entity is by default ‘unchanged’.
using System.Data; using System.Data.Entity; using CodeFirstData; using CodeFirstPocos; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CodeFirstTests { [TestClass] public class BomTests { [TestMethod] public void AttachEntitySetStateToUnchanged() { Database.SetInitializer(new DropBomContextIfModelChanges()); var bomContext = new BomContext(); AssemblyItem assemblyItem = GetDetachedExistingAssemblyItem(); bomContext.AssemblyItems.Attach(assemblyItem); Assert.IsTrue( bomContext.Entry(assemblyItem) .State == EntityState.Unchanged); } private AssemblyItem GetDetachedExistingAssemblyItem() { using (var bomContext = new BomContext()) { AssemblyItem assemblyItem = bomContext.AssemblyItems.Find(3); return assemblyItem; } } } }
Detach an Entity
To explicitly detach an entity from the DbContext we can either take the entity outside of the DbContext scope or call the Detach method on the ObjectContext under the hood of the DbContext.
using System.Data; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Data.Objects; using System.Linq; using CodeFirstData; using CodeFirstPocos; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CodeFirstTests { [TestClass] public class BomTests { [TestMethod] public void DetachEntitySetStateToDetached() { Database.SetInitializer(new DropBomContextIfModelChanges()); var bomContext = new BomContext(); AssemblyItem assemblyItem = bomContext.AssemblyItems.First(); ObjectContext objectContext = ((IObjectContextAdapter) bomContext).ObjectContext; objectContext.Detach(assemblyItem); Assert.IsTrue( bomContext.Entry(assemblyItem) .State == EntityState.Detached); } } }
Using Entry
A much cleaner way of Adding, Removing, Attaching or Detaching an entity is by using the Entry method of the DbContext. It is smart enough to know to which DbSet the entity belongs and if the entity is already in the database or not (attach or add). You can set the current Entity State in one fell swoop as well. The example below shows an Attach operation and changing the state to ‘modified’.
using System.Data; using System.Data.Entity; using CodeFirstData; using CodeFirstPocos; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace CodeFirstTests { [TestClass] public class BomTests { [TestMethod] public void AttachEntitySetStateToUnchanged() { Database.SetInitializer(new DropBomContextIfModelChanges()); var bomContext = new BomContext(); AssemblyItem assemblyItem = GetDetachedExistingAssemblyItem(); bomContext.Entry(assemblyItem).State = EntityState.Modified; Assert.IsTrue( bomContext.Entry(assemblyItem) .State == EntityState.Modified); } private AssemblyItem GetDetachedExistingAssemblyItem() { using (var bomContext = new BomContext()) { AssemblyItem assemblyItem = bomContext.AssemblyItems.Find(3); return assemblyItem; } } } }
