防止实体框架为导航属性插入值

本文关键字:属性 插入 导航 实体 框架 | 更新日期: 2023-09-27 18:12:31

我正在使用实体框架4.0开发一个WPF应用程序。当我试图保存对象时,我得到了一个主键异常,但是主键是一个autoincrement字段,我无法理解异常的原因。

因此,在尝试了这个和那个之后,以及一些调试和使用SQL分析器,我发现在插入我的对象之前,必须在父表中插入一条记录,因为我设置了该对象的导航属性。

所以关键是如果尝试插入Employee对象并将其部门设置为Employee。Department = deptObject,则在Department对象上插入一条新记录

请建议我以某种方式导航属性对象不会被插入数据库,任何属性或任何方法,任何。

谢谢

防止实体框架为导航属性插入值

如果您错误地使用分离实体,EF就是这样工作的。我猜你用的是这样的:

var employee = new Employee();
employee.Department = GetDepartmentFromSomewhere(departmentId);
...
using (var context = new YourContext())
{
    context.Employees.AddObject(employee);
    context.SaveChanges();
}

此代码准备员工实体,添加对现有部门的引用,并将新员工保存到数据库中。问题在哪里?问题是AddObject不仅添加了员工,还添加了整个对象图。这就是EF的工作原理——你不能有对象图,其中一部分对象连接到上下文,而另一部分不连接。AddObject将图中的每个对象添加为一个新对象(新对象=插入数据库)。因此,您必须更改操作顺序或手动修复实体的状态,以便您的上下文知道该部门已经存在。

第一个解决方案-使用相同的上下文来加载部门和保存员工:

using (var context = new YourContext())
{
    var employee = new Employee();
    ...
    context.Employees.AddObject(employee);
    employee.Department = context.Departments.Single(d => d.Id == departmentId);
    context.SaveChanges();
}

第二个解决方案-将实体分别连接到上下文,然后在实体之间进行引用:

var employee = new Employee();
...
var department = GetDepartmentFromSomewhere(departmentId);
using (var context = new YourContext())
{
    context.Employees.AddObject(employee);
    context.Departments.Attach(department);
    employee.Department = department;
    context.SaveChanges();
}

第三个解决方案-手动更正部门的状态,以便上下文不再插入它:

var employee = new Employee();
employee.Department = GetDepartmentFromSomewhere(departmentId);
...
using (var context = new YourContext())
{
    context.Employees.AddObject(employee);
    context.ObjectStateManager.ChangeObjectState(employee.Department, 
                                                 EntityState.Unchanged);
    context.SaveChanges();
}

我想添加第四个解决方案,除了Ladislavs已经提供的三个解决方案。事实上,这是Naor简短回答的详细版本。我正在使用实体框架版本6。


将部门id分配给员工而不是部门对象

我倾向于在我的模型类中除了导航属性之外还有一个"外键值"属性。

因此,在Employee类上,我有一个Department属性和一个int类型的DepartmentId(如果Employee可能没有Department,则将int设置为空):

public class Employee
{
    public int Id { get; set; }
    public String EmployeeName { get; set; }

    #region FK properties
    public Department Department { get; set; }
    public int? DepartmentId { get; set; }
    #endregion
}

你现在能做的就是设置DepartmentId:所以不是:

employee.Department = departmentObject;

设置:

employee.DepartmentId = departmentObject.Id;

employee.DepartmentId = departmentid

现在对添加的员工调用SaveChanges时,只保存该员工,不创建新部门。但是EmployeeDepartment的引用设置正确,因为分配了部门id。


更多信息

我通常只在读取/处理雇员时访问Employee类的Department对象。在创建或更新员工时,我会使用Employee类的DepartmentId属性来分配给。

不赋值给EmployeeDepartment属性有一个缺点:它可能会使调试更加困难,因为在调用SaveChanges并重新读取雇员之前,不可能看到或使用EmployeeDepartment对象。


修复EF6中的实体状态信息

这是Ladislavs解3。

使用EF6是这样做的:

_context.Entry(employee.Department).State = EntityState.Unchanged;

当您将部门设置为employee -我认为您应该验证从数据库中检索到的部门及其附属实体。
另外,您可以使用部门的id(外键属性)来代替设置部门导航属性。

在我的例子中,我的集合是从不同的上下文(不同的数据库)手动填充的。为了防止主上下文试图保存这些集合,我最后添加了

[NotMapped, NotNavigable]