实体框架:附加断开的实体会引发异常

时间:2016-06-16 00:39:57

标签: c# entity-framework

我有两个实体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,Spindle Head,SomeDesc
  • Asset1C53,Milling Block,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&#39;的实体失败,因为同一类型的另一个实体已具有相同的主键值。使用&#39;附加&#39;方法或将实体的状态设置为“未更改”#39;或者&#39;修改&#39;如果图中的任何实体具有冲突的键值。这可能是因为某些实体是新的并且尚未收到数据库生成的键值。在这种情况下,请使用&#39;添加&#39;方法或“添加”#39;实体状态跟踪图形,然后将非新实体的状态设置为“未更改”。或者&#39;修改&#39;酌情。

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

2 个答案:

答案 0 :(得分:1)

循环遍历context.Employees及其资产Asset&#34; Asset1F32&#34;加载到上下文中,即附加,因此您无法使用相同的主键值附加另一个实例

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

更强大的解决方案是:

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

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

答案 1 :(得分:0)

查看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);
}