5

很抱歉的含糊不清的標題,這是很難形容這一行:爲什麼在更新外鍵後變成引用約束不一致?

我有2個實體UserUserAddress,其中用戶有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. 

我打電話了用戶對象我從數據庫中,它包含了DefaultDeliveryAddress retrive,我通過沿着它加載的方法急切的加載。

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 

簡而言之,我只想讓用戶也是他DefaultInvoiceAddress,這與上面的SQL Update命令很容易完成的DefaultDeliveryAddress,但我很想念我的EF代碼的東西。 我已經檢查了:

  • 只有ID設置,導航性能(DefaultInvoiceAddress)重新設置爲null
  • UserAddress.UserId = User.Id(很明顯,因爲它已經被分配給用戶)
  • 用戶對象將成爲Modified(含調試檢查),因爲它的一個屬性被標記爲修改
  • 我也試過清除包括默認地址的導航性能,但是這並沒有幫助

我懷疑這個問題是由於用戶實體有2個UserAddress引用,並且這兩個外鍵都設置爲引用相同的地址 - 我如何才能使用EF來處理這個問題?

更新:

下面是用戶實體的映射:

// 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沒有導航屬性返回到用戶;它只包含HasMaxLength和HasColumnName設置(我排除它們以保持問題的可讀性)。

更新2

下面是從智能跟蹤執行的命令:

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

看起來好像沒什麼問題;似乎只有EF狀態管理器被關鍵映射弄糊塗了。

+0

發佈用戶和地址的定義和配置。同時發佈生成的實際SQL狀態。 –

+0

嗯,SELECT可以簡單地從查詢中獲得,但對於UPDATE/INSERT,您需要SQL Mgmt工作室或配置文件(迷你配置文件似乎值得一看)。 –

+0

我發現了一種用Intellitrace獲得聲明的方法,我用命令更新了我的問題。 – enzi

回答

7

找出問題所在:顯然它將導航屬性設置爲空時的差異很大,因爲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可能是足夠聰明,忽略的情況下導航屬性更改)。

我不會馬上接受我自己的回答,讓其他(更有資格的)讀者有機會回答;如果在接下來的兩天內沒有發佈任何答案,我會接受這個答案。