Иерархические шаблоны данных в Silverlight

ОГЛАВЛЕНИЕ

Данная статья изучает некоторые свойства иерархических шаблонов данных с помощью пробного приложения "Пользователи и группы". В примере используются управляющие элементы DataGrid(сетка данных) и TreeView(список с древовидным отображением) Silverlight вместе с преобразователями значения.

•    Скачать исходный код - 332 Кб

Введение

Иерархические шаблоны данных являются мощным способом организации и привязки данных, имеющимся в основе представления Windows (WPF) и Silverlight. Прочитав данную статью и пример кода, вы получите глубокие знания о том, как работают иерархические шаблоны данных, как работает привязка данных в Silverlight, и о некоторых изобретательных способах их применения в приложениях, а также как вносить ряд ориентированных на производительность изменений в приложение, чтобы обеспечить отложенную загрузку больших наборов данных.

Подготовка

Есть много бизнес-требований, предполагающих отображение данных в иерархическом формате. Обычно конструкция пользовательского интерфейса интернета требовала управления иерархией посредством кода и рекурсивного выполнения итераций для построения дерева. Иерархические наборы данных отсутствовали.

Иерархический шаблон данных позволяет привязать данные, являющиеся иерархическими и автореферентными. Шаблон понимает, как рекурсивно обойти дерево, и обеспечивает более четкое разделение реализации данных и представления. Понимание принципа работы иерархического шаблона данных позволяет выгодно использовать преимущества производительности, путем загрузки данных в нужное время и только когда они нужны для отображения.

В данном примере берется типичная схема: пользователи в организационных группах. Группы формируют иерархию объектов "группы": например, Северная Америка может иметь группу для Восточного региона, и внутри нее группу для администраторов, и так далее. Пользователь, принадлежащий любой группе, автоматически принадлежит родителям группы, и родителям другой группы, и так далее.

Уникальность этой схемы заключается в том, что группа может иметь два типа потомков: другая группа или пользователь. Как можно представить два разных типа данных в иерархической структуре данных и выгодно использовать шаблон в приложении? Более того, что если человек, использующий приложение, хочет добраться до конкретной группы и изучить пользователь в ней, не дожидаясь загрузки всей организации? Представьте наличие 10.000 пользователей в системе и ожидание загрузки всех этих деталей в сравнении с возможностью просто загрузить пользователей, отображаемых в конкретном уровне иерархии.

Начало работы: домен и транспорты

При построении сервис-ориентированного кода предоставляются классы транспорта для сущностей домена. Сущность домена может быть сложным классом, содержащим несколько подклассов и большой граф объекта. Например, в принципе, можно представить наличие класса User с именем пользователя, адресом электронной почты, именем, фамилией, номером социального страхования, несколькими адресами, веб-адресами, личными блогами, логинами твиттера и многими другими точками данных, не нужных при представлении сводных данных. Отправка всей этой информации приложению Silverlight была бы растратой пропускной способности сети и памяти браузера.

Поэтому создаются объекты транспорта, содержащие более маленькие, "развернутые" версии объектов. Чтобы облегчить преобразование объекта домена в объект транспорта, предоставляется конструктор такого вида:

...
public UserTransport(UserEntity user)
{
    ID = user.ID;
    Username = user.Username;
}
...

Для упрощения примера не создаются классы и архитектура службы на стороне веб-приложения, зато создается "тестовая база данных" внутри приложения Silverlight. Это упрощает настройку и запуск. Были зашиты некоторые инструкции Debug, чтобы можно было увидеть, как вызываются определенные службы.
Чтобы узнать больше об абстрагировании вызовов служб, жмите сюда. По сути, будет имитироваться "объект-помощник", вызываемый для инициации вызова службы.

Транспорты

В примере есть два транспорта: группа и пользователь. Посмотрим на эти классы:

public class UserTransport
{
    public string Username { get; set; }
    public string Email { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }       
}

Видно, что UserTransport является простым легким классом, содержащим базовую информацию о пользователе. Более сложным классом является GroupTransport, содержащий информацию об иерархии группы. Класс выглядит так:

public class GroupTransport
{       

    public class GroupUserAddedEventArgs : EventArgs
    {
        public List<UserTransport> Users { get; set; }

        public GroupUserAddedEventArgs(List<UserTransport> users)
        {
            Users = users;
        }
    }

    public List<GroupTransport> Unroll()
    {
        List<GroupTransport> retVal = new List<GroupTransport> {this};
        foreach(GroupTransport child in Children)
        {
            retVal.AddRange(child.Unroll());
        }
        return retVal;
    }

    public string GroupName { get; set; }

    public event EventHandler<GroupUserAddedEventArgs> UsersAdded;

    private readonly List<GroupTransport> _groups = new List<GroupTransport>();

    public List<GroupTransport> Children
    {
        get { return _groups; }

        set
        {
            _groups.AddRange(value);
            foreach (GroupTransport group in value)
            {
                group.UsersAdded += _GroupUsersAdded;
            }
        }
    }

    private void _GroupUsersAdded(object sender, GroupUserAddedEventArgs e)
    {
        if (e != null && e.Users.Count > 0)
        {
            _users.AddRange(e.Users);
            if (UsersAdded != null)
            {
                UsersAdded(this, e);
            }
        }
    }

    private readonly List<UserTransport> _users = new List<UserTransport>();

    public void AddUsers(List<UserTransport> users)
    {
        _users.AddRange(users);
        if (UsersAdded != null)
        {
            UsersAdded(this, new GroupUserAddedEventArgs(users));
        }
    }

   public List<UserTransport> Users
    {
        get { return _users; }           
    }      
}

Важно отметить, что из-за пропуска стороны веб-приложения ("сервер" в модели клиент/сервер), в класс встроен некоторый бизнес-функционал, обычно существующий только на стороне сервера — Silverlight видел бы только свойства, а не методы для их заполнения.

Ключевыми свойствами являются имя группы (GroupName), пользователи, принадлежащие группе (Users), и подгруппы, принадлежащие группе (Children).

Остальные методы используются для заполнения иерархии. Событие добавления пользователя позволяет распространить до верха иерархии пользователя, добавленного ниже в иерархии. События развертывания выпрямляют иерархии в список, чтобы облегчить поиск группы в списке.