Центральный случай, который я буду рассматривать для примера — это работа со справочником Country из базы данных, который часто читается, но очень редко меняется.
Использование active record объекта в коде бизнес логики выглядит вот так:
Country russia = Country.All[“Russia”];
Country не имеет публичного конструктора, и получение объектов возможно только через обращение к методу Dictionary<string, Country> All.
Теперь поподробнее о том, как устроен этот класс изнутри.
Конструктор записи
public readonly string name; private Country(IDataRecord record) { name = record.GetString(0); }
Благодаря приватности конструктора мы можем надеяться на корректное применение конструктора. И только внутри класса.
Например вот так:
private static Dictionary<string, Country> _all = null; private static Dictionary<string, Country> LoadDictionary() { _all = new Dictionary<string, Country>(); IDataReader reader = DBWrapper.GetReader(sqlSelect); try { while (reader.Read()) { Country item = new Country(reader); _all.Add(item.name, item); } } finally { reader.Close(); } return _all; }
sqlSelect – приватная константа, для чтения всех записей с нужным для конструктора порядком полей.
DBWrapper – самописный класс, инкапсулирующий работу с базой данных. Благодаря ему на этом уровне нам приходится работать только с интерфейсами, без указания конкретной реализации.
Add – добавление новой записи в общий реестр, скрыта для лаконичности кода в статье.
Словарь All
Тут тоже ничего сложного:
public static Dictionary<string, Country> All { get { return _all ?? LoadDictionary(); }}
В итоге мы имеем отложенную загрузку справочника по первому обращению.
Приватная переменная _all использует только в этом куске кода. К сожалению C# не позволяет ограничить ее применение меньше чем на весь класс. Остается опасность ее применения например в публичных методах. Что станет настоящей проблемой при работе в нескольких потоках.
Это первый вопрос, который я хочу обсудить: как сильнее ограничить видимость переменной _all?
Синхронизация многопоточности
Такой способ отложенной загрузки пока не пригоден для работы в многопоточности, поэтому я добавил класс LoadStatus.
private static readonly LoadStatus statusCountryList = new LoadStatus(“country”);
Название для статуса нужно для его идентификации в списке статусов всех справочников. Но об этом позже.
private static Dictionary<string, Country> _all = null; public static Dictionary<string, Country> All { get { if ( !statusCountryList.IsCompleted ) { lock (statusCountryList) { if ( !statusCountryList.IsCompleted ) { statusCountryList.Start(); _all = new Dictionary<string, Country>(); IDataReader reader= DBWrapper.GetReader(sqlSelect); try { while (reader.Read()) Add(new Country(reader)) statusCountryList.Finish(_all.Count); } catch Exception ex { statusCountryList.Error(ex); } finally { reader.Close(); } }}} return _all; }}
Много макарон, зато теперь у нас есть многопоточность и отчет о здоровье нашего справочника, в качестве бонуса от архитектуры.
LoadStatus прячет в себе синхронизацию и сбор данных о здоровье справочника.
Кроме того, через обнуление LoadStatus появляется возможность перегрузить справочник на лету.
Именно ради этой возможности я отказался от readonly для _all.
Class Generic
Решение получилось настолько удобным и изящным, что я использую его в десятках справочников в всех проектах. И возникает огромное желания превратить этот код в generic class.
Однако синтаксис C# не позволяет этого сделать.
Что вы думаете о этом решении?
Какие могут быть способы для превращения этого решения в generic?
ссылка на оригинал статьи http://habrahabr.ru/post/155545/
Добавить комментарий