В прошлых статьях мы познакомились с классом Cache, научились использовать его функции для помещения и удаления объектов в кэше. Сегодня мы поговорим о классе CacheDependency, напоминаю он связывает объект с определенными источниками данных и при выявлении изменений в последних, удаляет объект из кэша. В прошлой статье мы даже рассмотрели простой сценарий использования таких зависимостей, например связывание с файлом на диске. Однако класс CacheDependency является намного более гибким и расширяемым элементом инфраструктуры кэширования ASP.NET, поэтому пришло время познакомиться с ним подробнее.
Архитектура класса CacheDependency
В первых версиях ASP.NET данный класс представлял достаточно жалкое зрелище – функционал был ограничен, его нельзя было наследовать и, как следствие, расширять. Однако разработчики подсуетились и начиная с ASP.NET класс CacheDependency стал полноценным и расширяемым механизмом.
Стандартный класс CacheDependency позволяет настроить зависимости от файлов и папок на диске или от других объектов в кэше. Для более специализированных зависимостей существуют специальные расширения класса CacheDependency:
- AggregateCacheDependency – позволяет создать зависимость, которая комбинирует в себе произвольное количество других зависимостей;
- XmlDataCacheDependency – зависимость объекта в кэше от Xml данных в файле на диске;
- SqlCacheDependency – установка зависимостей от таблиц в базе данных;
- “Ваша собственная” – как говорилось выше класс CacheDependency является наследуемым объектом, поэтому разработчикам предоставляется возможность создать собственный специализированный класс зависимости.
Использование AggregateCacheDependency
Первой мы рассмотрим зависимость AggregateCacheDependency. Ее следует использовать в том случае, когда необходимо установить для объекта в кэше не единичную зависимость, а составную. Например для объекта в кэше можно установить зависимость от таблицы в базе данных и от файла на диске.
Рассмотрим следующий пример создания AggregateCacheDependency:public void PutObjectToCache(string key, MyClass value){string filename1 = "info.txt"; string filename2 = "error.log"; CacheDependency dependency1 = new CacheDependency(filename1); CacheDependency dependency2 = new CacheDependency(filename2); CacheDependency[] depenMas = new CacheDependency[] { dependency1, dependency2 }; AggregateCacheDependency dependencies = new AggregateCacheDependency(); dependencies.Add(depenMas); Cache.Insert(key, value, dependencies);}
В данном случае мы устанавливаем зависимость помещаемого в кэш объекта от двух файлов на диске. Мы создали два объекта CacheDependency и передали их в массив. Создали экземпляр класса AggregateCacheDependency и при помощи метода Add поместили туда созданный массив зависимостей. Массив должен содержать объекты типа CacheDependency, поэтому в него можно передать как объекты класса CacheDependency так и объекты его различных потомков(например SqlCacheDependency).
Создание SqlCacheDependency
Следующим мы рассмотрим процесс создания зависимостей для объектов в кэше от таблиц базы данных MS SQL Server. Стоит отметить, что данных механизм существенно эволюционировал по сравнению с первыми версиями ASP.NET, в которых для обеспечения таких зависимостей необходимо было использовать триггеры на уровне базы данных, что приводило к блокировкам на уровне базы данных в высоконагружаемых приложениях и, как следствие, наблюдалось существенная потеря производительности базы данных.
В версиях ASP.NET 2.0-4.0 разработчики постарались максимально упростить данный механизм. В результате мы получили более удобный программный интерфейс для работы с SqlCacheDependency. А также, начиная с SQL Server 2005, в ядро СУБД MS SQL внедрен эффективный механизм отслеживания изменений в таблицах баз данных, без применения триггеров и блокировок.
Итак, давайте посмотрим как можно создавать зависимости от таблиц SQL Server и применять эти зависимости для кэширования данных. Мы рассмотрим два способа кэширования:
- Кэширование данных, которые используются в SqlDataSource;
- Отслеживание изменений в результатах выполнения определенного запроса к базе данных.
Первым делом необходимо создать в файле конфигурации web.config строку подключения к необходимой базе данных. В примере ниже – строка соединения с базой данных Northwind:
Теперь необходимо создать объект SqlCacheDependency и соединить его со строкой подключения к базе данных Northwind, указанной выше. Это также можно седлать в файле конфигурации:<configuration> <connectionStrings> <add name="NorthwindConnectionString" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=Northwind; User ID=temp_login; Password=temp_login" providerName="System.Data.SqlClient" /> </connectionStrings> </configuration>
<system.web> <caching> <sqlCacheDependency enabled="true" pollTime="1000" > <databases> <add name="Northwind" connectionStringName="NorthwindConnetionString" pollTime="1000" /> </databases> </sqlCacheDependency> </caching> </system.web>
В примере создается объект SqlCacheDependency, в разделе <databases> находятся ссылки на базы данных, в которых будет проводится отслеживание изменений в рамках данной зависимости.
Свойство pollTime указывает частоту проверки базы данных на наличие изменений(в миллисекундах). Это свойство можно задавать как для всего объекта SqlCacheDependency, так и отдельно для каждой базы данных в разделе <databases>. Необходимо серьезно подумать каким будет оптимальный интервал проверки изменений для вашего проекта. В примере выше – это одна секунда. Однако данный показатель рассчитывается для каждого приложения индивидуально. Если разработчик установит слишком большой интервал – будет высокая вероятность того, что пользователи получат неактуальные данные, если установит слишком низкий интервал – база данных будет нагружена дополнительными запросами.
Теперь осталось только соединить созданную конфигурацию SqlCacheDependency с источником данных в приложение – SqlDataSource:<asp:SqlDataSource ID="SqlDataSource1" runat="server" EnableCaching="true" SelectCommand="SELECT * FROM Customers" ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>" SqlCacheDependency="Northwind:Customers">
</asp:SqlDataSource>
Обязательно необходимо включить кэширование у компонента SqlDataSource, установив свойство EnableCaching в значение true.
Свойство SqlCacheDependency устанавливает объект зависимости. В примере указано Northwind:Customers. Northwind – это название записи в разделе <databases> конфигурации элемента SqlCacheDependency в файле web.config, Customers – это название таблицы в базе данных Northwind, в которой будут отслеживаться изменения. Если необходимо указать несколько таблиц – следует записать их через запятую.
В этом примере мы использовали компонент SqlDataSource для демонстрации кэширования данных из таблиц SQL Server. Однако также возможно настроить похожее кэширование для ObjectDataSource.
Кэширование результатов запросов к базе данныхМы рассмотрели кэширование данных из баз данных для источников данных, теперь посмотрим как можно использовать объекты SqlCacheDependency для помещения данных в хранилище Cache. Рассмотрим следующий пример:
protected void Page_Load(object sender, EventArgs e) {SqlConnection conn = new SqlConnection( ConfigurationManager.ConnectionStrings ["NorthwindConfigurationString"].ConnectionString); SqlCommand cmd = new SqlCommand( "SELECT * FROM Customers WHERE Country='USA'", conn); SqlDataAdapter sqladapter = new SqlDataAdapter(cmd); DataTable resultData = new DataTable(); sqladapter.Fill(resultData);SqlCacheDependency sqlDependency = new SqlCacheDependency(cmd); Cache.Insert("NortwindCustomers", resultData, sqlDependency);}
Здесь будет установлена зависимость от результатов конкретного запроса к таблице базы данных(объект cmd). Мы извлекаем результаты данного запроса в виде таблицы(resultData) и помещаем из в коллекцию Cache с ключом “NortwindCustomers”. Третьим параметром указывается объект SqlCacheDependency, который мы создали, передав в конструктор объект cmd.
Теперь таблица данных будет находится в кэше пока не изменятся результаты заданного запроса. Как только в таблицу Customers будут добавлены или удалены строки, у которых столбец Country равен “USA“, данные в кэше будут отмечены как устаревшие и будут удалены.
Допустим, мы хотим чтобы данные в кэше не удалялись, а обновлялись при изменении таблицы в базе данных. Чтобы это обеспечить, можно воспользоваться функцией обратного вызова. Напишем следующий код:
public partial class WebForm : System.Web.UI.Page {protected void Page_Load(object sender, EventArgs e) { PushUSACustomersToCache(); } public void PushUSACustomersToCache() {SqlConnection conn = new SqlConnection( ConfigurationManager.ConnectionStrings ["NorthwindConfigurationString"].ConnectionString); SqlCommand cmd = new SqlCommand( "SELECT * FROM Customers WHERE Country='USA'", conn); SqlDataAdapter sqladapter = new SqlDataAdapter(cmd); DataTable resultData = new DataTable(); sqladapter.Fill(resultData);SqlCacheDependency sqlDependency= new SqlCacheDependency(cmd);Cache.Insert("NortwindCustomers", resultData, sqlDependency, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, removeCallback); } public void RemoveCustomersFromCache(string key, object value, CacheItemRemovedReason reason) {CacheItemRemovedCallback removeCallback = new CacheItemRemovedCallback(RemoveCustomersFromCache);}if (reason == CacheItemRemovedReason.DependencyChanged) { PushUSACustomersToCache(); } }
В функции PushUSACustomersToCache извлекаются данные из таблицы базы данных(создание sql зависимости точно такое же как в прошлом примере) и объявляется объект делегата CacheItemRemovedCallback, который указывает на функцию RemoveCustomersFromCache. Теперь при каждом критическом изменении в базе данных, когда наши данные будут удаляться их кэша – будет вызываться функция RemoveCustomersFromCache. В ней проверяется причина удаления из кэша, которую передал механизм ASP.NET и, если это - изменение зависимостей, то мы снова помещаем последние данные из базы в кэш. Таким образом мы обеспечили постоянное нахождение в кэше актуальных данных из базы.
Кэширование данных при помощи XmlDataCacheDependency
Как понятно из названия данная создает зависимость данных в кэше от данных в xml документе. Мы конечно можем установить зависимость с самим файлов, содержащим данные xml, воспользовавшись стандартным объектом CacheDependency. Однако в этом случае данные в кэше будут удалятся при любом изменении в любом месте данного файла. Если же необходимо удалять данные из кэша только при изменениях в определенном узле документа xml – следует воспользоваться специализированным классом XmlDataCacheDependency.
Данный класс использует выражение Xpath для обнаружения поддерева в документе xml. После этого он с определенным интервалом извлекает данные из документа xml и сравнивает их со значениями, которые находятся в кэше. В случае несовпадения – данные к кэше отмечаются как устаревшие и удаляются.
Рассмотрим следующий пример. Допустим, у нас есть следующий xml документ:
<Data> <Customers> ... <Customer> <id>150</id> <email>abc@hotmail.com</email> <firstname>Morgan</firstname> <lastname>Sparlok</lastname> <balance>200</balance> </Customer> </Customers> </Data>
Допустим, мы хотим чтобы данные оставались в кэше, пока у кастомера с id = 150 не изменяется баланс счета. Напишем Xpath выражение которое будет отображать узел <balance> объекта <customer> с <id> равным 150:
string xpath = "/Data/Customers/Customer[id=150]/balance";Теперь осталось только поместить данные в кэш:
protected void Page_Load(object sender, EventArgs e) { XmlDataCacheDependency xmlDependency = new XmlDataCacheDependency("customers.xml", xpath, 1); Cache.Insert("my_key",xmlDependency.CurrentValue, xmlDependency); }
Выводы
В этой статье мы подробно рассмотрели класс CacheDependency, изучили его потомков и научились создавать разнообразные зависимости для записей в кэше, такие как AggregateCacheDependency, XmlDataCacheDependency и SqlCacheDependency.