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

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

迁移后,某些单元测试不再起作用,抛出重复键db异常。

我模拟了这个问题,并意识到带有选项UseInmemoryDatabase的EF内核在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; }
    }
}

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;
        }
    }
}

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>
sandao9 回答:将Entity Framework Core 3.1与ServiceProvider中的UseInMemoryDatabase选项一起使用(范围有效期)

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

Install-Package Microsoft.EntityFrameworkCore.InMemory -Version 3.1.5

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

,

对我来说,解决方案是使用唯一名称更改数据库名称。

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

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

请参阅github问题:https://github.com/dotnet/efcore/issues/19541

,

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

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

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

    return services.BuildServiceProvider();
}

然后使用此功能在每次测试中构建提供程序

[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);
        }
    }
}

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

提示:

由于您正在netcoreapp3.1上运行,因此您现在也可以使用c#8语法using语句

[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);
}
,

如果您想做内存数据库以用于单元测试,则可以使用Microsoft提供的nuget程序包:

Install-Package Microsoft.EntityFrameworkCore.InMemory -Version 3.1.5

还要配置您可以伪造DbContext和存储库,例如:

namespace YourProject.Tests.UnitTests
{
    public class FakeDbContext : DbContext
    {
        public DbSet<Entity> Entities { get; set; }    

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseInMemoryDatabase(databaseName: "FakePersonalSiteDbContext");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            AddMappingOverrides(modelBuilder);
        }

        private void AddMappingOverrides(ModelBuilder modelBuilder)
        {
            modelBuilder.ApplyConfiguration(new SomeEntityMappingOverride());
        }
    }
}

然后您可以使用此FakeDbContext注入单元测试项目中的FakeRepository:

namespace YourProject.Tests.UnitTests
{
    public class FakePersonalSiteRepository : IYourRepository
    {
        private FakeDbContext dbContext;

        public FakeRepository(FakeDbContext dbContext)
        {
            this.dbContext = dbContext;
        }

        // Your repository methods (Add,Delete,Get,...)
    }
}

这样,您现在就可以使用内存数据库进行单元测试了。例如:

namespace YourProject.Tests.UnitTests
{
    public class UnitTestBase
    {
        protected IYourRepositoryRepository Repository { get; set; }
        protected FakeDbContext FakeDbContext { get; set; }

        [SetUp]
        public void SetUp()
        {
            FakeDbContext = new FakeDbContext();
            FakeDbContext.Database.EnsureDeleted();
            Repository = new FakeRepository(FakeDbContext);
        }
    }
}
,

我为Extension做了以下UnitTest方法:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddInMemoryDbContext<TDbContext>(this IServiceCollection services) where TDbContext: DbContext
        => services.AddDbContext<TDbContext>(builder
            => builder.UseInMemoryDatabase(Guid.NewGuid().ToString())
                .ConfigureWarnings(w =>
                    {
                        w.Ignore(InMemoryEventId.TransactionIgnoredWarning);
                        w.Throw(RelationalEventId.QueryClientEvaluationWarning);
                    }
                ),ServiceLifetime.Transient);
}

用法非常简单。只需在DbContext设置中将您所有的IServiceCollection添加到您的UnitTest

...

services
    .AddInMemoryDbContext<AppDbContext>()
    .AddInMemoryDbContext<FooDbContext>()
    .AddInMemoryDbContext<AnotherDbContext>();

...

您还需要安装 Microsoft.EntityFrameworkCore.InMemory https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.InMemory

本文链接:https://www.f2er.com/2802565.html

大家都在问