EF 6 代码首先,在导航属性上使用包含更改外键 ID 会导致“发生引用完整性约束违规”错误

Posted

技术标签:

【中文标题】EF 6 代码首先,在导航属性上使用包含更改外键 ID 会导致“发生引用完整性约束违规”错误【英文标题】:EF 6 Code First, changing a Foreign Key Id with an Include on navigation property causes "A referential integrity constraint violation occurred" error 【发布时间】:2016-01-10 14:33:28 【问题描述】:

我正在使用 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();

我明白了:

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

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

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

我已经阅读了 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; 

产品品牌图

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");
    

【问题讨论】:

我测试过了,没有任何异常! @Alireza - 我的开发和项目的细节是 VS2013,.net 4.5.2。我再次运行代码,我仍然得到错误。我也将我的项目发送给了一位开发人员,他遇到了同样的行为......并且和我一样困惑。你是否有具体的步骤来运行代码? 你能把你的项目发给我吗? @Alireza 不确定你是如何测试它的,但我可以很容易地重现它,只需一个简单的模型就足够了。看起来分离的实体仍然对关系有一些了解(由Include 加载),这对我来说相当奇怪。 @Alireza - 从expirebox.com/download/155d60675ebcdad9b18d23b4bbdd3113.html下载项目文件 【参考方案1】:

启用ProxyCreationEnabled 时,即使实体没有被任何上下文跟踪,它们也会以某种方式在内部相互连接。我的意思是,动态代理实体会仔细记录 FK 中的任何更改,以确保引用完整性。所以,干脆关掉ProxyCreationEnabled

public MyContext()

     this.Configuration.ProxyCreationEnabled = false;

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

【讨论】:

太棒了!加载实体时设置 ProxyCreationEnabled = false 就可以了!至于“我更喜欢在跟踪实体时更改实体,修改后我将它们分离” - 该示例是 WPF 应用程序的简化版本,用户/UI 层修改分离的实体,然后将其发送回应用程序层。 【参考方案2】:

我在尝试分配新的 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;
    
  

【讨论】:

以上是关于EF 6 代码首先,在导航属性上使用包含更改外键 ID 会导致“发生引用完整性约束违规”错误的主要内容,如果未能解决你的问题,请参考以下文章

EF的导航属性

EF6:导航属性外键到外键

没有导航属性的EF Code First外键

EF代码中视图之间的导航属性优先

EF Code First 导航属性 与外键

EF Code First 导航属性 与外键