"The instance of entity type cannot be tracked" 添加分离的实体树时出错

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

我无法添加交易实体,因为买方和卖方引用的是同一个城市“City1”。 我在这里使用分离的实体,因为我通过 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();

错误是

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.'

以下是实体的设置方式:

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

与此类实体合作的正确方法是什么? 我使用 EF Core 6

您的两个 Person 对象需要为 City 引用同一个对象。或者,公开并使用 FK 属性(这似乎是城市的 Name 属性)而不是此操作的导航 属性。

如果您想继续使用 JSON 中的 City 对象,则需要保留引用。例如,这里是 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

如果稍微修改一下代码,就可以避免跟踪具有相同键的两个对象:

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

这样,您就告诉 Entity framework 这是同一个城市 - 您只是引用该对象并将该对象添加到城市列表中。保存将根据您的 table 定义以正确的顺序保留它。

同样,如果您仍然 运行 遇到问题,因为您添加了同一个人 - 首先创建人员对象并将其添加到人员列表中。

还有一件事 - 如果某个对象可能已经存在,请查询它。 例如:

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