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

标签 c# entity-framework sql-update entity-framework-5 foreign-key-relationship

抱歉,标题含糊不清,很难用一行来描述:

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

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

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

但不起作用的是将用户的现有地址设置为例如默认发票地址。在 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 调用该方法,我通过预先加载将其加载。

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) 重新设置为 null
  • UserAddress.UserId = User.Id(显然是因为它已经分配给了用户)
  • 用户对象将变为已修改(通过调试器检查),因为它的一个属性被标记为已修改
  • 我也尝试清除两个默认地址导航属性,但这也没有帮助

我怀疑这个问题是由于 User 实体有 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 没有返回 User 的导航属性;它仅包含 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;
        }
    }

为 future 的读者总结我的发现:

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

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

关于c# - 为什么更新外键后引用约束不一致?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17941905/

相关文章:

c# - Ajax 错误名称未解决

entity-framework - 无法创建 'ElectiveTesting.Models.ApplicationUser' 类型的常量值

php - 仅更新 1 行 While 循环错误

c# - EF 6 不选择最近更新的值,除非我重建项目

entity-framework - 加载在另一个DbContext中所做的更改

mysql - 使用 JOIN 和 GROUP_CONCAT 进行更新

php - 无法使用此 mysql 语句更新分数

c# - 按名称属性比较文本框控件

c# - 序列化对象时如何忽略事件订阅者?

c# - 如何判断 linq to sql 对象是新的、修改的还是未更改的?