Страницы

Поиск по вопросам

понедельник, 9 марта 2020 г.

Entity Framework не удаляется связанная по внешнему ключу запись

#c_sharp #entity_framework


Пытаюсь удалить элемент из коллекции UsersVaults объекта типа User, но получаю Exception
(текст ошибки и код ниже). Подскажите, где я повернул не туда?

Метод изменения объекта:

[HttpPost]
    public ActionResult Edit(int id, FormCollection form)
    {
        try
        {
            using (var db = new AccessControlSystemDatabaseModel())
            {
                var user = db.Users.Find(id);
                var tempData = form.AllKeys.ToDictionary(key
=> key, key => form[key]);

                user.RoleID = Convert.ToInt32(tempData["RoleID"]);
                user.Username = tempData["Username"].ToString();
                user.Password = tempData["Password"].ToString();
                user.Email = tempData["Email"].ToString();

                if (!tempData["GrantAccess"].ToString().Equals("false") && user.UsersVaults.Where(
                    d => d.VaultID == AuthenticationController.CurrentUser.UsersVaults.ToList()[0].VaultID)
                    .ToList()
                    .Count == 0)
                {
                    user.UsersVaults.Add(new UsersVault
                    {
                        UserID = id,
                        VaultID = AuthenticationController.CurrentUser.UsersVaults.ToList()[0].VaultID,
                    });
                }
                else
                {
                    var tt =
                        user.UsersVaults.Where(
                            d => d.VaultID == AuthenticationController.CurrentUser.UsersVaults.ToList()[0].VaultID).ToList()[0];

                    user.UsersVaults.Remove(tt);
                }

                db.Entry(user).State = EntityState.Modified;
                db.SaveChanges();

                return RedirectToAction("Index");
            }
        }
        catch(Exception e)
        {
            return RedirectToAction("Index");
        }
    }


Модель:

public partial class AccessControlSystemDatabaseModel : DbContext
{
    public AccessControlSystemDatabaseModel()
        : base("name=AccessControlSystemDatabaseModel")
    {
    }

    public virtual DbSet Roles { get; set; }
    public virtual DbSet Users { get; set; }
    public virtual DbSet UsersVaults { get; set; }
    public virtual DbSet Vaults { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity()
            .HasMany(e => e.UsersVaults)
            .WithRequired(e => e.User)
            .WillCascadeOnDelete(true);

        modelBuilder.Entity()
            .HasMany(e => e.UsersVaults)
            .WithRequired(e => e.Vault)
            .WillCascadeOnDelete(true);
    }
}


public partial class Role
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Role()
    {
        Users = new HashSet();
    }

    public int ID { get; set; }

    [Required]
    [StringLength(255)]
    public string RoleName { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection Users { get; set; }
}

public partial class User
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public User()
    {
        UsersVaults = new HashSet();
    }

    public int ID { get; set; }

    [Required]
    [StringLength(255)]
    public string Username { get; set; }

    [Required]
    [StringLength(255)]
    public string Password { get; set; }

    public int? RoleID { get; set; }

    [StringLength(255)]
    public string Email { get; set; }

    public virtual Role Role { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection UsersVaults { get; set; }
}

public partial class UsersVault
{
    public int ID { get; set; }

    public int UserID { get; set; }

    public int VaultID { get; set; }

    public virtual User User { get; set; }

    public virtual Vault Vault { get; set; }
}

public partial class Vault
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Vault()
    {
        UsersVaults = new HashSet();
    }

    public int ID { get; set; }

    [Required]
    [StringLength(255)]
    public string VaultName { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection UsersVaults { get; set; }
}




Текст исключения:


  "Операция завершилась с ошибкой. Не удалось изменить связь, поскольку один или
несколько свойств внешнего ключа не допускают значения NULL. При изменении связи соответствующему
свойству внешнего ключа присваивается значение NULL. Если внешний ключ не поддерживает
значений NULL, должна быть определена новая связь, свойству внешнего ключа должно быть
присвоено другое значение, отличное от NULL, либо необходимо удалить несвязанный объект."

    


Ответы

Ответ 1



В EF, к сожалению, операции Add и Remove для навигационных коллекций не всегда симметричны - первая операция устанавливает связь между сущностями и при необходимости добавляет их в базу, вторая же только разрывает связь. Но разорвать связь между UsersVault и User невозможно, отсюда и ошибка. Поэтому надо вместо удаления объекта из коллекции явно удалить его из базы: var tt = user.UsersVaults.Single(...); db.Entry(tt).State = EntityState.Deleted; или db.UsersVaults.Remove(tt); Для сохранения симметричности операций, можно при добавлении UsersVault точно так же сразу добавлять его в базу: db.UsersVaults.Add(new UsersVault { UserID = id, VaultID = ..., }) Кстати, в вашем текущем коде есть дублирование - UserID присваивать не обязательно если вы добавляете объект через навигационную коллекцию - EF проставит его сама. В качестве альтернативного решения могу предложить вообще удалить сущность UsersVault - она не нужна! EF умеет самостоятельно отображать связи "многие-ко-многим" на промежуточные таблицы, просто создайте mtm-связь между Users и Vaults.

Комментариев нет:

Отправить комментарий