Search code examples
c#.net-coreentity-framework-coreautomapper

Exception when I try to update graph using Entity Framework Core with AutoMapper


I get an exception when I try to update graph. I use Automapper to map DTOs to my entities, then I check if my object already exists in the database before I do the update operation.

When I call dbContext.Update, I get this exception

The instance of entity type 'Order' cannot be tracked because another instance with the same key value for {'OrderId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'

My code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using EFCoreIssue;
using EFCoreIssue.Dtos;
using EFCoreIssue.Models;
using Microsoft.EntityFrameworkCore;
using Xunit;

namespace EFCoreIssue_Tests
{
  public class OrdersTests
  {
    private readonly OrderRepository _orderRepository;

    public OrdersTests()
    {
        _orderRepository = new OrderRepository(new OrdersDbContext());
        Mapper.Initialize(cfg => cfg.CreateMap<OrderDto, Order>());
    }

    [Fact]
    public async void Should_Update_Customer_Successfully()
    {
        //Arrange
        var order = new Order
        {
            Description = "Test Order",
            OrderLines = new List<OrderLine>
            {
                new OrderLine {ProductId = 1, Quantity = 1}
            }
        };
        using (var dbContext = new OrdersDbContext())
        {
            dbContext.Orders.Add(order);
            await dbContext.SaveChangesAsync();
        }

        //Act
        var orderDto = new OrderDto
        {
            Description = "Test Order 2",
            OrderLines = new List<OrderLineDto>
            {
                new OrderLineDto {OrderLineId = order.OrderLines[0].OrderLineId, ProductId = 2, Quantity = 2}
            },
            OrderId = order.OrderId
        };

        await _orderRepository.UpdateAsync(Mapper.Map<Order>(orderDto));

        //Assert
        using (var dbContext = new OrdersDbContext())
        {
            var updatedOrder = await dbContext.Orders.Include(c => c.OrderLines)
                .Where(c => c.OrderId == order.OrderId).SingleAsync();
            //updatedOrder.ShouldNotBeNull();
            //customerFetch.Id.ShouldNotBeNull();
            //customerFetch.Id.ShouldBe(customerDto.Id);
        }
    }
  }
}

and my order repository update method

    public async Task<Order> UpdateAsync(Order order)
    {
        var dbOrder = await GetOrderAsync(order.OrderId);
        if (dbOrder == null)
            throw new Exception($"Invalid Or Missing Order with Id {order.OrderId}");

        _dbContext.Update(order);
        await _dbContext.SaveChangesAsync();
        return order;
    }

Note: you can get the code on this github repository code repository


Solution

  • When you query the database for some record without using AsNoTracking EF Core will start tracking it in current context. So when you update Entity in your code, EF Core finds multiple entities with same id hence the error. AsNoTracking is one solution as you don't want EF Core to track any modifications to that. In many cases not having AsNoTracking is fine as long as you don't add/attach/update entity with same id in the context. But it is good to have it explicitly when tracking is not required.