关于 System.Data.EntityState.Add 和 DbSet.Add 之间差异(如果有的话)的令人困惑的文章和文档

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

我正在使用 EF 5 开发 C# ASP.NET MVC 5 Web 应用程序。使用 EF 映射我的数据库表会生成 DbContext class 和 .edmx文件。今天,我正在阅读a great article about creating generic DAL classes,但我停在了以下句子:

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.

这与这些问题中提到的矛盾:

在上述所有问题的回答中,所有用户都提到使用System.Data.EntityState.Added与使用DbSet.Add完全相同。但是我首先提到的文章指出,使用 System.Data.EntityState.Added 不会级联通过图形。

根据我的测试,我得出结论,使用 System.Data.EntityState.Added 会像在 DBset.Add 的情况下一样在图中级联。是文章写错了,还是我的测试和问答?

这些方法是相同的,您可以通过常规测试来验证,或者,如果您想完全确定 - 通过对 EF 6 代码的一些探索。

  1. DbSet.Add 方法 (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;
    }
    

这会调用 InternalSet<T>.Add(object) 方法。

  1. DbEntityEntry<T>.State 属性 (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Infrastructure/DbEntityEntry.cs)

    public EntityState State
    {
        get { return _internalEntityEntry.State; }
        set { _internalEntityEntry.State = value; }
    }
    

其中 _internalEntityEntry 属于 InternalEntityEntry 类型。

InternalEntityEntry.State 属性 (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;
                }
            }
        }
    }

你看到如果实体被分离(你的情况)并且状态被添加 - 相同的 InternalSet<T>.Add(object) 被调用。

至于测试验证:

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

我不认识那个博客的作者。我确实认识 book DbContext 的作者(尽管不是本人)。他们对 EF 了如指掌。所以当他们在第 80 页写

Calling DbSet.Add and setting the State to Added both achieve exactly the same thing.

我知道我在做什么。他们完全一样,即:

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.

我也根据经验知道它是这样工作的。但为了消除任何疑问,在 EF 的源代码中,DbSet.AddDbEntityEntry.State(当设置为 Added 时)都到达了 ObjectContext 中完成实际工作的同一点:

public virtual void AddObject(string entitySetName, object entity)

这是一个继续迷惑开始使用 EF 的开发人员的功能,从 Whosebug 上的大量问题中可以明显看出 "how come my entities are duplicated?"。 Julie Lerman 写了一篇 entire blog 解释了为什么会发生这种情况。

这种持续的错觉使 EF 团队决定 change this behavior 在 EF7 中。

也许您提到的博客的作者是那些受骗的开发人员之一。