将 Entity Framework Core 3.1 与 ServiceProvider 中的 UseInMemoryDatabase 选项一起使用(范围生命周期)

Jam*_*mes 14 c# unit-testing dbcontext entity-framework-core .net-core-3.1

我已将 Web 应用程序项目从 .NET Core 2.1 迁移到 3.1(也将 EF Core 从 2.1.1 迁移到 3.1.0)。

迁移后,一些单元测试不再工作,抛出重复键数据库异常。

我模拟了这个问题,并意识到带选项的 EF 核心UseInMemoryDatabase在 3.1 中的行为有所不同,它不会清理旧数据。

在第二种测试方法中,People表已经包含从第一个测试添加的数据,这在 2.1 中没有发生

有谁知道如何使内存数据库适用于每个单元测试?

这是我的测试代码:

AppDbContext.cs

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace MyConsoleApp.Database
{
    public class AppDbContext: DbContext
    {
        protected AppDbContext(DbContextOptions options) : base(options) { }

        public AppDbContext(DbContextOptions<AppDbContext> options) : this((DbContextOptions)options)
        {
        }

        public virtual DbSet<Person> Person { get; set; }
    }

    public class Person
    {
        [Key]
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
Run Code Online (Sandbox Code Playgroud)

AppUnitTest.cs

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MyConsoleApp.Database;
using System.Linq;

namespace MyConsoleAppTest
{
    [TestClass]
    public class AppUnitTest
    {
        public ServiceCollection Services { get; private set; }
        public ServiceProvider ServiceProvider { get; protected set; }

        [TestInitialize]
        public void Initialize()
        {
           Services = new ServiceCollection();

           Services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase(databaseName: "InMemoryDb"), 
               ServiceLifetime.Scoped, 
               ServiceLifetime.Scoped);

            ServiceProvider = Services.BuildServiceProvider();
        }

        [TestMethod]
        public void TestMethod1()
        {
            using (var dbContext = ServiceProvider.GetService<AppDbContext>())
            {
                dbContext.Person.Add(new Person { Id = 0, Name = "test1" });
                dbContext.SaveChanges();
                Assert.IsTrue(dbContext.Person.Count() == 1);
            }
        }

        [TestMethod]
        public void TestMethod2()
        {
            using (var dbContext = ServiceProvider.GetService<AppDbContext>())
            {
                dbContext.Person.Add(new Person { Id = 0, Name = "test2" });
                dbContext.SaveChanges();
                Assert.IsTrue(dbContext.Person.Count() == 1);
            }
        }

        [TestCleanup]
        public virtual void Cleanup()
        {
            ServiceProvider.Dispose();
            ServiceProvider = null;
        }
    }
}

Run Code Online (Sandbox Code Playgroud)

MyConsoleAppTest.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
    <IsPackable>false</IsPackable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.1.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.2.0" />
    <PackageReference Include="MSTest.TestAdapter" Version="2.0.0" />
    <PackageReference Include="MSTest.TestFramework" Version="2.0.0" />
    <PackageReference Include="coverlet.collector" Version="1.0.1" />
  </ItemGroup>

  <ItemGroup>
    <ProjectReference Include="..\MyConsoleApp\MyConsoleApp.csproj" />
  </ItemGroup>

</Project>
Run Code Online (Sandbox Code Playgroud)

riz*_*izu 30

您可以通过软件包控制台安装软件包

Install-Package Microsoft.EntityFrameworkCore.InMemory -Version 3.1.5
Run Code Online (Sandbox Code Playgroud)

https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.InMemory

  • 这与如何将内存数据库范围限定到一个测试的问题有何关系? (3认同)
  • 我只是在找这个。谢谢先生。 (3认同)
  • @GertArnold,它不相关,我多次看到这种情况,有些人只是抛出一个答案,然后要求您将其标记为正确答案,而不向解决方案添加任何其他解释。我认为他们只是在寻找声誉得分点,也许有些公司在招聘时会使用此信息。 (2认同)

Jam*_*mes 10

我的解决方案是使用唯一名称更改数据库名称。

Services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString()), ServiceLifetime.Scoped, ServiceLifetime.Scoped);
Run Code Online (Sandbox Code Playgroud)

通过这种方式,每个测试方法都有一个新的数据库。

见github问题:https : //github.com/dotnet/efcore/issues/19541


als*_*ami 0

我会亲自为每个测试构建一个服务提供者,这样您就可以确保同时执行的测试之间没有共享状态。像这样的东西:

private IServiceProvider BuildServiceProvider()
{
    var services = new ServiceCollection();

    services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase(databaseName: "InMemoryDb"), 
        ServiceLifetime.Scoped, 
        ServiceLifetime.Scoped);

    return services.BuildServiceProvider();
}
Run Code Online (Sandbox Code Playgroud)

然后使用这个函数在每个测试中构建提供者

[TestMethod]
public void TestMethod1()
{
    using (var serviceProvider = BuildServiceProvider()) 
    {
        using (var dbContext = serviceProvider.GetService<AppDbContext>())
        {
            dbContext.Person.Add(new Person { Id = 0, Name = "test1" });
            dbContext.SaveChanges();
            Assert.IsTrue(dbContext.Person.Count() == 1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

这可能会导致执行时间比以前长一点,但绝对可以防止当前问题再次发生。

提示:

您现在还可以使用 c# 8 语法 using 语句,因为您正在运行netcoreapp3.1

[TestMethod]
public void TestMethod1()
{
    using (var serviceProvider = BuildServiceProvider()) 
    {
        using (var dbContext = serviceProvider.GetService<AppDbContext>())
        {
            dbContext.Person.Add(new Person { Id = 0, Name = "test1" });
            dbContext.SaveChanges();
            Assert.IsTrue(dbContext.Person.Count() == 1);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)