'"The instance of entity type cannot be tracked" error when adding detached entity tree

I am unable to add the Deal entity because Buyer and Seller are referencing the same city "City1". I work with detached entities here because I receive the whole model through API.

// This is the result of Deal entity deserialization
var deal = new Deal
{
    Id = "D1",
    Buyer = new Person
    {
        Id = "P1",
        Name = "Person1",
        City = new City { Name = "City1" }
    },
    Seller = new Person
    {
        Id = "P2",
        Name = "Person2",
        City = new City { Name = "City1" }
    }
};

_dbContext.Deals.Add(deal);
_dbContext.SaveChanges();

The error is

System.InvalidOperationException: 'The instance of entity type 'City' cannot be tracked because another instance with the same key value for {'Name'} 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.'

Here is how entities are set up:

modelBuilder.Entity<City>(x =>
{
    x.HasKey(e => e.Name);
});

modelBuilder.Entity<Person>(x =>
{
    x.HasKey(e => e.Id);
    x.Property(e => e.Name);
    x.HasOne(e => e.City).WithMany();
});

modelBuilder.Entity<Deal>(x =>
{
    x.HasKey(e => e.Id);
    x.HasOne(e => e.Buyer).WithMany();
    x.HasOne(e => e.Seller).WithMany();
});

What is the proper way of working with such kind of entities? I use EF Core 6



Solution 1:[1]

Your two Person objects would need to reference the same object for City. Alternatively, expose and use the FK property (which appears to be the city's Name property) instead of the navigation property for this operation.

If you want to keep using the City objects in JSON, then you'll need to preserve references. For example, here's the documentation for System.Text.Json

using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace PreserveReferences
{
    public class Employee
    {
        public string Name { get; set; }
        public Employee Manager { get; set; }
        public List<Employee> DirectReports { get; set; }
    }

    public class Program
    {
        public static void Main()
        {
            Employee tyler = new()
            {
                Name = "Tyler Stein"
            };

            Employee adrian = new()
            {
                Name = "Adrian King"
            };

            tyler.DirectReports = new List<Employee> { adrian };
            adrian.Manager = tyler;

            JsonSerializerOptions options = new()
            {
                ReferenceHandler = ReferenceHandler.Preserve,
                WriteIndented = true
            };

            string tylerJson = JsonSerializer.Serialize(tyler, options);
            Console.WriteLine($"Tyler serialized:\n{tylerJson}");

            Employee tylerDeserialized =
                JsonSerializer.Deserialize<Employee>(tylerJson, options);

            Console.WriteLine(
                "Tyler is manager of Tyler's first direct report: ");
            Console.WriteLine(
                tylerDeserialized.DirectReports[0].Manager == tylerDeserialized);
        }
    }
}

// Produces output like the following example:
//
//Tyler serialized:
//{
//  "$id": "1",
//  "Name": "Tyler Stein",
//  "Manager": null,
//  "DirectReports": {
//    "$id": "2",
//    "$values": [
//      {
//        "$id": "3",
//        "Name": "Adrian King",
//        "Manager": {
//          "$ref": "1"
//        },
//        "DirectReports": null
//      }
//    ]
//  }
//}
//Tyler is manager of Tyler's first direct report:
//True

Solution 2:[2]

If you modify your code a little bit, you can avoid having two objects with the same key tracked:

var newCity = new City { Name = "City1" };
_dbContext.Cities.Add(newCity);

var p1 = new Person
{
    Id = "P1",
    Name = "Person1",
    City = newCity
};
_dbContext.Persons.Add(p1);

var p2 = new Person
{
    Id = "P2",
    Name = "Person2",
    City = newCity
};
_dbContext.Persons.Add(p2);

var deal = new Deal
{
    Id     = "D1",
    Buyer  = p1,
    Seller = p2
};
_dbContext.Deals.Add(deal);

_dbContext.SaveChanges();

This way, you're telling Entity framework that it is the same city - you're just referencing the object and you added the object to the Cities list. Saving will persist it in the right order according to your table definitions.

Likewise, if you still run into issues because you add the same person - create the person object first and add it to the Persons list.

And another thing - if an object might be already there, query it. For example:

var cityName = "City3";
var city = _dbContext.Cities.FirstOrDefault(f => f.Name == cityName);
if (city == null)
{
   city = new City { Name = cityName };
   _dbContext.Cities.Add(city);
}
// from here on you can safely use city

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Moho
Solution 2