无法使用相同的 InMemory Db 跨多个 DbContext 获取更新数据

Can't get updated data across multiple DbContext with the same InMemory Db

我想使用 InMemory 数据库来测试我的 Web api 使用 WebApplicationFactory。

在我的测试中,我想在我的 dbcontext 中设置数据,调用我的 api,测试数据。 这适用于获取数据或创建新条目,但在更新时,上下文不会更新,它仍然包含数据的旧副本。 我的 DbContext 按照建议 here.

使用 InMemoryDatabaseRoot 初始化

这是我的 WebApplicationFactory

public class InMemDbWebApplicationFactory : WebApplicationFactory<Startup>
{
    public static readonly InMemoryDatabaseRoot InMemoryDatabaseRoot = new InMemoryDatabaseRoot();

    public ServiceProvider ServiceProvider { get; private set; }

    protected override void ConfigureWebHost(IWebHostBuilder builder)
    {
        builder.ConfigureServices(services =>
        {
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();

            services.AddDbContext<TestDbContext>(options =>
            {
                options.UseInMemoryDatabase("InMemoryDb", InMemoryDatabaseRoot);
                options.UseInternalServiceProvider(serviceProvider);
            });

            ServiceProvider = services.BuildServiceProvider();
        });
    }
}

我的测试 class 将它用作 IClassFixture,因此它在我的测试中共享。

这是我的失败测试

[Fact]
public async void TestPutBook()
{
    using (var scope = _serviceProvider.CreateScope())
    using (var context = scope.ServiceProvider.GetRequiredService<TestDbContext>())
    {
        var book1 = new Book { Name = "Book1" };
        var book2 = new Book { Name = "Book2" };
        context.Books.AddRange(book1, book2);
        context.SaveChanges();

        var updateResp = await _httpClient.PutAsJsonAsync($"/api/books/{book1.Id}", "Book1Update");
        updateResp.EnsureSuccessStatusCode();

        var getResp = await _httpClient.GetAsync($"/api/books/{book1.Id}");
        getResp.EnsureSuccessStatusCode();
        var stringResponse = await getResp.Content.ReadAsStringAsync();
        book1 = JsonConvert.DeserializeObject<Book>(stringResponse);
        Assert.NotNull(book1);
        //this works
        Assert.Equal("Book1Update", book1.Name);

        book1 = context.Books.Find(book1.Id);
        Assert.NotNull(book1);
        //this fails book1.Name is still equal to "Book1"
        Assert.Equal("Book1Update", book1.Name);
    }
}

我也尝试在同一范围内获取一个新的 DbContext,但是它失败了,说 DbContext 已经被释放。

您可以找到完整的测试解决方案here

  1. 避免手动处理 DbContext,因为它由依赖容器 (DI) 管理。换句话说,不要使用using :

    using (var context = scope.ServiceProvider.GetRequiredService())
    {
         ...
    }  // the context will be DISPOSED now!
       // now if you try to resolve the TestDbContext service in future, it throws
       //     var context = scope.ServiceProvider.GetRequiredService(); // Wrong
    
  2. 测试失败也是因为控制器使用的DbContext和单元测试中使用的DbContext在不同的范围内 .这里有两种解决这个问题的方法:

    方法 1 :Reload() 手动:

    using (var scope = _serviceProvider.CreateScope())
    {
        var context = scope.ServiceProvider.GetRequiredService<TestDbContext>();
        var book1 = new Book { Name = "Book1" };
        var book2 = new Book { Name = "Book2" };
        context.Books.AddRange(book1, book2);
        context.SaveChanges();
        ...
        ...
        book1 = context.Books.Find(book1.Id);
        context.Entry(book1).Reload(); // refresh to make sure we can get the newest book1 
        Assert.NotNull(book1);
        Assert.Equal("Book1Update", book1.Name);
    }
    

    方法 2:创建另一个更小的范围 以获取最新记录:

    using (var scope = _serviceProvider.CreateScope())
    {
        Book book1;
        var context = scope.ServiceProvider.GetRequiredService<TestDbContext>();
        book1 = new Book { Name = "Book1" };
        context.Books.AddRange(book1);
        context.SaveChanges();
        var updateResp = await _httpClient.PutAsJsonAsync($"/api/books/{book1.Id}", "Book1Update");
        updateResp.EnsureSuccessStatusCode();
        // create a new smaller scope 
        using(var scope2= scope.ServiceProvider.CreateScope()){
            var context2 = scope2.ServiceProvider.GetRequiredService<TestDbContext>();
            book1 = context2.Books.Find(book1.Id);
            Assert.NotNull(book1);
            Assert.Equal("Book1Update", book1.Name);
        }
    }