[英]Entity Framework Core migrations error using UseInMemoryDatabase
[英]Using Entity Framework Core 3.1 with UseInMemoryDatabase option in ServiceProvider ( Scoped lifetime )
我已將 Web 應用程序項目從 .NET Core 2.1 遷移到 3.1(也將 EF Core 從 2.1.1 遷移到 3.1.0)。
遷移后,一些單元測試不再工作,拋出重復鍵數據庫異常。
我模擬了這個問題,並意識到帶有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>
您可以通過軟件包控制台安裝軟件包
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
我為我的UnitTest
制作了以下Extension
方法:
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);
}
用法非常簡單。 只需在UnitTest
設置中將所有DbContext
添加到IServiceCollection
。
...
services
.AddInMemoryDbContext<AppDbContext>()
.AddInMemoryDbContext<FooDbContext>()
.AddInMemoryDbContext<AnotherDbContext>();
...
您還需要安裝Microsoft.EntityFrameworkCore.InMemory ( https://www.nuget.org/packages/Microsoft.EntityFrameworkCore.InMemory )
我會親自為每個測試構建一個服務提供程序,以便您確保同時執行的測試之間沒有共享狀態。 像這樣的東西:
private IServiceProvider BuildServiceProvider()
{
var services = new ServiceCollection();
services.AddDbContext<AppDbContext>(opt => opt.UseInMemoryDatabase(databaseName: "InMemoryDb"),
ServiceLifetime.Scoped,
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);
}
如果你想做內存數據庫用於單元測試,你可以使用微軟為它提供的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);
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.