在单元测试中使用EF Core SqlLite时防止跟踪问题

Ste*_*son 10 entity-framework-core asp.net-core

我正在编写单元测试来测试更新EF核心实体的控制器操作.

我使用的是SQLLite,而不是嘲笑.

我像这样设置我的数据库:

        internal static ApplicationDbContext GetInMemoryApplicationIdentityContext()
    {
        var connection = new SqliteConnection("DataSource=:memory:");
        connection.Open();

        var options = new DbContextOptionsBuilder<ApplicationDbContext>()
                .UseSqlite(connection)
                .Options;

        var context = new ApplicationDbContext(options);
        context.Database.EnsureCreated();

        return context;
Run Code Online (Sandbox Code Playgroud)

然后将实体添加到数据库,如下所示:

        private DiaryEntriesController _controller;
    private ApplicationDbContext _context;

    [SetUp]
    public void SetUp()
    {
        _context = TestHelperMethods.GetInMemoryApplicationIdentityContext();
        _controller = new DiaryEntriesController(_context);
    }

    [Test]
    [Ignore("http://stackoverflow.com/questions/42138960/preventing-tracking-issues-when-using-ef-core-sqllite-in-unit-tests")]
    public async Task EditPost_WhenValid_EditsDiaryEntry()
    {
        // Arrange
        var diaryEntry = new DiaryEntry
        {
            ID = 1,
            Project = new Project { ID = 1, Name = "Name", Description = "Description", Customer = "Customer", Slug = "slug" },
            Category = new Category { ID = 1, Name = "Category" },
            StartDateTime = DateTime.Now,
            EndDateTime = DateTime.Now,
            SessionObjective = "objective",
            Title = "Title"
        };

        _context.DiaryEntries.Add(diaryEntry);
        await _context.SaveChangesAsync();

        var model = AddEditDiaryEntryViewModel.FromDiaryEntryDataEntity(diaryEntry);
        model.Actions = "actions";

        // Act
        var result = await _controller.Edit(diaryEntry.Project.Slug, diaryEntry.ID, AddEditDiaryEntryViewModel.FromDiaryEntryDataEntity(diaryEntry)) as RedirectToActionResult;

        // Assert
        var retreivedDiaryEntry = _context.DiaryEntries.First();

        Assert.AreEqual(model.Actions, retreivedDiaryEntry.Actions);
    }
Run Code Online (Sandbox Code Playgroud)

我的控制器方法如下所示:

        [HttpPost]
    [ValidateAntiForgeryToken]
    [Route("/projects/{slug}/DiaryEntries/{id}/edit", Name = "EditDiaryEntry")]
    public async Task<IActionResult> Edit(string slug, int id, [Bind("ID,CategoryID,EndDate,EndTime,SessionObjective,StartDate,StartTime,Title,ProjectID,Actions,WhatWeDid")] AddEditDiaryEntryViewModel model)
    {
        if (id != model.ID)
        {
            return NotFound();
        }

        if (ModelState.IsValid)
        {
            var diaryEntryDb = model.ToDiaryEntryDataEntity();
            _context.Update(diaryEntryDb);
            await _context.SaveChangesAsync();

            return RedirectToAction("Details", new { slug = slug, id = id });
        }
        ViewData["CategoryID"] = new SelectList(_context.Categories, "ID", "Name", model.CategoryID);
        ViewData["ProjectID"] = new SelectList(_context.Projects, "ID", "Customer", model.ProjectID);
        return View(model);
    }
Run Code Online (Sandbox Code Playgroud)

我的问题是,当测试运行时,当我尝试更新实体时它会出错.我收到消息:

无法跟踪实体类型"DiaryEntry"的实例,因为已经跟踪了具有相同密钥的此类型的另一个实例.

代码在现实生活中很好用.我在插入测试后如何停止跟踪,以便生产代码中的db上下文仍然不跟踪插入的实体.

我理解模拟一个repo模式的接口的好处,但我真的想让这种测试方法工作 - 我们将数据插入一个内存数据库,然后测试它已在数据库中更新.

任何帮助将非常感激.

谢谢

编辑: 我添加了我的测试的完整代码,以表明我使用相同的上下文来创建数据库并插入我实例化控制器的日记条目.

Smi*_*mit 13

问题出在设置中.您到处使用相同的dbcontext.因此,在调用update时,EF抛出异常,即已经跟踪具有相同键的实体.代码在生产中工作,因为传递给控制器​​DI的每个请求都会生成一个新的控制器实例.由于控制器在构造函数中也有DbContext,在相同的服务范围内,DI也会生成新的dbcontext实例.因此,您的Edit操作始终具有新的dbcontext.如果你真的测试了你的控制器,那么你应该确保你的控制器获得一个新的dbcontext而不是已经使用过的上下文.

您应该在设置阶段更改GetInMemoryApplicationIdentityContext方法以返回DbContextOptions,将选项存储在字段中.每当您需要dbcontext(在保存实体或创建控制器期间)时,使用存储在该字段中的选项新建DbContext.这将为您提供所需的分离,并允许您测试控制器,因为它将在生产中进行配置.