无法获取要更新实体框架中导航属性的关系

Posted

技术标签:

【中文标题】无法获取要更新实体框架中导航属性的关系【英文标题】:Cannot get relationship to update for navigation properties in entity framework 【发布时间】:2012-03-07 18:11:24 【问题描述】:

我目前正在使用 EF4.3 和 Code First。我的对象的创建工作(通过我的视图 - 仅使用自动生成的创建),但是当我尝试编辑一个对象时,它不会保存任何最终与我的导航属性相关的更改。我一直是reading on relationships,但我不明白如何告诉我的上下文关系已经改变。

这是我的实现的一些示例代码。

@* Snippet from my view where I link into my ViewModel. *@
<div class="row">
    <div class="editor-label">
        @html.LabelFor(model => model.ManagerID)
    </div>
    <div class="editor-field">
        @Html.DropDownListFor(model => model.ManagerID, ViewBag.Manager as SelectList, String.Empty)
        @Html.ValidationMessageFor(model => model.ManagerID)
    </div>
</div>

这是我的控制器实现(我的编辑的 POST):

    [HttpPost]
    public ActionResult Edit(ProjectViewModel projectViewModel)
    
        if (ModelState.IsValid)
        
            Project project = new Project();
            project.ProjectID = projectViewModel.ProjectID;
            project.Name = projectViewModel.Name;
            project.ProjectManager = repository.GetUser(projectViewModel.ManagerID);
            repository.InsertOrUpdateProject(project);
            repository.Save();
            return RedirectToAction("Index");
        
        ViewBag.Manager = new SelectList(repository.GetUsers(), "UserID", "FullName", projectViewModel.ManagerID);
        return View(projectViewModel);
    

在我的项目对象中:

public class Project

    public int ProjectID  get; set; 
    [Required]
    public string Name  get; set; 

    // Navigation Properties
    public virtual User Manager  get; set; 

这是来自存储库(我的上下文所在的位置)的相应方法:

public void InsertOrUpdateProject(Project project)
    
        if (program.ProjectID == default(int))
        
            context.Projects.Add(project);
        
        else
        
            context.Entry(project).State = EntityState.Modified;
        
    

需要明确的是,这确实可以更新我的属性,但不会更新我的导航属性(在本例中为 Manager)。感谢任何帮助。

【问题讨论】:

【参考方案1】:

将状态设置为Modified 仅将标量属性标记为已修改,而不是导航属性。您有多种选择:

一个 hack(你不会喜欢它)

//...
else

    var manager = project.Manager;
    project.Manager = null;
    context.Entry(project).State = EntityState.Modified;
    // the line before did attach the object to the context
    // with project.Manager == null
    project.Manager = manager;
    // this "fakes" a change of the relationship, EF will detect this
    // and update the relatonship

从数据库中重新加载项目包括(急切加载)当前经理。然后设置属性。变更跟踪会再次检测到manager的变更并写入UPDATE。

在模型中为 Manager 导航属性公开一个外键属性:

public class Project

    public int ProjectID  get; set; 
    [Required]
    public string Name  get; set; 

    public int ManagerID  get; set; 
    public virtual User Manager  get; set; 

现在ManagerID 是一个标量属性,将状态设置为Modified 将包含此属性。此外,您不需要从数据库中加载 Manager 用户,您只需分配从视图中获取的 ID:

Project project = new Project();
project.ProjectID = projectViewModel.ProjectID;
project.Name = projectViewModel.Name;
project.ManagerID = projectViewModel.ManagerID;
repository.InsertOrUpdateProject(project);
repository.Save();

【讨论】:

优秀。谢谢你。我最初已经完成了外键路由,但遇到了一些问题。事实证明,它有效,但我必须使用导航属性定义 [ForeignKey("Name")] 属性,以便正确映射所有内容。接受你的回答。谢谢! @user1240560:使用我的答案中的名称,它实际上应该在没有 ForeignKey 属性的情况下工作(按名称约定进行 FK 检测)。但是,如果您的名称略有不同并且不遵循约定规则,那么是的,您需要注释。 @Slauma 我爱你,其他方法应该适用于 Code First 模型,但是使用 DataBase First 唯一对我有用的是你的 hack,我已经搞砸了好几天,现在我不得不说我非常爱你 未来的感谢【参考方案2】:

这里有几个选项,我将列出其中的 3 个:

选项 1:使用 GraphDiff

*这需要将上下文的Configuration.AutoDetectChangesEnabled 设置为true。

只需使用 NuGet 安装 GraphDiff

Install-Package RefactorThis.GraphDiff

然后

using (var context = new Context())

    var customer = new Customer()
    
        Id = 12503,
        Name = "Jhon Doe",
        City = new City()  Id = 8, Name = "abc" 
    ;

    context.UpdateGraph(customer, map => map.AssociatedEntity(p => p.City));
    context.Configuration.AutoDetectChangesEnabled = true;

    context.SaveChanges();

有关 GraphDiff 的更多详细信息,请查看here。

选项 2:查找和编辑

使用 EF 搜索您的实体以将其跟踪到上下文中。然后编辑属性。

*这需要将上下文的Configuration.AutoDetectChangesEnabled 设置为true。

var customer = new Customer()

    Id = 12503,
    Name = "Jhon Doe",
    City = new City()  Id = 8, Name = "abc" 
;

using (var context = new Contexto())

    var customerFromDatabase = context.Customers
                                      .Include(x => x.City)
                                      .FirstOrDefault(x => x.Id == customer.Id);

    var cityFromDataBase = context.Cities.FirstOrDefault(x => x.Id == customer.City.Id);

    customerFromDatabase.Name = customer.Name;
    customerFromDatabase.City = cityFromDataBase;                

    context.Configuration.AutoDetectChangesEnabled = true;
    context.SaveChanges();

选项 3:使用标量属性

就性能而言,这是最好的方法,但它会使您的课程因数据库问题而变得混乱。因为您需要创建一个标量(primitive 类型)属性来映射 Id。

*这样就不需要将Configuration.AutoDetectChangesEnabled 设置为true。而且您不需要对数据库进行查询来检索实体(就像前两个选项一样 - 是的 GraphDiff 在幕后完成它!)。

var customer = new Customer()

    Id = 12503,
    Name = "Jhon Doe",
    City_Id = 8,
    City = null
;

using (var contexto = new Contexto())

    contexto.Entry(customer).State = EntityState.Modified;
    contexto.SaveChanges();

【讨论】:

非常感谢,我相信如果Configuration.AutoDetectChangesEnabled = true; 不起作用,请检查其他选项【参考方案3】:

我不确定您所说的导航属性到底是什么意思?你的意思是像外键关系吗?如果是这样,请尝试以下数据注释:

public class Project

    public int ProjectID  get; set; 

    [Required]
    public string Name  get; set; 

    [ForeignKey("YourNavigationProperty")]
    public virtual UserManager  get; set; 

更新您的 EF 上下文,看看会发生什么?

更新

public class Project

    public int ProjectID  get; set; 

    [Required]
    public string Name  get; set; 

    [ForeignKey("ManagerId")]
    public ManagerModel UserManager  get; set; 


public class ManagerModel

    [Key]
    public int ManagerId  get; set; 

    public String ManagerName  get; set; 

看看有没有用?

【讨论】:

所以我没有明确定义我的 FK 名称。我希望能够将我的 Manager 值设置为一个新的 Manager(这似乎可以工作),但是当我调用 SaveChanges() 时,这些更改实际上并没有持久化到数据库中。我不知道如何做到这一点。

以上是关于无法获取要更新实体框架中导航属性的关系的主要内容,如果未能解决你的问题,请参考以下文章

实体框架检查属性是否为导航属性

使用实体框架中的导航属性填充多对多关系表

核心数据:获取关系属性

在实体框架中找出导致异常的确切实体

突然从实体框架中获取异常

EF更新多对多关系表中记录的时候,无法更新关系表的问题。