【问题标题】:"The instance of entity type cannot be tracked" error when adding detached entity tree添加分离的实体树时出现“无法跟踪实体类型的实例”错误
【发布时间】:2022-04-25 18:41:51
【问题描述】:

我无法添加交易实体,因为买家和卖家引用的是同一个城市“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:“无法跟踪实体类型“City”的实例,因为已经在跟踪具有相同键值 {'Name'} 的另一个实例。附加现有实体时,请确保仅附加一个具有给定键值的实体实例。考虑使用“DbContextOptionsBuilder.EnableSensitiveDataLogging”来查看冲突的键值。'

以下是实体的设置方式:

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

【问题讨论】:

  • 数据库中的任何键都必须是唯一的。使用多个人来自同一城市的城市是错误的。对于密钥,请使用唯一的 ID。

标签: c# entity-framework entity-framework-core


【解决方案1】:

您的两个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

【讨论】:

    【解决方案2】:

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

    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 框架它是同一个城市 - 您只是在引用该对象并将该对象添加到 Cities 列表中。保存将根据您的表定义以正确的顺序保存它。

    同样,如果您仍然因为添加同一个人而遇到问题,请先创建个人对象并将其添加到 Persons 列表中。

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

    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
    

    【讨论】:

      猜你喜欢
      • 2019-10-28
      • 2017-06-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-04-29
      • 2021-05-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多