MVC 3 EF 4.1 dbContext - Удаление объекта данных "один ко многим" с нечетным отношением внешних ключей
Я использую MVC 3, EF 4.1 и dbContext. Мне нужно знать, как удалить объект с отношением "один ко многим" с внешним ключом, который не является нулевым.
Когда я удаляю дочерний объект и выполняю SaveChanges, я получаю сообщение об ошибке:
Операция завершилась неудачно: отношение не может быть изменено, поскольку одно или несколько свойств внешнего ключа не имеют значения NULL. Когда происходит изменение отношения, соответствующее свойство внешнего ключа устанавливается равным нулевому значению. Если внешний ключ не поддерживает нулевые значения, необходимо определить новое отношение, для свойства внешнего ключа должно быть назначено другое ненулевое значение, или не связанный с ним объект должен быть удален.
Из других сообщений я понимаю, что использование Remove (entity) отмечает сущность для удаления. Во время SaveChanges EF устанавливает внешний ключ в Null, и появляется ошибка выше.
Я нашел несколько сообщений, которые используют DeleteObject для дочернего объекта, а не Remove; однако, по-видимому, метод DeleteObject был удален из-за добавления в dbContext и DbSet.
Я нашел сообщения, которые предполагают изменение отношения внешнего ключа EDMX к Nullable. Модификация EDMX прекрасна, но всякий раз, когда делается обновление модели для базы данных, эти изменения становятся нуклерованными и должны быть повторно применены. Не оптимально.
Другая публикация предложила создать прокси-объект с отношениями внешних ключей, установленными на Nullable, но я не понимаю этого подхода. Кажется, что он страдает от той же проблемы, что и изменение EDMX в том, что контекст автоматически обновляется при сохранении изменений в EDMX.
Моя упрощенная модель:
public partial class User
{
public User()
{
this.UserContacts = new HashSet<UserContact>();
}
public long userId { get; set; }
public string userEmail { get; set; }
public string userPassword { get; set; }
public string userFirstName { get; set; }
public string userLastName { get; set; }
. . .
public virtual Country Country { get; set; }
public virtual State State { get; set; }
public virtual ICollection<UserContact> UserContacts { get; set; }
}
}
public partial class UserContact
{
public long userContactId { get; set; }
public long userContactUserId { get; set; }
public long userContactTypeId { get; set; }
public string userContactData { get; set; }
public virtual ContactType ContactType { get; set; }
public virtual User User { get; set; }
}
Пользователь UserContactUserId и userContactTypeId - это обязательные внешние ключи.
В контейнере dbContext оба пользователя и UserContact являются DbSet.
У меня есть ViewModel для пользователя и ViewModel для UserContact следующим образом
public class UserContactViewModel
{
[HiddenInput]
public long UserContactId { get; set; }
[HiddenInput]
public long UserContactUserId { get; set; }
[Display(Name = "Contact")]
[Required]
public string ContactData { get; set; }
[Required]
public long ContactType { get; set; }
[HiddenInput]
public bool isDeleted { get; set; }
}
public class MyProfileViewModel
{
[HiddenInput]
public long UserId { get; set; }
[Required]
[Display(Name = "First Name")]
[StringLength(100)]
public string FirstName { get; set; }
[Required]
[StringLength(100)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
....
public IEnumerable<UserContactViewModel> Contacts { get; set; }
}
При сохранении изменений в пользовательском профиле я просматриваю список объектов UserContactViewModel, чтобы определить, какие из них были добавлены, изменены или удалены.
foreach (var c in model.Contacts)
{
UserContact uc = usr.UserContacts.Single(con => con.userContactId == c.UserContactId);
if (uc != null)
{
if (c.isDeleted == true) // Deleted UserContact
{
ctx.UserContacts.Remove(uc); // Remove doesn't work
}
else // Modified UserContact
{
uc.userContactData = c.ContactData;
uc.userContactTypeId = c.ContactType;
ctx.Entry(uc).State = EntityState.Modified;
}
}
else // New UserContact
{
usr.UserContacts.Add(new UserContact { userContactUserId = model.UserId, userContactData = c.ContactData, userContactTypeId = c.ContactType });
}
}
Буду признателен за любую помощь.
Ответы
Ответ 1
Мне удалось решить проблему следующим образом:
Во-первых, мне удалось получить ObjectContext, отбросив мой DbContext (например, "ctx" ) в IObjectContextAdapter, а затем получить ссылку на ObjectContext.
Далее я просто вызвал метод DeleteObject, который удаляет запись UserContact.
Когда SaveChanges получает удаленные в базе данных события, как ожидалось.
if (c.isDeleted == true) // Deleted UserContact
{
ObjectContext oc = ((IObjectContextAdapter)ctx).ObjectContext;
oc.DeleteObject(uc)
}
Вот фрагмент соответствующего кода:
foreach (var c in model.Contacts)
{
UserContact uc = null;
if (c.UserContactId != 0)
{
uc = ctx.UserContacts.Find(c.UserContactId);
}
if (uc != null)
{
if (c.isDeleted == true) // Deleted UserContact
{
ObjectContext oc = ((IObjectContextAdapter)ctx).ObjectContext;
oc.DeleteObject(uc);
}
else // Modified UserContact
{
uc.userContactData = c.ContactData;
uc.userContactTypeId = c.ContactType;
ctx.Entry(uc).State = EntityState.Modified;
}
}
else // New UserContact
{
usr.UserContacts.Add(new UserContact { userContactData = c.ContactData, userContactTypeId = c.ContactType });
}
}
ctx.Entry(usr).State = EntityState.Modified;
ctx.SaveChanges();
Надеюсь, что это поможет кому-то в будущем.
Ответ 2
Я решил ту же проблему, что и в разделе "Правила удаления каскадов на отношениях" на странице руководства MSDN здесь http://msdn.microsoft.com/en-us/library/bb738695.aspx
Надежда может быть полезной: D
Ответ 3
хорошо, реализуйте свой собственный ICollection и отметьте эти дочерние объекты для удаления вместе с его удалением. И затем, в свой собственный метод SaveChanges переопределите, удалите эти объекты.
Ответ 4
Вы можете настроить отношение к каскаду... это будет распространять удаление на зависимые объекты.
Но это очень опасно:)
Я предпочитаю устанавливать флаг в строке, который запрещает включение уровня данных в будущих запросах, большинству приложений не требуется физическое удаление (и будет шанс отменить).