Search code examples
entity-frameworkentity-framework-5dbcontextdbset

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:

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?


Solution

  • 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);                
    }