Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Confusing articles and documentation about the differences (if any) between System.Data.EntityState.Add & DbSet.Add

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:

  • http://forums.asp.net/p/2015170/5803192.aspx
  • http://forums.asp.net/p/2060606/5943259.aspx
  • Difference between DbSet.Add(entity) and entity.State = EntityState.Added
  • What is the difference between IDbSet.Add and DbEntityEntry.State = EntityState.Added?

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?

like image 637
John John Avatar asked Apr 15 '16 16:04

John John


People also ask

What does EntityState Modified do?

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.

What is EntityState detached?

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.

How do I change the state of entity using DB 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.


2 Answers

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.

  1. 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.

  1. 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);                
}
like image 153
Evk Avatar answered Oct 13 '22 12:10

Evk


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 the State to Added 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. Both DbSet.Add and setting the State to Added 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 as Added.

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.

like image 30
Gert Arnold Avatar answered Oct 13 '22 10:10

Gert Arnold