Search code examples
c#visual-studiounit-testingentity-framework-corein-memory-database

The instance of entity type 'Game' cannot be tracked because another instance with the key value is already being tracked


I am unit testing an application I made with an in-memory database using Entity Framework Core but I am running into a problem with one of my functions. I have so far tested getting a game by Id (it works), adding a game to the collection (it works too) and updating a game in the collection (it doesn't work).

The function code:

 [TestMethod()]
        public async Task UpdateGameAsyncTest()
        {
            //Arrange
            await CreateDb();

            GameDto gameDto = new GameDto()
            {
                Id = 2,
                Team1 = "PSG",
                Team2 = "AZ",
                Date = DateTime.UtcNow
            };

            //Act
            var id = await _gameService2.UpdateGameAsync(gameDto);
            var game = await _gameService2.GetGameAsync(id);

            //Assert
            Assert.AreEqual("AZ", game.Team1);
        }

UpdateGameAsync() in my service layer:

public async Task<int> UpdateGameAsync(GameDto game)
        {
            var gameEntity = game.ToEntity();

            return await _gameRepository.UpdateGameAsync(gameEntity);
        }

UpdateGameAsync() in my repository layer:

public async Task<int> UpdateGameAsync(Game game)
        {
            if (game.Result != null)
            {
                if (game.Result.Length == 3 && game.Result.Substring(1, 1) == "-")
                {
                    game.typeResult = await CheckTypeResult(game.Result);
                }
            }
            _dbContext.Games.Update(game);

            return await AutoSaveChangesAsync();
        }

CreateDb():

public async Task CreateDb()
        {
            var options = new DbContextOptionsBuilder<StrikeNetDbContext>()
                .UseInMemoryDatabase(databaseName: Guid.NewGuid().ToString())
                .EnableSensitiveDataLogging()
                .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking)
                .Options;

            var dbContext = new StrikeNetDbContext(options);

            if (await dbContext.Games.CountAsync() <= 0)
            {
                Game g1 = new Game()
                {
                    Id = 1,
                    Team1 = "FCB",
                    Team2 = "BVB",
                    Date = DateTime.UtcNow
                };
                Game g2 = new Game()
                {
                    Id = 2,
                    Team1 = "PSG",
                    Team2 = "PSV",
                    Date = DateTime.UtcNow
                };
                Game g3 = new Game()
                {
                    Id = 3,
                    Team1 = "Ajax",
                    Team2 = "Feyenoord",
                    Date = DateTime.UtcNow
                };
                dbContext.Games.Add(g1);
                dbContext.Games.Add(g2);
                dbContext.Games.Add(g3);
                await dbContext.SaveChangesAsync();
            }

            var gameRepository = new GameRepository(dbContext);
            _gameService2 = new GameService(gameRepository);
        }

When running the test I am running into the error: "The instance of entity type 'Game' cannot be tracked because another instance with the key value '{Id: 2}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached."

For comparing purposes, here is the adding function that DOES work:

[TestMethod()]
        public async Task AddGameAsyncTest()
        {
            //Arrange
            await CreateDb();

            GameDto game = new GameDto()
            {
                Team1 = "Juve",
                Team2 = "Real",
                Date = DateTime.UtcNow
            };

            //Act
            var id = await _gameService2.AddGameAsync(game);
            var expected = await _gameService2.GetGameAsync(id);

            //Assert
            Assert.AreEqual("Juve", expected.Team1);
        }

I have tried several things that answers to similar questions suggested but none of them work for me. I do have my repository en service scoped in Startup.cs, and detaching the entities in the createdb() function als doesn't seem to work. Does anyone have any other idea?


Solution

  • I think it's come from your UpdateGameAsync method. If you call EF Update inside, as the documentation says, you try to track the entity

    Begins tracking the given entity in the Microsoft.EntityFrameworkCore.EntityState.Modified

    But, i think your entity is already tracked, because i feel like you use the same dbContext in CreateDb, and this line begin the tracking of your entity:

    dbContext.Games.Add(g2);

    You can check if your entity is tracked in your repository:

    _context.Games.Local.Any(g => g.Id == entity.Id)

    If it's already tracked, you don't need to call EF Update().

    By the way, if your real case do not use the same DbContext for data initialization and data read, i recommand to use another context to be sure that's work as you want.