I am working on a C# ASP.NET MVC 5 web application with EF 5. Mapping of my database tables using EF generates a DbContext
class and an .edmx
file. Today, I was reading a great article about creating generic DAL classes, but I stopped on the following sentence:
Note that using the Entry method to change the state of an entity will only affect the actual entity that you pass in to the method. It won’t cascade through a graph and set the state of all related objects, unlike the DbSet.Add method.
That contradicts what is mentioned in these questions:
In all the above questions’ answers, all users mentioned that using System.Data.EntityState.Added
is exactly the same as using DbSet.Add
. But the article I mentioned first states that using System.Data.EntityState.Added
will not cascade through the graph.
Based on my test, I conclude that using System.Data.EntityState.Added
will cascade through the graph same as in the DBset.Add
case. Is the article wrong, or is it my test and the Q&A?
State = EntityState. Modified; , you are not only attaching the entity to the DbContext , you are also marking the whole entity as dirty. This means that when you do context. SaveChanges() , EF will generate an update statement that will update all the fields of the entity.
Detached. 1. The object exists but is not being tracked. An entity is in this state immediately after it has been created and before it is added to the object context.
This can be achieved in several ways: setting the EntityState for the entity explicitly; using the DbContext. Update method (which is new in EF Core); using the DbContext. Attach method and then "walking the object graph" to set the state of individual properties within the graph explicitly.
Those methods are the same which you can verify by regular testing, or, if you want to be completely sure - by some exploration of EF 6 code.
DbSet.Add
method (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/DbSet.cs)
public virtual TEntity Add(TEntity entity)
{
Check.NotNull<TEntity>(entity, "entity");
this.GetInternalSetWithCheck("Add").Add((object) entity);
return entity;
}
This calls InternalSet<T>.Add(object)
method.
DbEntityEntry<T>.State
property (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Infrastructure/DbEntityEntry.cs)
public EntityState State
{
get { return _internalEntityEntry.State; }
set { _internalEntityEntry.State = value; }
}
Where _internalEntityEntry
is of InternalEntityEntry
type.
InternalEntityEntry.State
property (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Internal/EntityEntries/InternalEntityEntry.cs)
public virtual EntityState State
{
get { return IsDetached ? EntityState.Detached : _stateEntry.State; }
set
{
if (!IsDetached)
{
if (_stateEntry.State == EntityState.Modified
&& value == EntityState.Unchanged)
{
// Special case modified to unchanged to be "reject changes" even
// ChangeState will do "accept changes". This keeps the behavior consistent with
// setting modified to false at the property level (once that is supported).
CurrentValues.SetValues(OriginalValues);
}
_stateEntry.ChangeState(value);
}
else
{
switch (value)
{
case EntityState.Added:
_internalContext.Set(_entityType).InternalSet.Add(_entity);
break;
case EntityState.Unchanged:
_internalContext.Set(_entityType).InternalSet.Attach(_entity);
break;
case EntityState.Modified:
case EntityState.Deleted:
_internalContext.Set(_entityType).InternalSet.Attach(_entity);
_stateEntry = _internalContext.GetStateEntry(_entity);
Debug.Assert(_stateEntry != null, "_stateEntry should not be null after Attach.");
_stateEntry.ChangeState(value);
break;
}
}
}
}
You see that if entity is detached (your case) and state is Added - the same InternalSet<T>.Add(object)
is called.
As for verification by testing:
using (var ctx = new TestDBEntities()) {
// just some entity, details does not matter
var code = new Code();
// another entity
var error = new Error();
// Code has a collection of Errors
code.Errors.Add(error);
var codeEntry = ctx.Entry(code);
// modify code entry and mark as added
codeEntry.State = EntityState.Added;
// note we did not do anything with Error
var errorEntry = ctx.Entry(error);
// but it is marked as Added too, because when marking Code as Added -
// navigation properties were also explored and attached, just like when
// you do DbSet.Add
Debug.Assert(errorEntry.State == EntityState.Added);
}
I don't know the writer of that blog. I do know the writers of the book DbContext though (albeit not in person). They know EF inside-out. So when on page 80 they write
Calling
DbSet.Add
and setting theState
toAdded
both achieve exactly the same thing.
I know what I'm up to. They do exactly the same thing, which is:
If the entity is not tracked by the context, it will start being tracked by the context in the
Added
state. BothDbSet.Add
and setting theState
toAdded
are graph operations— meaning that any other entities that are not being tracked by the context and are reachable from the root entity will also be marked asAdded
.
I also know by experience that it works that way. But to remove any doubt, in EF's source code, both DbSet.Add
and DbEntityEntry.State
(when set to Added
) arrive at the same point in ObjectContext
that does the actual work:
public virtual void AddObject(string entitySetName, object entity)
It's a feature that continues to delude developers that start working with EF, as is evident from the large number of questions at StackOverflow asking something along the lines of "how come my entities are duplicated?". Julie Lerman wrote an entire blog explaining why this may happen.
This continued delusion made the EF team decide to change this behavior in EF7.
Maybe the writer of the blog you refer to was one of those deluded developers.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With