- PVSM.RU - https://www.pvsm.ru -

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?

Автор: BloodJohn


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/net/17637