模拟对同一实体的 2 个或更多 DBContext 调用失败

Mocking 2 or more DBContext calls to the same entity fails

我有一个 MVC ASP.Net 应用程序,使用 Entity Framework v6.0 和员工的 table。

我们将代码优先方法与标准创建 (CRUD) 方法结合使用,该方法具有针对现有员工的 EF 查找以及针对员工 CreatedBy/ModifiedBy 字段的 EF 查找。 尝试为两者(EF 对象存根)创建模拟失败。

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EmployeeID,TeamID,EmployeeADID,FirstName,MiddleName,LastName,EmailAddress,IsAdministrator,IsDeleted")] Employee employee)
{
    if (ModelState.IsValid)
    {
        //Only unique employee IDs
        var existingEmployee = db.Employees.FirstOrDefault(o => o.EmployeeADID == employee.EmployeeADID);
        if(existingEmployee != null)
        {
            ViewBag.Error = "Employee ID must be unique, this employee (" + existingEmployee.FullName + ") already exists in the system.";
            ViewBag.TeamID = new SelectList(db.Teams, "TeamID", "Name", employee.TeamID);
            return View(existingEmployee);
        }

        SetAuditFields(employee);
        db.Employees.Add(employee);
        db.SaveChanges();
        return RedirectToAction("Index");
    }    
    ViewBag.TeamID = new SelectList(db.Teams, "TeamID", "Name", employee.TeamID);
    return View(employee);
}

问题是 SetAuditFields 调用,我需要为编辑操作模拟 db.Employees.AsNoTracking AsNoTracking

private void SetAuditFields(Employee employee, bool onlyModified = false)
{
    char sep = '\';
    string pID = User.Identity.Name.Split(sep)[1].ToUpper();
    var users = db.Employees.AsNoTracking().Where(c => c.EmployeeADID == pID || c.EmployeeID == employee.EmployeeID).ToList();
    var currentUser = users.FirstOrDefault(u => u.EmployeeADID == pID);
    if (onlyModified)
    {
        //Notice the AsNoTracking, when you set the db.Entry(object).State = EntityState.Modified; this query wont return anything as its in Modified Mode.
        //var originalEmployee = db.Employees.AsNoTracking().FirstOrDefault(o => o.EmployeeID == employee.EmployeeID);
        var originalEmployee = users.FirstOrDefault(o => o.EmployeeID == employee.EmployeeID);
        employee.CreatedByID = originalEmployee.CreatedByID;
        employee.CreatedDate = originalEmployee.CreatedDate;
        employee.ModifiedByID = currentUser.EmployeeID;
        employee.ModifiedDate = DateTime.Now;
    }
    else
    {
        employee.CreatedByID = currentUser.EmployeeID;
        employee.CreatedDate = DateTime.Now;
        employee.ModifiedByID = currentUser.EmployeeID;
        employee.ModifiedDate = DateTime.Now;
    }
}

那么在最初模拟 db.Employees 之后如何模拟 db.Employees.AsNoTracking

下面代码中的这两行被注释掉了不工作并失败:

"The member 'IQueryable.Provider' has not been implemented on type 'DbSet1Proxy' which inherits from 'DbSet1'

我也尝试了 mockContext.SetupSequence,但我需要在 AsNoTracking 的开启和关闭之间切换。肯定有我遗漏的东西吗?

[TestMethod()]
public void Create_Submit_Test()
{
    // Arrange
    var employeeDoesntExist = new Employee { EmployeeID = 0, FirstName = "DoesntExist" };
    var employeeAdmin = new Employee { EmployeeID=140, FirstName = "Bern", MiddleName = "", LastName = "O", EmployeeADID = "123", EmailAddress = "Bernard.O@a.com", TeamID = 1, IsDeleted = false, IsAdministrator = true };
    var employeeNew = new Employee { FirstName = "Jez", MiddleName = "", LastName = "T", EmployeeADID = "321", EmailAddress = "Jeremy.Thompson@a.com", TeamID = 1, IsDeleted = false, IsAdministrator = true };

    var mockContext = new Mock<ICTEntities>();
    var employeeEmptyMock = base.GetQueryableMockDbSet(employeeDoesntExist);
    var employeeAdminMock = base.GetQueryableMockDbSet(employeeAdmin);

    //THESE TWO LINES COMMENTED OUT
    //mockContext.Setup(m => m.Employees).Returns(employeeEmptyMock);
    //mockContext.Setup(m => m.Employees.AsNoTracking()).Returns(employeeAdminMock);

    mockContext.SetupSequence(x => x.Employees.AsNoTracking())
    .Returns(employeeEmptyMock)
    .Returns(employeeAdminMock);

    //I dont want to save it to the Database, otherwise next time we run this the object will already exist, so I mock the call
    mockContext.Setup(d => d.SaveChanges()).Returns(1);

    var controller = new EmployeesController(mockContext.Object);
    controller.ControllerContext = base.MockAccess().Object;

    // Act
    RedirectToRouteResult result = controller.Create(employeeNew) as RedirectToRouteResult;

    // Assert
    Assert.IsNotNull(result);
    Assert.AreEqual("Index", result.RouteValues["Action"]); 
}

这是 GetQueryableMockDbSet 方法:

protected DbSet<T> GetQueryableMockDbSet<T>(params T[] sourceList) where T : class
{
    var queryable = sourceList.AsQueryable();

    var dbSet = new Mock<DbSet<T>>();
    dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
    dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
    dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
    dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());

    return dbSet.Object;
}

我最终根据

使用计数器创建了一系列模拟
int callCounter = 1;
mockContext.Setup(m => m.Employees)
    .Returns(() =>
    {
        if (callCounter == 1)
        {
            callCounter++;
            return employeeToEditMockCU;
        }
        else
        {
            return employeeMockCU;
        }
    });

第一次模拟后,使用 SetupSequence 模拟对我不起作用。 db.Employee 在第一次调用后变为 null。所以我不使用 SetupSequence:

mockContext.SetupSequence(x => x.Employees)
.Returns(employeeToEditMockCU)
.Returns(employeeMockCU);

另外为了解决 AsNoTracking() 我最终获取记录进行更新并保存它而不使用 EntityState.Modified:


How to update record using Entity Framework 6?