Использование Entity Framework Core 3.1 с параметром UseInMemoryDatabase в ServiceProvider (время действия Scoped) - PullRequest
3 голосов
/ 08 января 2020

Я перенес проект веб-приложения с. 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; }
    }
}

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>

Ответы [ 3 ]

9 голосов
/ 11 февраля 2020

@ james, я сталкивался с той же проблемой, решение простое, вы можете установить пакет через консоль пакета

Install-Package Microsoft.EntityFrameworkCore.InMemory -Version 3.1.1

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

Вы можете пометить это как ответ, если это решит проблему для вас так, чтобы другие могли легко видеть это, ссылка была добавлена, если в будущем это изменится

3 голосов
/ 13 января 2020

Для меня решение было изменить имя базы данных с уникальным именем.

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

Таким образом, есть новая база данных для каждого метода тестирования.

См. Выпуск Github: https://github.com/dotnet/efcore/issues/19541

0 голосов
/ 08 января 2020

Я бы лично построил сервис-провайдера для каждого теста, чтобы вы убедились, что между тестами, которые выполняются одновременно, нет общего состояния. Примерно так:

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

Это может привести к тому, что время выполнения будет немного выше, чем раньше, но оно должно определенно не допустить повторения вашей текущей проблемы.

Совет:

Вы также можете использовать синтаксис c# 8, используя операторы, так как вы работаете на 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);
}
...