Конфликтующие значения ключей при запуске всех модульных тестов с базой данных SQLite InMemory

#c# #unit-testing #entity-framework-core #xunit #asp.net-core-5.0

#c# #модульное тестирование #сущность-структура-ядро #xunit #asp.net-core-5.0

Вопрос:

Я работаю над ASP.NET Проект Core 5, в котором я провожу модульное тестирование базы данных SQLite. Когда я запускаю тесты по отдельности, все тесты проходят, но когда я запускаю все тесты одновременно, я получаю эту ошибку:

Система.Исключение InvalidOperationException : экземпляр объекта типа ‘Contact’ не может быть отслежен, поскольку другой экземпляр с тем же значением ключа для {‘Id’} уже отслеживается. При подключении существующих объектов убедитесь, что подключен только один экземпляр объекта с заданным значением ключа.

Трассировка стека: [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Отслеживание изменений.Внутренний.Карта 1.ThrowIdentityConflict(InternalEntityEntry entry) [xUnit.net 00:00:03.06] at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap идентификаторов 1.Добавить (ключ TKey, запись InternalEntityEntry, логическое значение updateDuplicate) [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Отслеживание изменений.Внутренний.IdentityMap 1.Add(TKey key, InternalEntityEntry entry) [xUnit.net 00:00:03.06] at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap 1.Добавить (запись InternalEntityEntry) [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Отслеживание изменений.Internal.StateManager.Отслеживание запуска (запись InternalEntityEntry) [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Отслеживание изменений.Внутренний.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, логические AcceptChanges, логические modifyProperties) [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Отслеживание изменений.Внутренний.InternalEntityEntry.SetEntityState(EntityState EntityState, логические AcceptChanges, логические modifyProperties, обнуляемый 1 forceStateWhenUnknownKey) [xUnit.net 00:00:03.06] at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode 1 узел) [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Отслеживание изменений.Внутренний.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode 1 node, Func 2 handleNode) [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Отслеживание изменений.Внутренний.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode 1 node, Func 2 handleNode) [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Отслеживание изменений.Внутренний.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode 1 node, Func 2 handleNode) [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Отслеживание изменений.Внутренний.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, логическое значение forceStateWhenUnknownKey) [xUnit.net 00:00:03.06] в Microsoft.EntityFrameworkCore.Внутренний.InternalDbSet 1.SetEntityState(InternalEntityEntry entry, EntityState entityState) [xUnit.net 00:00:03.06] at Microsoft.EntityFrameworkCore.Internal.InternalDbSet 1.Добавить (объект TEntity)

В каждом модульном тестировании я использую оператор ‘using’, поэтому контекст базы данных должен быть удален, как я понимаю.

У меня есть класс TestBase:

 using System;
using Project.Data;
using Microsoft.EntityFrameworkCore;

namespace Project.Connections.Test
{
    public class TestBase
    {
        private bool useSqlite;

        public DataContext GetDbContext()
        {
            var builder = new DbContextOptionsBuilder<DataContext>();
            if (useSqlite)
            {
                // Use Sqlite DB.
                builder.UseSqlite("DataSource=:memory:", x => { });
            }
            else
            {
                // Use In-Memory DB.
                builder.UseInMemoryDatabase(Guid.NewGuid().ToString());
            }

            var dbContext = new DataContext(builder.Options);
            if (useSqlite)
            {
                // SQLite needs to open connection to the DB.
                // Not required for in-memory-database and MS SQL.
                dbContext.Database.OpenConnection();
            }

            dbContext.Database.EnsureCreated();

            TestData.Seed(dbContext);
            dbContext.SaveChanges();

            return dbContext;
        }

        public void UseSqlite()
        {
            useSqlite = true;
        }
    }
}
 

One of the objects that I’m seeding is causing the problem. Note that the customer of the project contains a contacts and addresses list, and the project object itself has one of the customer’s contact and address:

 using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Project.Data.Customers;
using Project.Data.Projects;
using Microsoft.EntityFrameworkCore;

namespace Project.Data
{
    public class TestData
    {
        public static void Seed(DataContext context)
        {
            // Other data...
            CreateProjects(context);
        }

        private static void CreateProjects(DataContext context)
        {
            var project1 = new Project()
            {
                Name = "Lorem",
                ProjectNumber = "2020/10/0001",
                DeadLine = System.DateTime.Now.AddDays(10),
                Customer = microsoft,
                State = ProjectState.Accepted,
                Contact = microsoft.Contacts.First(),
                DeliveryAddress = microsoft.DeliveryAddresses.First(),
                EndSize = new Size()
                {
                    Height = 300,
                    Width = 2000,
                },
            };

            context.Projects.Add(project1);
            context.SaveChanges();

            // Other project objects...
        }

        private static Customer microsoft = new Customer
        {
            IsCompany = true,
            CompanyName = "Microsoft",
            Contacts = new List<Contact>()
            {
                new Contact
                {
                    FirstName = "Bill",
                    LastName = "Gates",
                    PhoneNumber = " 16506815000",
                    Email = "bill.gates@microsoft.com",
                    Other = "CEO, in case of emergency :)",
                }
            },
            DeliveryAddresses = new List<Address>()
            {
                new Address
                {
                     Country = "USA",
                     ZipCode = "WS 98052-6399",
                     City = "Redmond",
                     Street = "One Microsoft Way",
                     Other = "Microsoft HQ"
                },
                new Address
                {
                     Country = "Norway",
                     ZipCode = "0194",
                     City = "Oslo",
                     Street = "Dronning Eufemias gate 71",
                     Other = "Microsoft Norway"
                }
            }
        
        };
    }
}

 

И вот как выглядит мой модульный тест:

 using System.Linq;
using System.Threading.Tasks;
using Project.Connections.Services.Projects;
using Project.Data.Customers;
using Project.Data.Projects;
using Project.Data.Projects.Dtos;
using Xunit;

namespace Project.Connections.Test
{
    public class ProjectServiceTests : TestBase
    {
        [Fact]
        public async Task GetAll_ShouldReturn()
        {
            UseSqlite();

            using (var context = GetDbContext())
            {
                var ProjectService = new ProjectService(context);

                var result = await ProjectService.GetAll();

                Assert.Equal(3, result.Count()); // I made 3 dummy projects originally
            }
        }

    // Other tests...
 

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

Комментарии:

1. Вы нашли решение? У меня такая же проблема

2. @CedricArnould На самом деле нет, я просто создавал новые объекты вместо ссылок, поскольку это были фиктивные данные. Однако я узнал, как EF может настроить, чтобы не отслеживать объект. Например.: вместо microsoft.Contacts.First(); попытки microsoft.Contacts.AsNoTacking().First(); , возможно, это может помочь 🙂

3. Дело в том, что я хочу удалить элемент из базы данных, поэтому мне нужно сохранить отслеживание (насколько я понимаю). Я решаю, выполнив новое действие в API, чтобы удалить его, не совсем то, что я хотел, но это работает.