C# 为什么更新外键后引用约束变得不一致?
抱歉,标题含糊不清,很难用一行字来描述: 我有两个实体C# 为什么更新外键后引用约束变得不一致?,c#,entity-framework,sql-update,entity-framework-5,foreign-key-relationship,C#,Entity Framework,Sql Update,Entity Framework 5,Foreign Key Relationship,抱歉,标题含糊不清,很难用一行字来描述: 我有两个实体User和UserAddress,其中User有两个外键DefaultInvoiceAddressId和DefaultDeliveryAddressId,UserAddress有一个UserId外键 用户对象具有默认地址(DefaultInvoiceAddress和DefaultDeliveryAddress)的导航属性,以及其所有地址的导航属性:AllAddresses 映射等工作,创建和更新用户和地址也工作 但不起作用的是将用户的现有地址
User
和UserAddress
,其中User有两个外键DefaultInvoiceAddressId
和DefaultDeliveryAddressId
,UserAddress有一个UserId
外键
用户对象具有默认地址(DefaultInvoiceAddress
和DefaultDeliveryAddress
)的导航属性,以及其所有地址的导航属性:AllAddresses
映射等工作,创建和更新用户和地址也工作
但不起作用的是将用户的现有地址设置为DefaultInvoiceAddress。在SQL术语中,我希望发生的是更新用户集DefaultInvoiceAddressId=5,其中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;
}
}
创建新用户地址和更新地址时都会调用此方法。创建场景按预期工作,但是在更新情况下,我收到以下错误:
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调用该方法,我通过即时加载将其加载到该方法旁边
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,导航属性(
)将重新设置为nullDefaultInvoiceAddress
- UserAddress.UserId=User.Id(显然,因为它已经分配给用户)
- 由于用户对象的一个属性被标记为已修改,因此该用户对象将变为已修改(通过调试器检查)
- 我还尝试清除两个默认地址导航属性,但也没有帮助
// 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
以下是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不需要他们找出update语句
- 在将实体附加到上下文之前,请清除导航属性,否则EF可能会将其解释为更改(在我的情况下,外键可以为空,如果不是这样,EF可能足够聪明,可以忽略导航属性更改)
我不会马上接受我自己的答案,给其他(更有资格的)读者一个回答的机会;如果在接下来的两天内没有回复,我将接受此回复。发布用户和地址定义和配置。同时发布实际生成的SQL语句。嗯,SELECT可以简单地从查询中获取,但对于更新/插入,您需要SQL Mgmt studio或配置文件(迷你配置文件器似乎值得一看)。我找到了一种使用Intellitrace获取语句的方法,我已经用命令更新了我的问题。