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

2

7

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?

Melano answered 15/4, 2016 at 16:58 Comment(0)
I
4

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);                
}
Illtreat answered 19/4, 2016 at 16:5 Comment(4)
so you are saying that using EntityState.Added and using DBset.Add are exactly the same ?Melano
Yes, if your entity is not tracked by a context both this methods will do exactly the same thing, with exactly the same result, which is confirmed by test example plus source code exploration.Illtreat
so let say the entity is being tracked by the context ,, then will they differ ? as you said "if your entity is not tracked ..."Melano
@JohnJohn I just verified that even if entity is already attached - both methods do the same thing with same result anyway.Illtreat
B
4

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.

Blouson answered 19/4, 2016 at 18:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.