Active Record Pattern

от автора

Хочу рассказать о применении шаблона Active Record для C# на практике. Такой класс реализует извлечение и запись в базу данных. Бизнес логика выносится на следующие уровни абстракции, где с таким объектом можно работать уже как с обычной структурой.

Центральный случай, который я буду рассматривать для примера — это работа со справочником 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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *