EF 6 Code First,在导航 属性 中包含更改外键 ID 会导致 "A referential integrity constraint violation occurred" 错误

EF 6 Code First, changing a Foreign Key Id with an Include on navigation property causes "A referential integrity constraint violation occurred" error

我正在使用 Entity Framework 6.1.3 并且有这样的场景,我通过导航 属性(使用 Include())检索实体并将其与上下文断开连接,更改外部然后将密钥 ID 重新附加到新的 DbContext:

// Init the Db
using (var db = new MyContext())
{
   var theWarranty = new ProductWarranty { WarrantyName = "The Warranty" };
   var newWarranty = new ProductWarranty { WarrantyName = "New Warranty" };
   var brand = new ProductBrand { BrandName = "The Brand", DefaultWarranty = theWarranty };

   db.ProductBrands.Add(brand);
   db.ProductWarranties.Add(newWarranty);

   db.SaveChanges();
}

// Load the detached Brand
ProductBrand detachedBrand;
using (var db = new MyContext())
{
   detachedBrand = db.ProductBrands.AsNoTracking()
      .Include(b => b.DefaultWarranty)  // <<< If this line is removed the Attach works
      .First(x => x.Id == 1);
}

// Modify the Default Warranty Foreign Key
detachedBrand.DefaultWarranty = null;
detachedBrand.DefaultWarranty_Id = 2;

// Attempt to re-attach and save the changes
using (var db = new MyContext())
{
   var entity = db.Set<ProductBrand>().Attach(detachedBrand); // <<< This line throws the exception

   db.Entry(entity).State = EntityState.Modified;

   db.SaveChanges();
}

我得到:

A referential integrity constraint violation occurred: The property value(s) of >'ProductWarranty.Id' on one end of a relationship do not match the property >value(s) of 'ProductBrand.DefaultWarranty_Id' on the other end.

但是,如果我不使用 Include(),附加工作正常。

在现实场景中我确实需要导航 属性 (DefaultWarranty),但我看不出在分离实体中包含导航与不在分离实体中加载导航有什么区别。根据我的经验和阅读,应该是将外键设置为新值并将导航 属性 设置为空的情况。

我已经阅读了 Ladislav 关于外键与独立属性的博客 http://www.ladislavmrnka.com/2011/05/foreign-key-vs-independent-associations-in-ef-4/,但它并没有完全处理这种情况,据我所知,我在这种情况下使用的是外键。

这是怎么回事?处理包含导航属性的外键更改的正确方法是什么?

这几乎就像 EF 在使用 Include 时没有 "fully" 分离实体......这看起来也很奇怪。

这是简化的设置:

产品品牌

public partial class ProductBrand
{
    public int Id { get; set; }
    public string BrandName { get; set; }

    public Nullable<int> DefaultWarranty_Id { get; set; }
    public virtual ProductWarranty DefaultWarranty { get; set; }
}

产品品牌图

public class ProductBrandMap : EntityTypeConfiguration<ProductBrand>
{
    public ProductBrandMap()
    {
        // Primary Key
        this.HasKey(t => t.Id);

        // Properties
        this.Property(t => t.BrandName)
            .IsRequired()
            .HasMaxLength(40);

        // Table & Column Mappings
        this.ToTable("ProductBrands");
        this.Property(t => t.Id).HasColumnName("Id");
        this.Property(t => t.BrandName).HasColumnName("BrandName");
        this.Property(t => t.DefaultWarranty_Id).HasColumnName("DefaultWarranty_Id");

        // Relationships
        this.HasOptional(t => t.DefaultWarranty)
            .WithMany(t => t.ProductBrands)
            .HasForeignKey(d => d.DefaultWarranty_Id)
            .WillCascadeOnDelete(false);
    }
}

产品保修

public partial class ProductWarranty
{
    public ProductWarranty()
    {
        this.ProductBrands = new List<ProductBrand>();
    }

    public int Id { get; set; }
    public string WarrantyName { get; set; }
    public virtual ICollection<ProductBrand> ProductBrands { get; set; }
}

产品保修地图

public class ProductWarrantyMap : EntityTypeConfiguration<ProductWarranty>
{
    public ProductWarrantyMap()
    {
        // Primary Key
        this.HasKey(t => t.Id);

        // Properties
        this.Property(t => t.WarrantyName)
            .IsRequired()
            .HasMaxLength(40);

        // Table & Column Mappings
        this.ToTable("ProductWarranties");
        this.Property(t => t.Id).HasColumnName("Id");
        this.Property(t => t.WarrantyName).HasColumnName("WarrantyName");
    }
}

启用 ProxyCreationEnabled 时,即使实体未被任何上下文跟踪,它们也会以某种方式在内部相互连接。我的意思是,FK 中的任何更改都由动态代理实体仔细记录以强制执行参照完整性。所以,只需关闭 ProxyCreationEnabled:

public MyContext()
{
     this.Configuration.ProxyCreationEnabled = false;
}

但如果你问我的意见,我更喜欢在实体被跟踪时更改它们,修改后我将它们分离。

我在尝试分配新的 foreign_key 值时遇到了同样的问题。

我在更新核心对象的外键后进行附加,这是一个问题。

相反,我现在附加到对象,然后将更改应用到字段。

public static bool ChangeForeignKeyAssocation(baseobject existing, int newFK, bool throwOnError = true) {
  try {
    using (var e = new Entities()) {
      e.table.Attach(existing);
      e.Entry(existing).State = EntityState.Modified;

      existing.related_table_id = newFK;

      int result = sbe.SaveChanges();
      return (result == 1);
    }
  } catch (Exception ex) {
    //LogIt.E(ex);
    if (throwOnError) {
      throw ex;
    } else {
      return false;
    }
  }
}