#c# #oop #observer-pattern
#c# #ооп #наблюдатель-шаблон
Вопрос:
Допустим, у меня есть следующее дерево объектов. Мое дерево объектов на самом деле не такое глупое, но у него есть ряд отношений «многие ко многим»
Account
- Users {UserId, Name, etc}
- UserPermissions {User object, Permission object}
- Permissions {PermissionId, Name, etc}
Скажем, я хочу удалить пользователя или объект разрешений из-под объекта учетной записи. Я бы хотел, чтобы в любом сценарии любые объекты UserPermission, содержащие удаленный элемент, также удалялись из коллекции UserPermission.
Есть ли элегантный способ сделать это, не удаляя объекты UserPermission вручную? Я знаю, что объекты из моделей Entity Framework делают это, но не уверен, как я могу добиться тех же результатов… Я не использую EF, а вместо этого сохраняю структуры объектов в RavenDB.
По запросу, вот более полная модель данных
public class Account
{
public List<User> Users {get;set;}
public List<UserGroup> UserGroups {get;set;}
public List<Alert> Alerts {get;set;}
public List<Resource> Resources {get;set;}
public List<AlertSubscription> AlertSubscriptions {get;set;}
}
public class UserGroup : IAlertSubscriber
{
public string Name {get;set;}
public List<User> Users {get;set;}
}
public class User : IAlertSubscriber
{
public string Name {get;set;}
}
public class AlertSubscription
{
public Alert Alert {get;set;}
public List<Resources> AlertPublishers {get;set;}
public List<IAlertSubscribers> AlertTargets {get;set;}
public int MinimumSeverity {get;set;}
}
Итак, что я хотел бы иметь возможность сделать, так это удалить пользователя из учетной записи.Коллекция пользователей, чтобы также удалить ее из различных предупреждений, групп пользователей и т. Д. Если я удалю группу пользователей из учетной записи.Группы пользователей, я хочу удалить его из AlertTargets, если я удалю ресурс из учетной записи.Ресурсы, чтобы также отключить его от AlertSubsciptions…
Ответ №1:
Вместо того, чтобы пытаться синхронизировать две копии одних и тех же данных, я бы выбрал единственную копию. Вы можете либо иметь только список пользовательских разрешений, либо вычислять две другие коллекции:
public class Account
{
private List<UserPermission> _userPersmission;
public List<UserPermission> UserPermissions
{
get { return _userPersmission; }
set { _userPersmission = value; }
}
public IEnumerable<User> Users
{
get { return _userPersmission.Select(up => up.User); }
}
public IEnumerable<Permission> Permissions
{
get { return _userPersmission.Select(up => up.Permission); }
}
}
или вычислите разрешения пользователей на основе двух других коллекций. Но я бы предпочел предыдущий подход, поэтому разрешение пользователя может содержать дополнительные данные.
ОБНОВЛЕНИЕ: если вы не можете вычислить все коллекции из некоторых из них без дублирования данных, тогда вы возвращаетесь к Observer. Тема в вашем случае — это разные коллекции, например, пользователи. И субъекты должны уведомлять другие коллекции, если какой-то элемент удаляется. BindingList<T>
является субъектом, который реализует такое уведомление. К сожалению, он не предоставляет информацию о том, какой элемент был удален, поэтому вам нужно будет создать пользовательский список привязок:
public class ExtendedBindingList<T> : BindingList<T>
{
public T LastRemovedItem { get; private set; }
protected override void RemoveItem(int index)
{
LastRemovedItem = base[index];
base.RemoveItem(index);
}
}
Вы можете использовать это, чтобы получать уведомления об удалении элемента:
public class Account
{
public ExtendedBindingList<User> Users { get; private set; }
public Account()
{
Users = new ExtendedBindingList<User>();
Users.ListChanged = Users_ListChanged;
}
private void Users_ListChanged(object sender, ListChangedEventArgs e)
{
if (e.ListChangedType != ListChangedType.ItemDeleted)
return;
foreach (var group in UserGroups)
group.Users.Remove(Users.LastRemovedItem);
}
// ...
}
Другим вариантом является использование методов AddUser
и RemoveUser
для изменения списка пользователей. И отображение списка пользователей как IEnumerable
:
public class Account
{
private List<User> _users = new List<User>();
public IEnumerable<User> Users { get; }
public void AddUser(User use)
{
_users.Add(user);
}
public void RemoveUser(User user)
{
_users.Remove(user);
foreach (var group in UserGroups)
group.Users.Remove(user);
}
// ...
}
Комментарии:
1. это прерывается, как только возникает другое отношение «многие ко многим», которое включает либо пользователей, либо разрешения, не так ли?
2. @Igorek было бы неплохо увидеть образец, который отражает вашу реальную проблему
3. @Igorek с обновленной моделью вы все еще можете избежать дублирования данных — используйте группы пользователей и подписки на оповещения. Другие коллекции (пользователи, оповещения, ресурсы) могут быть рассчитаны на основе этих двух
4. Все еще не думаю, что это сработает. Пользователь не обязательно должен принадлежать к группе пользователей, или он может принадлежать более чем к одной группе пользователей.
5. @Igorek получение пользователей из групп просто:
UserGroups.SelectMany(ug => ug.Users).Distinct()
. Но если есть пользователи, которые не принадлежат ни к каким группам.. значение не может быть вычислено