我缺少EF的基本概念是什么?

时间:2014-01-16 11:37:56

标签: c# asp.net-mvc entity-framework entity-framework-5 code-first

我确信我误解了EF5的工作方式。

在[上一个问题]中,我询问了如何在ASP.NET MVC应用程序中的操作之间传递值,并建议我可以使用TempData作为传递数据的机制(在我的情况下,我已经去了在EF中代表我的数据模型的POCO。

我在MVC中的控制器不知道EF中的任何持久性机制。他们使用我称之为“Managers”的服务层来执行我的POCO上的常见任务,并将它们读取/保存到底层数据存储区。

我正在编写一个工作流程,允许我网站的“员工”取消“LeaveRequest”。在控制器和动作方面,有一个HttpGet动作“CancelLeaveRequest”,它获取有问题的LeaveRequest的ID,通过服务层检索LeaveRequest,并显示一些细节,警告和确认按钮。在控制器返回相关视图之前,它将LeaveRequest实体提交到TempData中,准备好在下一步中获取...

确认按钮导致HttpPost为“LeaveRequest”,然后使用来自TempData的LeaveRequest和调用服务层来更改LeaveRequest并使用EF将它们保存回数据库。

我的代码中的每个经理类实例都有自己的EF DBContext。 MVC中的控制器实例化管理器并在页面生命周期内处理它。因此,使用DBContext的一个实例检索LeaveRequest,并通过另一个实例进行更改并提交。

我的理解是当第一个DBContext超出范围时,实体变为“分离”。所以,当我尝试对第二个DBContext提交更改时,我必须使用DBContext.LeaveRequests.Attach()将实体附加到上下文?还有一个复杂的问题,我需要使用“员工”实体来记录哪个员工取消了请假请求。

我在服务层中用于取消请假请求的代码如下所示。

public void CancelLeaveRequest(int employeeId, LeaveRequest request)
{
  _DBContext.LeaveRequests.Attach(request);
  request.State = LeaveRequestApprovalState.Cancelled;
  request.ResponseDate = DateTime.Now;

  using (var em = new EmployeesManager())
  {
    var employee = em.GetEmployeeById(employeeId);
    request.Responder = employee;
    _DBContext.Entry(request.Responder).State = System.Data.EntityState.Unchanged;
  }

  _CommitDatabaseChanges();
}

您可以看到我从EmployeesManager中检索了一个Employee实体,并将该员工作为响应者分配给请假。

在我的测试案例中,请假申请的“响应者”与“请求者”是同一员工,“请求者”是请假申请的另一个属性。请假与请求员工之间的关系是多对一的,请假与请求员工之间的关系是多对一的。

当我的代码以其当前状态运行时,我收到以下错误:

  

AcceptChanges无法继续,因为对象的键值与ObjectStateManager中的另一个对象冲突。在调用AcceptChanges之前,请确保键值是唯一的。

我怀疑这是因为EF认为它已经知道有问题的员工了。失败的一行是:

_DBContext.Entry(request.Responder).State = System.Data.EntityState.Unchanged;

但是,如果我删除这一行并且不要试图通过告诉EF不要更改我的员工对象而变得聪明,那么请假按照预期被取消,但是我的员工会发生一些非常奇怪的事情。

首先,制作/回复请求的员工是重复的。然后,任何导航属性(如“Manager”,Employee和其他Employees之间的多对一关系)似乎也会重复。我可以理解,Employee上Manager属性的重复是因为我在GetEmployeeById中加载了Manager对象图,而我我明白原来的Employee是重复的,因为就LeaveRequest DBContext而言,它刚出现(我通过不同的DBContext检索了Employee)。但是,假设这两点是正确的,我不知道如何a)防止Employee和它的相关对象图在数据库中重复,以及b)我如何确保修改后的LeaveRequest被正确保持(它似乎停止了各种组合的附加,改变状态到修改等...在员工和请假)。

有人可以突出我的方式错误吗?


我的LeaveRequest实体:

public class LeaveRequest
{
    public LeaveRequest()
    {
        HalfDays = new List<LeaveRequestHalfDay>();
    }

    public int CalculatedHalfDaysConsumed { get; set; }

    public Employee Employee { get; set; }

    public virtual ICollection<LeaveRequestHalfDay> HalfDays { get; set; }

    public int LeaveRequestId { get; set; }

    public DateTime RequestDate { get; set; }

    public int ResponderId { get; set; }
    public virtual Employee Responder { get; set; }

    public DateTime? ResponseDate { get; set; }

    public LeaveRequestApprovalState State { get; set; }

    public LeaveRequestType Type { get; set; }

    public ICollection<LeaveRequest> ChildRequests { get; set; }
    public LeaveRequest ParentRequest { get; set; }
}

“员工”字段(类型为Employee ...)是提交请求的人。 “响应者”可能有所不同,但员工可能是相同的。

1 个答案:

答案 0 :(得分:1)

您应该将导航属性更改为:

public int ResponderId {get;set;}
public virtual Employee Responder { get; set; }

此标量属性将由EF自动映射到导航属性。接下来,您只需执行以下操作(并且您不需要Unchanged州):

var employee = em.GetEmployeeById(employeeId);
request.ResponderId = employee.Id;

另见this article关于EF的关系。