Entity Framework удаляет слишком много записей - C#

Я волнуюсь с Entity Framework. Это должно быть проще и быстрее, но это просто ужасно.

Я использую первый подход к базе данных с двумя очень простыми таблицами. Таблица User с Id и Username, первичный ключ и автоинкремент на Id, и таблица Permission с permissionname и userid, первичный ключ в обоих столбцах и переход от userid к user.id.

Это мой код для предоставления и отзыва разрешений:

   public bool ContributionMarginCustomer
    {
        get => GetPermission(_contributionmarginCustomer);
        set => SetPermission(value, _contributionmarginCustomer);
    }

Свойство ContributionMarginCustomer привязано к флажку.

private void SetPermission(bool permissionIsGranted, string key)
{
    var permissionStatus = permissionIsGranted ? PermissionStatus.Granted : PermissionStatus.Revoked;

    using (var entity = new KundeninfoEntities())
    {
        var user = entity.Users.Single(x => x.Id == _user.Id);
        var existingPermission = user.Permissions.SingleOrDefault(x => x.Name == key);

        switch (permissionStatus)
        {
            case PermissionStatus.Granted:
                if (existingPermission == null)
                {
                    user.Permissions.Add(new Permission { Name = key });
                    entity.SaveChanges();
                }
                break;

            case PermissionStatus.Revoked:
                if (existingPermission != null)
                {
                    entity.Permissions.Remove(existingPermission);
                    entity.SaveChanges();
                }
                break;
        }

        _permissions = entity.Permissions
                             .Where(x => x.UserId == _user.Id)
                             .ToList();
    }
}

Предоставление разрешения отлично работает.

Удаление разрешения с помощью entity.Permission.Remove(existingPermission) удаляет его у каждого пользователя в базе данных.

Я не понимаю такого поведения. Кто-нибудь из вас?

Спасибо

Обновлено: Таблица пользователя

Entity Framework удаляет слишком много записей - C#

Таблица разрешений до:

Entity Framework удаляет слишком много записей - C#

Таблица разрешений после удаления с помощью userId 15 и ключа CONTRIBUTIONMARGINCUSTOMER

Entity Framework удаляет слишком много записей - C#

Изменить 2: РЕШЕНИЕ

Я изменил таблицу разрешений, чтобы в качестве первичного ключа использовался только один столбец. Это означает, что в новой таблице разрешений есть три столбца: Id (автоинкремент), Name и UserId с уникальным ключом для имени и идентификатором пользователя.

Приведенный выше код теперь работает, но меня это не очень устраивает.

Не могли бы вы указать, что именно вы имеете в виду, говоря «удаляет его у каждого пользователя в базе данных»? Я интерпретирую это как «одно и то же разрешение удалено у всех пользователей», но это не должно быть возможным. Можете ли вы в качестве примера показать нам содержимое обеих «таблиц» до и после удаления? Пожалуйста, используйте как минимум два разных типа разрешений.

Sentry 08.08.2018 09:36

я отредактировал свой пост

Thomas Klammer 08.08.2018 09:43

Я пытаюсь воспроизвести вашу исходную проблему, потому что это вызывает у меня любопытство. Дай мне несколько минут, хорошо? ;)

Sentry 08.08.2018 10:02

Кажется, вы делаете что-то еще не так. Фактически, вы никогда не показываете нам, как вы называете SetPermission(). Я предполагаю, что вы вызываете это слишком часто, то есть для всех пользователей. Я реализовал это, и все работает как надо, удаление разрешения удаляет его только для этого одного пользователя. Не могли бы вы показать, как вы звоните на SetPermissions()?

Sentry 08.08.2018 10:11

Я обновил пост. SetPermission() вызывается только один раз. Отладил.

Thomas Klammer 08.08.2018 10:18

Спасибо. Однако мне все еще недостаточно проверить свою гипотезу. Но я настоятельно рекомендую вам добавить в метод SetPermissions() некоторые отладочные данные, которые показывают, когда и с какими аргументами он выполняется. Потому что, когда я выполняю его один раз, для конкретного пользователя удаляется только одно разрешение. Значит, вы должны делать что-то по-другому, чтобы это вызывалось слишком часто.

Sentry 08.08.2018 10:24

Когда я изменил базу данных, как упоминалось выше, код заработал. Ничего не менял. Это определенно не вызывается дважды.

Thomas Klammer 08.08.2018 10:28

Позвольте нам продолжить обсуждение в чате.

Sentry 08.08.2018 10:35
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
83
4

Ответы 4

Во втором случае вы удаляете свое разрешение не у пользователя, а у объекта (который по сути является всем вашим списком пользователей). Изменить на

case PermissionStatus.Revoked:
                if (existingPermission != null)
                {
                    user.Permissions.Remove(existingPermission);
                    entity.SaveChanges();
                }
                break;

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

Thomas Klammer 08.08.2018 08:38

@ThomasKlammer Вероятно, потому, что таким образом связь удаляется, а сущность existingPermission - нет. А поскольку его первичный ключ содержит user.id, это не сработает.

Sentry 08.08.2018 09:33

ОК. но, на мой взгляд, этот ответ неверен. Если я удалю конкретное разрешение из коллекции разрешений, оно не должно удалять несколько разрешений.

Thomas Klammer 08.08.2018 09:34

Установите состояние разрешения на Удалено.

var existingPermission = user.Permissions.SingleOrDefault(x => x.Name == key);
//your other code here.
entity.Entry(existingPermission).State = EntityState.Removed;
entity.SaveChanges();

такое же поведение!

Thomas Klammer 08.08.2018 09:49

Могу я увидеть содержимое вашей таблицы разрешений?

Captain Kenpachi 08.08.2018 10:41

находится в исходном посте

Thomas Klammer 08.08.2018 11:26

Мне кажется, что ваша база данных недостаточно нормализована. IMHO это усложняет ваш код и вызывает проблемы при предоставлении / отказе в разрешениях.

У вас есть Users и Permissions. Каждый User имеет ноль или более Permissions, каждому Permission предоставлено ноль или более Users. Стандартное отношение "многие ко многим".

Многие ко многим в структуре сущностей разработаны следующим образом:

class User
{
    public int Id {get; set;}

    // every user has zero or more Permissions (many-to-many)
    public virtual ICollection<Permission> Permissions {get; set;}

    ...
}
class Permission
{
    public int Id {get; set;}

    // every Permission is granted to zero or more Users (many-to-many)
    public virtual ICollection<User> Users {get; set;}
    ...
}
class MyDbContext : DbContext
{
     public DbSet<User> Users {get; set;}
     public DbSet<Permission> Permissions {get; set;}
}

Это все, что структура сущности должна знать, что вы разработали отношение «многие ко многим». Он создаст таблицу «Пользователи и разрешения», а также таблицу соединений, которая необходима для (групповых) объединений, которые необходимо выполнить, когда вы хотите получить «Разрешения, предоставленные пользователю» или «Пользователи, у которых есть определенные разрешения». .

Однако вы не используете эту таблицу соединений в своем коде, вы используете ICollection. Entity Framework достаточно умен, чтобы понимать, что соединение (Group) необходимо с таблицей соединений, и будет выполнять правильные соединения

Я создам несколько функций расширения для DbContext:

static void GrantPermission(this MyDbContext dbContext, User user, int permissionId)
{
    // TODO: check input parameters
    var permissionToGrant = dbContext.Permissions
        .Where(permission => permission.Id == permissionId)
        .FirstOrDefault();
    // TODO: decide what to do if not found
    user.Permissions.Add(permissionToGrant);
    var userToChange = dbContext.Users
}
static void GrantPermission(this MyDbContext dbContext, Permission permission, int userId)
{
     // TODO: check input parameters
     var userToGrantPermission = dbContext.Users
         .Where(user => user.Id == userId)
         .FirstToDefault();
      // TODO: decide what to do if not found
      permission.Users.Add(userToGrantPermission);
}

При желании предоставьте Разрешение по имени:

static void GrantPermission(this MyDbContext dbContext, User user, string permissionName)
{
    // TODO: check input parameters
    var permissionToGrant = dbContext.Permissions
        .Where(permission => permission.Name == permissionName)
        .FirstOrDefault();
    // TODO: decide what to do if not found
    user.Permissions.Add(permissionToGrant);
    var userToChange = dbContext.Users
}

Запретить разрешение:

static void GrantPermission(this MyDbContext dbContext, User user, string permissionName)
{
    // TODO: check input parameters
    var permissionToDeny = dbContext.Permissions
        .Where(permission => permission.Name == permissionName)
        .FirstOrDefault();
    if (permissionToDeny != null)
    {
         user.Permissions.Remove(permissionToDeny);
    }
    // else: user does not have this Permission; do nothing
}

// TODO: if desired: add function with userId and permissionName

Использование:

using (var dbContext = new MyDbContext()
{
     int userId = ...
     string permissionName = ...
     dbContext.GrantPermission(userId, permissionName);
     dbContext.SaveChanges();
}

это должно работать. но я не хочу хранить все доступные разрешения в базе данных. О них должен знать только код.

Thomas Klammer 08.08.2018 10:21

Это потому, что вы не хотите, чтобы другие приложения считывали ваши разрешения? Почему бы не обеспечить безопасный доступ к вашей базе данных, чтобы только те, кто знает пароль, могли получить к ней доступ?

Harald Coppoolse 08.08.2018 14:21

Я изо всех сил пытался воспроизвести проблему, но не смог.

Я твердо верю, что причина поведения, о котором вы сообщаете, заключается в том, что SetPermission() не называется так, как вы думаете.

Вот мой код и результат, который он производит. Единственные изменения в методе SetPermission() - сделать его статическим, чтобы я мог сделать свой код коротким, но он не должен изменять функциональность.

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;

namespace EFTest
{
   public class Program
   {
      static void Main( string[] args )
      {
         Database.SetInitializer( new DropCreateDatabaseAlways<EFTestContext>() );

         User userHans;
         User userFranz;

         var permissionA = "A";
         var permissionB = "B";

         using( var context = new EFTestContext() )
         {
            userHans = context.Users.Add( new User() { Name = "Hans" } );   // Id = 1
            userFranz = context.Users.Add( new User() { Name = "Franz" } ); // Id = 2
            context.SaveChanges();
         }

         SetPermission( userHans.Id, true, permissionA );
         SetPermission( userHans.Id, true, permissionB );
         SetPermission( userFranz.Id, true, permissionA );
         SetPermission( userFranz.Id, true, permissionB );
         ListAllPermissions();
         // 1: A
         // 1: B
         // 2: A
         // 2: B

         SetPermission( userFranz.Id, false, permissionA );    
         ListAllPermissions();
         // 1: A
         // 1: B
         // 2: B

         SetPermission( userHans.Id, false, permissionB );    
         ListAllPermissions();
         // 1: A
         // 2: B    
      }

      enum PermissionStatus
      {
         Granted,
         Revoked,
      }

      private static void ListAllPermissions()
      {
         using( var context = new EFTestContext() )
         {
            foreach( var permission in context.Permissions )
            {
               Console.WriteLine( $"{permission.UserId}: {permission.Name}" );
            }
         }
         Console.ReadLine();
      }

      private static IList<Permission> SetPermission( int userId, bool permissionIsGranted, string key )
      {
         var permissionStatus = permissionIsGranted ? PermissionStatus.Granted : PermissionStatus.Revoked;

         using( var entity = new EFTestContext() )
         {
            var user = entity.Users.Single( x => x.Id == userId );
            var existingPermission = user.Permissions.SingleOrDefault( x => x.Name == key );

            switch( permissionStatus )
            {
               case PermissionStatus.Granted:
                  if ( existingPermission == null )
                  {
                     user.Permissions.Add( new Permission { Name = key } );
                     entity.SaveChanges();
                  }
                  break;

               case PermissionStatus.Revoked:
                  if ( existingPermission != null )
                  {
                     entity.Permissions.Remove( existingPermission );
                     entity.SaveChanges();
                  }
                  break;
            }

            var permissions = entity.Permissions
                                    .Where( x => x.UserId == userId )
                                    .ToList();

            return permissions;
         }
      }
   }
}

Мой контекст для полноты:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;

namespace EFTest
{
   public class EFTestContext : DbContext
   {
      public virtual DbSet<User> Users { get; set; }
      public virtual DbSet<Permission> Permissions { get; set; }

      protected override void OnModelCreating( DbModelBuilder modelBuilder )
      {
         base.OnModelCreating( modelBuilder );
      }
   }

   public class User
   {
      [Key]
      public int Id { get; set; }
      public string Name { get; set; }

      public virtual ICollection<Permission> Permissions { get; set; }
   }

   public class Permission
   {
      [Key, Column( Order = 1 )]
      public string Name { get; set; }
      [Key, Column( Order = 2 ), ForeignKey( nameof( User ) )]
      public int UserId { get; set; }

      public virtual User User { get; set; }
   }
}

Другие вопросы по теме