суббота, 17 мая 2014 г.

Data Binding на Linq

Да-да, речь пойдет о том, как сделать биндинг данных целиком и полностью на Linq. Т. е. без строковых путей и прочей каши, являющейся источником целого потока ошибок, появляющихся в самых неожиданных местах.

Предисловие

Давным-давно, когда я только начинал постигать хитрости языка C# (а, так же, технологии .Net в целом), основной рабочей технологией была Windows Forms, WPF еще не было даже в проекте, а технология web была развита крайне слабо и, как следствие, применима в весьма узком круге задач. Нарисовав свою первую формочку, я, разумеется, задумался, как отобразить в нее свои данные. В случае, если форма readonly, задача решается просто - нужно сделать простые присвоения и все. Но, что если данные должны редактироваться и присваиваться назад, в область данных по триггерам Changed. Прямой подход (присвоить свойство, подписаться на его изменения, считать свойство в триггере) я откинул сразу, как слишком громоздкий, очень плохо читаемый и крайне не удобный для изменений.

Первые шаги. Что предлагают авторы?

Разумеется, первое, что я сделал - это стал изучать мануал и штудировать форумы. Из мануала от компании Микрософт я понял, что технология WinForms предоставляет очень мощный, удобный и универсальный механизм биндинга данных. Копая глубже и подкрепляя знания форумами и личными экспериментами, я понял, что почти все "странности" этой системы реально можно обойти. Еще более глубокое изучение показало, что все особенности и странности, кажущиеся на первый взгляд явными багами, на самом деле являются фичами. А все сложности и неудобства механизма оправданы теми или иными "высшими целями". Проникнувшись этими самыми "высшими целями", я, не долго думая, набросал себе простенький движок биндингов на чистом рефлекшине, реализовав минимально-необходимый для меня функционал и забыл про эту проблему на несколько лет.

Вдруг откуда не возьмись, появился WPF

Вместе с технологией WPF, Микрософт предложила нам новый механизм биндингов. В общем-то механизм не плохой - в этот раз авторы действительно учли "грабли" прошлого опыта. Так что, осваивая новую систему, я, в общем-то был доволен. И у меня даже не возникло желания написать очередную заглушку. Собственно говоря, у бинингов WPF я увидел только один существенный недостаток - они очень узко-специализированы. Т. е. их практически не возможно использовать за рамками WPF.

Формирование задачи

Еще несколько лет плотной работы как с WPF, так и с WinForm, развитие технологии Linq и глубокое изучение шаблонов проектирования, наконец-то сложились для меня в четкое понимание того, что я хочу видеть от биндингов. А именно:
  1. Строгая типизация и полная проверка на стадии компиляции
  2. Полностью независимая система (не завязанная ни на WinForm, ни на WPF)
  3. Простая, интуитивно понятная и удобная система.
Очевидно, что в рамках языка C#, первый пункт решается только с помощью Linq. Впрочем, меня это вполне устраивает, так как мне нравится Linq.

Решение задачи

Разумеется, этой статьи не было бы, если бы задача не была решена. Начнем с раздела "Скачать", т. к., не имея под рукой скачанной библиотеки, дальше читать будет не очень интересно.

Скачать

  • {Скачать библиотеку} - Тут лежит сама библиотека (исходники+бинарники)
  • {Скачать юнит-тесты} - А отсюда можно забрать юнит-тэсты, которые подойдут в качестве примеров использования библиотеки
Если вдруг файлы будут недоступны - пишите в комментарии. Стараюсь читать их регулярно.

Описание библиотеки

В общем-то, использовать библиотеку очень просто. Для примера приведу один из юнит-тестов:
var a = new DataA { PropA = 11, B = new DataB { PropB = 21 } };
var b1 = new DataB { PropB = 4, PropC = 3 };
var b2 = new DataB { PropB = 8, PropC = 6 };

var binder = new BindingContent<DataA>( a );
binder.Bind( _a => _a.PropA ).To( b1, _b => _b.PropC ).ReadSource();
binder.Bind( _a => _a.PropA ).To( b2, _b => _b.PropC ).ReadTarget();

AssertHelper.AllEqual( 6, b1.PropC, b2.PropC, a.PropA );

b1.PropC = 8;

AssertHelper.AllEqual( 8, b1.PropC, b2.PropC, a.PropA );

binder.Clear();

b1.PropC = 11;
b2.PropC = 12;

Assert.AreEqual( 11, b1.PropC );
Assert.AreEqual( 12, b2.PropC );
Assert.AreEqual( 8, a.PropA );
Первые три строчки кода создают экземпляры классов, специально подготовленных для теста (в них нет ничего хитрого/интересного - просто классы с парой-тройкой свойств и событиями, уведомляющими об изменениях этих свойств). Далее создается экземпляр класса BindingContent из библиотеки LinqBinding. В конструкторе ему передается класс-источник (тот, кто будет биндиться). Далее идет сам биндинг, с указанием откуда должны прочитаться данные при инициализации биндинга (ReadSource или ReadTarget). Далее, проверяем, что биндинг сработал правильно, изменяем значение одного из свойств и снова проверяем. И, в конце, разрываем наши соединения и убеждаемся, что свойства стали меняться независимо.

Комментариев нет:

Отправить комментарий