Как стать автором
Обновить

Фотографируем объекты в C#: хроника и сопоставление снимков, реконструкция состояния по снимку

Время на прочтение 4 мин
Количество просмотров 6.1K
При разработке приложений часто встречается следующий сценарий: имеется некоторый набор данных доступных для просмотра и редактирования, например, это могут быть бизнес-сущности или настройки приложения. В момент, когда пользователь решает что-либо отредактировать, ему обычно становится доступна специальная форма с нужными полями ввода-вывода и другими элементами управления. Если он вносит какие-либо корректировки в данные, то при обработке формы хорошим тоном является запрос-подтверждение перед окончательным применением внесённых правок. В случае согласия пользователя данные обновляются в источнике и на интерфейсе, а при отмене используются старые значения.

Данная задача включает две подзадачи:

1) когда пользователь уходит с формы редактирования, необходимо понимать, действительно ли он произвёл изменения, чтобы не задавать вопрос на подтверждение впустую и не перезаписывать идентичные данные;

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

В статье мы рассмотрим обобщённый и очень лаконичный [размером в несколько строк кода!] подход к решению подобного рода задач, основанный на использовании библиотеки Replication Framework.

image


Рассмотрим пример приложения. Пусть дан список сущностей, среди которых пользователь может выбрать любую и нажать на кнопку редактирования [в режиме оригинала либо копии].



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



После подтверждения изменений выводится список всех найденных различий, если поднят флаг Show detailed changes, либо просто выводится сообщение об обнаружении хотя бы одного отличия [в реальных ситуациях иногда достаточно и такого поведения].



При отмене используются старые значения.



Теперь взглянем на код метода, который отвечает за данное поведение.

        private void Edit<T>(T sourceEntry, bool useCopy, bool showChanges, ReplicationProfile replicationProfile)
        {
            var cache = new ReconstructionCache();
            var sourceSnapshot = sourceEntry.CreateSnapshot(cache, replicationProfile);

            var editableEntry = useCopy ? sourceSnapshot.ReplicateGraph() : sourceEntry;
            if (GetView(editableEntry).ShowDialog() == true)
            {
                var resultSnapshot = editableEntry.CreateSnapshot(null, replicationProfile);
                var changes = sourceSnapshot.Juxtapose(resultSnapshot)
                    .Where(j => j.State != Etalon.State.Identical);

                if (changes.Any())
                {
                    MessageBox.Show(showChanges
                        ? changes.Aggregate("", (x, y) => x + y + Environment.NewLine)
                        : "Any changes has been detected!");
                    UpdateSourceData(editableEntry);
                    UpdateUserInterface();
                }
                else MessageBox.Show("There are no any changes.");
            }
            else if (!useCopy) sourceSnapshot.ReconstructGraph(cache);
        }

Сущность Person
    public class Person : INotifyPropertyChanged
    {
        private int _id;
        private string _name;
        private string _birthday;
        private string _phone;
        private string _mail;

        public event PropertyChangedEventHandler PropertyChanged = (o, e) => { };

        private void Set<T>(ref T target, T value, [CallerMemberName]string caller = "")
        {
            if (Equals(target, value)) return;
            target = value;
            PropertyChanged(this, new PropertyChangedEventArgs(caller));
        }

        public int Id
        {
            get => _id;
            set => Set(ref _id, value);
        }

        public string Name
        {
            get => _name;
            set => Set(ref _name, value);
        }

        public string Birthday
        {
            get => _birthday;
            set => Set(ref _birthday, value);
        }

        public string Phone
        {
            get => _phone;
            set => Set(ref _phone, value);
        }

        public string Mail
        {
            get => _mail;
            set => Set(ref _mail, value);
        }
    }


Профиль репликации для сущности Person
        private static readonly ReplicationProfile PersonRepicationProfile = new ReplicationProfile
        {
            MemberProviders = new List<MemberProvider>
            {
                new CoreMemberProviderForKeyValuePair(),
                new CoreMemberProvider(BindingFlags.Public | BindingFlags.Instance, Member.CanReadWrite),
            }
        };


Как видим, метод достаточно обобщённый и его можно испольовать для сущностей других типов.

Теперь обратим внимание на ключевые моменты. Работа библиотеки Replication Framework основана на использовании мгновенных снимков объектов в произвольные моменты времени, то есть с помощью метода-расширения Snapshot можно запросто сделать хронику мутаций [историю изменений] произвольного объекта или графа.

            var cache = new ReconstructionCache();
            var sourceSnapshot = sourceEntry.CreateSnapshot(cache, replicationProfile);
            ...
                var resultSnapshot = editableEntry.CreateSnapshot(null, replicationProfile);

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

                var changes = sourceSnapshot.Juxtapose(resultSnapshot)
                    .Where(j => j.State != Etalon.State.Identical);

С помощью вызова метода ReplicateGraph можно воссоздать новую копию графа идентичную той, что зафиксирована на снимке, а с помощью ReconstructGraph при наличии кэша репликации совершить реконструкцию графа, то есть вернуть старый экземпляр к прежнему состоянию.

            var editableEntry = useCopy ? sourceSnapshot.ReplicateGraph() : sourceEntry;

            var cache = new ReconstructionCache();
            var sourceSnapshot = sourceEntry.CreateSnapshot(cache, replicationProfile);
            ...
            else if (!useCopy) sourceSnapshot.ReconstructGraph(cache);

Более подробную информацию об использовании библиотеки вы можете найти в предыдущих публикациях:

1) Replication Framework • глубинное копирование и обобщённое сравнение связных графов объектов
2) Обобщённое копирование связных графов объектов в C# и нюансы их сериализации

Библиотека является бесплатной для некоммерческих и учебных проектов, а на Nuget доступна пробная версия, которая функциональна до конца лета. Для получения лицензионной версии с неограниченным сроком действия и доступа к исходным кодам необходимо отправить запрос на адрес makeman@tut.by.

За внешней простотой использования и хорошей функциональностью библиотеки кроется большая и кропотливая работа по её созданию и отладке, поэтому любая материальная поддержка и покупка коммерческой лицензии очень приветсвуются!

Вдохновения тебе, читатель!
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
+8
Комментарии 21
Комментарии Комментарии 21

Публикации

Истории

Работа

Ближайшие события

Московский туристический хакатон
Дата 23 марта – 7 апреля
Место
Москва Онлайн
Геймтон «DatsEdenSpace» от DatsTeam
Дата 5 – 6 апреля
Время 17:00 – 20:00
Место
Онлайн