【问题标题】:Entity Framework: Attach Disconnected Entity Throws Exception实体框架:附加断开连接的实体引发异常
【发布时间】:2023-03-08 03:44:01
【问题描述】:

我有两个实体 EmployeeAsset。它们被定义为:

public class Employee
{
    public Employee()
    {
        this.Assets = new List<Asset>();
    }

    public int EmployeeID { get; set; }
    public string Name { get; set; }
    public virtual List<Asset> Assets { get; set; }
}

public class Asset
{
    public string FacilityAssetID { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
}

Fluent 将架构定义为:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Employee>().ToTable("Employees");
    modelBuilder.Entity<Employee>().HasKey(e => e.EmployeeID);
    modelBuilder.Entity<Employee>().Property(e => e.Name).HasMaxLength(50).IsRequired();
    modelBuilder.Entity<Employee>().HasMany(e => e.Assets).WithMany().Map(m => m.MapLeftKey("EmployeeID").MapRightKey("FacilityAssetID").ToTable("EmployeeToAsset"));

    modelBuilder.Entity<Asset>().ToTable("Assets");
    modelBuilder.Entity<Asset>().HasKey(a => a.FacilityAssetID);
    modelBuilder.Entity<Asset>().Property(a => a.Name).HasMaxLength(50).IsRequired();
    modelBuilder.Entity<Asset>().Property(a => a.Description).HasMaxLength(200).IsRequired();

    base.OnModelCreating(modelBuilder);
}

现在,我有一些员工和一些资产。使用 Web 服务填充资产。假设我有以下资产:

  • {FacilityAssetID}、{Name}、{Description}

  • Asset1F32,主轴头,SomeDesc
  • Asset1C53,铣削块,SomeDesc

有两个具有 ID 的资产:Asset1F32 和 Asset1C53。如果我尝试使用以下代码向员工添加资产,我可以成功添加资产:

using (VCContext context = new VCContext())
{
    Employee emp = context.Employees.First();
    Asset assetDetached = new Asset() { FacilityAssetID = "Asset1F32" };
    context.Assets.Attach(assetDetached);
    emp.Assets.Add(assetDetached);

    context.SaveChanges();
}

如果我事先循环了一些资产并尝试添加资产,它将引发异常。这是代码:

using (VCContext context = new VCContext())
{
    foreach (Employee employee in context.Employees)
    {
        Console.WriteLine(employee.Name);
        foreach (Asset asset in employee.Assets)
        {
            Console.WriteLine(string.Format("  - {0}", asset.FacilityAssetID));
        }
    }

    Employee emp = context.Employees.First();
    Asset assetDetached = new Asset() { FacilityAssetID = "Asset1F32" };
    context.Assets.Attach(assetDetached);  // EXCEPTION HERE!!!
    emp.Assets.Add(assetDetached);

    context.SaveChanges();
}

抛出的异常是:

附加类型为“ASTC.Asset”的实体失败,因为同一类型的另一个实体已经具有相同的主键值。如果图中的任何实体具有冲突的键值,则在使用“附加”方法或将实体的状态设置为“未更改”或“已修改”时,可能会发生这种情况。这可能是因为某些实体是新实体,尚未收到数据库生成的键值。在这种情况下,使用“添加”方法或“已添加”实体状态来跟踪图形,然后将非新实体的状态设置为“未更改”或“已修改”。

为什么它会在第二种情况下抛出异常而不是第一种情况?另外,如何在第二种情况下添加分离的资产?

【问题讨论】:

  • 你为什么在这里调用 Attach()?您的实体是在同一上下文中创建和查询的,因此无需附加它们。
  • 唯一被创建和查询的实体是Employee. Asset 未被查询。这是我崩溃的一个简短示例。实际上,我正在遍历数据库的当前资产列表并与另一个异地列表进行比较。如果有需要添加的,我正在尝试添加。
  • 是的,但我的意思是,如果您想添加尚未在数据库中的资产,只需致电emp.Assets.Add(new Asset()),没有理由先附加它。 Attach() 函数用于已在数据库中但已在另一个上下文中查询的实体。
  • 嗨,弗洛里安。它可能已经在数据库中,但可能尚未与该员工关联。
  • 好的,我明白了 :-) 你想使用实体存根来建立你的关联。抛出此异常是因为您尝试为已在 foreach 循环中加载的实体附加存根。因此,要解决此问题,您必须检查加载的实体并使用完整实体而不是存根并将其与员工关联。

标签: c# entity-framework


【解决方案1】:

通过循环遍历context.Employees 及其资产,Asset "Asset1F32" 被加载到上下文中,即附加,因此您不能附加具有相同主键值的另一个实例

我不知道您为什么要遍历资产,但一个快速解决方法是使用 context.Employees.AsNoTracking(),因此该语句中的实体不会附加到上下文中。

更强大的修复方法是:

var id = "Asset1F32";
var asset = context.Assets.Local.FirstOrDefault(a => FacilityAssetID == id)
                ?? new Asset() { FacilityAssetID = id };
emp.Assets.Add(asset);

通过这种方式,您可以检查上下文的缓存中是否已附加资产,如果尚未附加,则将其创建为 存根实体,这样您就可以有效地建立关联(即无需不必要的从数据库中获取资产)。

【讨论】:

    【解决方案2】:

    看看this answer 是否有帮助:

    它显示了如何检查对象是否已附加。

    这个版本适合我:

    public static bool Exists<T>(T entity) where T : class
    {
        return dbContext.Set<T>().Local.Any(e => e == entity);
    }
    

    这样称呼:

    if (!Exists<Person>(p))
    {
        dbContext.People.Attach(p);
    }
    

    【讨论】:

    • 将它包含在您的答案中而不是发布一些链接会很有帮助。
    猜你喜欢
    • 2012-03-13
    • 1970-01-01
    • 1970-01-01
    • 2014-10-16
    • 1970-01-01
    • 1970-01-01
    • 2016-06-18
    • 1970-01-01
    • 2013-07-10
    相关资源
    最近更新 更多