EF 6 Code First,使用包含在导航属性上的外键ID更改会导致"发生引用完整性约束违规"错误

mip*_*ips 5 c# entity-framework ef-code-first dbcontext entity-framework-6

我正在使用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();
}
Run Code Online (Sandbox Code Playgroud)

我越来越:

发生参照完整性约束违规:关系一端的>'ProductWarranty.Id'的属性值与另一端的'ProductBrand.DefaultWarranty_Id'的属性>值不匹配.

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

我确实需要真实场景中的导航属性(DefaultWarranty),但我没有看到在分离的实体中包含导航与在未分离的实体中加载它的区别.根据我的经验和阅读,应该将外键设置为新值并将navigation属性设置为null.

我已经阅读了Ladislav关于外键与独立财产的博客http://www.ladislavmrnka.com/2011/05/foreign-key-vs-independent-associations-in-ef-4/,但它还不太明显在这种情况下,我可以告诉我在这种情况下使用外键.

发生了什么以及处理如何更改带有包含的导航属性的外键的正确方法是什么?

当使用Include时,EF几乎没有"完全"分离实体......这看起来也很奇怪.

这是简化的设置:

产品品牌

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; }
}
Run Code Online (Sandbox Code Playgroud)

产品品牌地图

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

产品质量保证

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; }
}
Run Code Online (Sandbox Code Playgroud)

产品保修地图

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");
    }
}
Run Code Online (Sandbox Code Playgroud)

Ali*_*eza 4

启用后ProxyCreationEnabled,即使实体没有被任何上下文跟踪,它们也会以某种方式在内部相互连接。我的意思是,动态代理实体会仔细记录 FK 中的任何更改,以强制引用完整性。因此,只需关闭即可ProxyCreationEnabled

public MyContext()
{
     this.Configuration.ProxyCreationEnabled = false;
}
Run Code Online (Sandbox Code Playgroud)

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