【问题标题】:EF Code First : One to Many relationship update the navigation objectEF Code First:一对多关系更新导航对象
【发布时间】:2018-02-11 14:29:24
【问题描述】:

让我通过一个例子来解释我的问题:

我有以下实体:

public class Project
{
    public int ProjectId { get; set; }
    public string ProjectName { get; set; }

    public int? BranchId { get; set; }
    public virtual Branch Branch { get; set; }
}

public class Branch
{
    public int BranchId { get; set; }
    public string BranchName { get; set; }

    public virtual ICollection<Project> Projects { get; set; }

    public Branch()
    {
        Projects = new ObservableCollection<Project>();
    }
}

我的 DbContext 为:

public class MyContext : DbContext
{
    public DbSet<Project> Projects { get; set; }
    public DbSet<Branch> Branches { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Branch>().HasMany<Project>(b => b.Projects).WithOptional(p => p.Branch).HasForeignKey(p => p.BranchId);
    }
}

从不同的上下文中添加/更新/删除分支和项目实体。项目在没有分支的情况下存在,但经过一些工作流程后,项目需要从给定的分支列表中附加到一个分支。

如果我试试这个,效果很好:

    private void ForeignKeyTest()
    {
        using (var db = new MyContext())
        {
            var project = db.Projects.Find(1);
            project.BranchId = 8;

            db.SaveChanges();

            var msg = project.Branch.BranchName; //call to the Branch after save

            MessageBox.Show(msg);
        }
    }

但是,如果我尝试这个,我会得到一个 NullReference 异常:

    private void ForeignKeyTest()
    {
        using (var db = new MyContext())
        {
            var project = db.Projects.Find(1);
            project.BranchId = 8;

            var msg = project.Branch.BranchName; //call to the Branch before save

            db.SaveChanges();

            MessageBox.Show(msg);
        }
    }

现在,我知道这是一种预期行为,因为 Project 的 Branch 属性未初始化。 但我的问题是,如何在保存项目之前获取 BranchId 后强制更新分支(此时仍保留与项目的一些处理,因此不能仅保存到分支)。希望我清楚地解释自己。

【问题讨论】:

    标签: entity-framework ef-code-first dbcontext


    【解决方案1】:

    至少有两种方法可以实现目标。

    首先是使用DbReferenceEntry&lt;TEntity, TProperty&gt;.Load 方法显式强制(重新)加载相关导航属性:

    var project = db.Projects.Find(1);
    project.BranchId = 8;
    db.Entry(project).Reference(e => e.Branch).Load(); // <--
    var branchName = project.Branch.Name;
    

    二是使用DbChangeTracker.DetectChanges方法强制更新所有修改的导航属性:

    var project = db.Projects.Find(1);
    project.BranchId = 8;
    db.ChangeTracker.DetectChanges(); // <--
    var branchName = project.Branch.Name;
    

    这两种方法都适用于您的场景,但第一种方法更可靠 - 无论是否使用代理,也适用于新实体(例如 db.Projects.Add)。

    【讨论】:

    • 嗨伊万斯托耶夫!感谢你的回答。这两个选项都工作得很好。但是,我还有另一个问题。如果我只有对存储库的 DbSet 属性(隐藏 DbContext)的公共访问权限,是否可以实现相同的目标?
    • 不幸的是,您需要访问DbContext,而DbSet&lt;T&gt; 不会公开它所绑定的DbContext(尽管它在内部引用了它)。
    • 相反,我想我可以将一个方法“DetectChanges()”添加到我的存储库中,这反过来将调用它作为受保护属性持有的 DbContext 上的相同方法。我不想将 DbContext 暴露给调用代码。可以接受吗?
    • 为我工作 :)
    • 嗨,伊万·斯托耶夫。在考虑了您对第一个选项更可靠的编辑之后,我在我的基础存储库中提出了以下内容,但需要测试。请查看并发表评论。 ' public void LoadChild(T entity, Expression> child) where TChild : PersistentEntity { Db.Entry(entity).Reference(child).Load(); } '
    猜你喜欢
    • 1970-01-01
    • 2022-07-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-07
    相关资源
    最近更新 更多