5

Извините за туманной названия, это трудно описать это в одной строке:Почему реляционные ограничения несовместимы после обновления внешнего ключа?

У меня есть 2 сущностей User и UserAddress, где пользователь имеет 2 внешних ключей DefaultInvoiceAddressId и DefaultDeliveryAddressId и UserAddress имеет UserId внешний ключ.

Пользовательский объект имеет свойства навигации для адресов по умолчанию (DefaultInvoiceAddress и DefaultDeliveryAddress), а также один для всех его адресов: 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 I retrive из базы данных и 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) повторно установить нулевое значение
  • UserAddress.UserId = User.Id (очевидно, так как он уже назначен пользователь)
  • объект пользователя будет Modified (проверено с отладчиком), так как один из его свойств помечается как модифицированные
  • Я также попытался очистки и адреса по умолчанию навигационные свойства, но это не помогло

Я подозреваю, что эта проблема возникает из-за того, что пользовательский объект имеет 2 ссылки на UserAddress, и оба внешних ключа настроены так, чтобы ссылаться на один и тот же адрес - как я могу заставить EF работать с этим?

Update:

Вот отображения объекта пользователя:

// 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 (я исключаю их, чтобы вопрос был несколько читаемым).

Update 2

Вот выполнена команда из IntelliTrace:

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

Выглядит хорошо для меня; кажется, только диспетчер состояний EF путается с помощью сопоставлений клавиш.

+0

Опубликовать определения и конфигурации пользователя и адреса. Также опубликуйте фактические созданные statemnts SQL. –

+0

Хм, SELECT можно просто получить из запроса, но для UPDATE/INSERT вам понадобится студия SQL Mgmt или профиль (мини-профайлер, кажется, стоит посмотреть). –

+0

Я нашел способ получить утверждение с Intellitrace, я обновил свой вопрос командой. – enzi

ответ

7

Выяснено, что проблема заключается в том, что для определения навигационных свойств значение 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 может интерпретировать это как изменение (в моем случае внешний ключ имеет значение NULL, если это не так, EF может быть достаточно умным, чтобы игнорировать изменение навигационной собственности).

Я не буду принимать свой собственный ответ сразу, чтобы дать другим (более квалифицированным) читателям возможность ответить; если ответы не будут отправлены в течение следующих 2 дней, я соглашусь с этим.

Смежные вопросы