Синхронизация объектов в сложном дереве объектов

#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() . Но если есть пользователи, которые не принадлежат ни к каким группам.. значение не может быть вычислено