为什么更新外键后引用约束不一致?

抱歉这个模糊的标题,很难用一行来描述:

我有2个实体UserUserAddress ,其中User有2个外键DefaultInvoiceAddressIdDefaultDeliveryAddressId ,UserAddress有UserId外键。

用户对象具有默认地址( DefaultInvoiceAddressDefaultDeliveryAddress )的导航属性以及其所有地址的一个: AllAddresses

映射等工作,创建和更新用户和地址也有效。

但是不起作用的是将用户的现有地址设置为例如DefaultInvoiceAddress。 在SQL术语中,我想要发生的是UPDATE USER SET DefaultInvoiceAddressId = 5 WHERE Id = 3

我试过以下方式:

 private void MarkAs(User user, UserAddress address, User.AddressType type) { if (context.Entry(user).State == EntityState.Detached) context.Users.Attach(user); // guess I don't really need this: if (context.Entry(address).State == EntityState.Detached) context.UserAddresses.Attach(address); if (type.HasFlag(User.AddressType.DefaultInvoice)) { user.DefaultInvoiceAddressId = address.Id; user.DefaultInvoiceAddress = null; context.Entry(user).Property(u => u.DefaultInvoiceAddressId).IsModified = true; } if (type.HasFlag(User.AddressType.DefaultDelivery)) { user.DefaultDeliveryAddressId = address.Id; user.DefaultDeliveryAddress = null; context.Entry(user).Property(u => u.DefaultDeliveryAddressId).IsModified = true; } } 

在创建新的UserAddresses以及更新地址时都会调用此方法。 创建方案按预期工作,但在更新的情况下,我收到以下错误:

 The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: A referential integrity constraint violation occurred: The property values that define the referential constraints are not consistent between principal and dependent objects in the relationship. 

我用一个User对象调用该方法,我从数据库中检索它和它包含的DefaultDeliveryAddress,我通过eager loading加载它。

 var user = mainDb.User.Get(UnitTestData.Users.Martin.Id, User.Include.DefaultAddresses); var existingAddress = user.DefaultDeliveryAddress; mainDb.User.Addresses.SetAs(user, existingAddress, User.AddressType.DefaultInvoice)) // the SetAs method verfies input parameters, calls MarkAs and then SaveChanges 

简而言之,我只想让用户的DefaultDeliveryAddress也是他的DefaultInvoiceAddress,这可以通过上面的SQL Update命令轻松完成,但我遗漏了我的EF代码。 我已经检查过了:

  • 仅设置了Id,导航属性( DefaultInvoiceAddress )被DefaultInvoiceAddress为null
  • UserAddress.UserId = User.Id(显然因为它已经分配给用户)
  • 用户对象将变为Modified (使用调试器检查),因为其中一个属性被标记为已修改
  • 我也尝试清除两个默认地址导航属性,但这也没有帮助

我怀疑这个问题是由于User实体有2个UserAddress引用,并且两个外键都设置为引用相同的地址 – 我怎样才能让EF使用它?

更新:

以下是User实体的映射:

 // from UserMap.cs: ... Property(t => t.DefaultInvoiceAddressId).HasColumnName("DefaultInvoiceAddressId"); Property(t => t.DefaultDeliveryAddressId).HasColumnName("DefaultDeliveryAddressId"); // Relationships HasOptional(t => t.DefaultInvoiceAddress) .WithMany() .HasForeignKey(t => t.DefaultInvoiceAddressId); HasOptional(t => t.DefaultDeliveryAddress) .WithMany() .HasForeignKey(t => t.DefaultDeliveryAddressId); HasMany(t => t.AllAddresses) .WithRequired() .HasForeignKey(t => t.UserId) .WillCascadeOnDelete(); 

UserAddress没有返回用户的导航属性; 它只有contanis HasMaxLength和HasColumnName设置(我排除它们以保持问题有点可读)。

更新2

这是Intellitrace执行的命令:

 The command text "update [TestSchema].[User] set [DefaultInvoiceAddressId] = @0 where ([Id] = @1) " was executed on connection "Server=(localdb)\..." 

对我来说很好看; 似乎只有EF状态管理员才被关键映射弄糊涂了。

找出问题:显然,将导航属性设置为null会产生很大的不同,因为EF可能会将其解释为预期的更改/更新(至少这是我怀疑的)。

以下版本的MarkAs方法有效:

 private void MarkAs(User user, UserAddress address, User.AddressType type) { if (context.Entry(user).State == EntityState.Detached) { // clear navigation properties before attaching the entity user.DefaultInvoiceAddress = null; user.DefaultDeliveryAddress = null; context.Users.Attach(user); } // address doesn't have to be attached if (type.HasFlag(User.AddressType.DefaultInvoice)) { // previously I tried to clear the navigation property here user.DefaultInvoiceAddressId = address.Id; context.Entry(user).Property(u => u.DefaultInvoiceAddressId).IsModified = true; } if (type.HasFlag(User.AddressType.DefaultDelivery)) { user.DefaultDeliveryAddressId = address.Id; context.Entry(user).Property(u => u.DefaultDeliveryAddressId).IsModified = true; } } 

总结我对未来读者的发现:

  • 如果您打算通过外键属性更新实体,请清除导航属性。 EF不需要他们来计算更新语句。
  • 将实体附加到上下文之前清除导航属性,否则EF可能会将其解释为更改(在我的情况下,外键可以为空,如果不是这种情况,则EF可能足够聪明以忽略导航属性更改)。

我不会马上接受我自己的回答,让其他(更有资格的)读者有机会回答; 如果在接下来的两天内没有发布答案,我会接受这个。