Я волнуюсь с 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) удаляет его у каждого пользователя в базе данных.
Я не понимаю такого поведения. Кто-нибудь из вас?
Спасибо
Обновлено: Таблица пользователя
Таблица разрешений до:
Таблица разрешений после удаления с помощью userId 15 и ключа CONTRIBUTIONMARGINCUSTOMER
Изменить 2: РЕШЕНИЕ
Я изменил таблицу разрешений, чтобы в качестве первичного ключа использовался только один столбец. Это означает, что в новой таблице разрешений есть три столбца: Id (автоинкремент), Name и UserId с уникальным ключом для имени и идентификатором пользователя.
Приведенный выше код теперь работает, но меня это не очень устраивает.
я отредактировал свой пост
Я пытаюсь воспроизвести вашу исходную проблему, потому что это вызывает у меня любопытство. Дай мне несколько минут, хорошо? ;)
Кажется, вы делаете что-то еще не так. Фактически, вы никогда не показываете нам, как вы называете SetPermission(). Я предполагаю, что вы вызываете это слишком часто, то есть для всех пользователей. Я реализовал это, и все работает как надо, удаление разрешения удаляет его только для этого одного пользователя. Не могли бы вы показать, как вы звоните на SetPermissions()?
Я обновил пост. SetPermission() вызывается только один раз. Отладил.
Спасибо. Однако мне все еще недостаточно проверить свою гипотезу. Но я настоятельно рекомендую вам добавить в метод SetPermissions() некоторые отладочные данные, которые показывают, когда и с какими аргументами он выполняется. Потому что, когда я выполняю его один раз, для конкретного пользователя удаляется только одно разрешение. Значит, вы должны делать что-то по-другому, чтобы это вызывалось слишком часто.
Когда я изменил базу данных, как упоминалось выше, код заработал. Ничего не менял. Это определенно не вызывается дважды.
Позвольте нам продолжить обсуждение в чате.





Во втором случае вы удаляете свое разрешение не у пользователя, а у объекта (который по сути является всем вашим списком пользователей). Изменить на
case PermissionStatus.Revoked:
if (existingPermission != null)
{
user.Permissions.Remove(existingPermission);
entity.SaveChanges();
}
break;
Да, была такая же мысль. Но происходит следующее: System.InvalidOperationException: «Операция завершилась неудачно: связь не может быть изменена, поскольку одно или несколько свойств внешнего ключа не допускают значения NULL. При изменении отношения для соответствующего свойства внешнего ключа устанавливается значение NULL. Если внешний ключ не поддерживает нулевые значения, должна быть определена новая связь, свойству внешнего ключа должно быть присвоено другое ненулевое значение или несвязанный объект должен быть удален.
@ThomasKlammer Вероятно, потому, что таким образом связь удаляется, а сущность existingPermission - нет. А поскольку его первичный ключ содержит user.id, это не сработает.
ОК. но, на мой взгляд, этот ответ неверен. Если я удалю конкретное разрешение из коллекции разрешений, оно не должно удалять несколько разрешений.
Установите состояние разрешения на Удалено.
var existingPermission = user.Permissions.SingleOrDefault(x => x.Name == key);
//your other code here.
entity.Entry(existingPermission).State = EntityState.Removed;
entity.SaveChanges();
такое же поведение!
Могу я увидеть содержимое вашей таблицы разрешений?
находится в исходном посте
Мне кажется, что ваша база данных недостаточно нормализована. 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();
}
это должно работать. но я не хочу хранить все доступные разрешения в базе данных. О них должен знать только код.
Это потому, что вы не хотите, чтобы другие приложения считывали ваши разрешения? Почему бы не обеспечить безопасный доступ к вашей базе данных, чтобы только те, кто знает пароль, могли получить к ней доступ?
Я изо всех сил пытался воспроизвести проблему, но не смог.
Я твердо верю, что причина поведения, о котором вы сообщаете, заключается в том, что 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; }
}
}
Не могли бы вы указать, что именно вы имеете в виду, говоря «удаляет его у каждого пользователя в базе данных»? Я интерпретирую это как «одно и то же разрешение удалено у всех пользователей», но это не должно быть возможным. Можете ли вы в качестве примера показать нам содержимое обеих «таблиц» до и после удаления? Пожалуйста, используйте как минимум два разных типа разрешений.