23 мая 2011 г.

Кэширование в ASP.NET Часть 3. Продвинутое создание зависимостей

В прошлых статьях мы познакомились с классом Cache, научились использовать его функции для помещения и удаления объектов в кэше. Сегодня мы поговорим о классе CacheDependency, напоминаю он связывает объект с определенными источниками данных и при выявлении изменений в последних, удаляет объект из кэша. В прошлой статье мы даже рассмотрели простой сценарий использования таких зависимостей, например связывание с файлом на диске. Однако класс CacheDependency является намного более гибким и расширяемым элементом инфраструктуры кэширования ASP.NET, поэтому пришло время познакомиться с ним подробнее.

Архитектура класса CacheDependency
В первых версиях ASP.NET данный класс представлял достаточно жалкое зрелище – функционал был ограничен, его нельзя было наследовать и, как следствие, расширять. Однако разработчики подсуетились и начиная с ASP.NET класс CacheDependency стал полноценным и расширяемым механизмом.
Стандартный класс CacheDependency позволяет настроить зависимости от файлов и папок на диске или от других объектов в кэше. Для более специализированных зависимостей существуют специальные расширения класса CacheDependency:
  1. AggregateCacheDependency – позволяет создать зависимость, которая комбинирует в себе произвольное количество других зависимостей;
  2. XmlDataCacheDependency – зависимость объекта в кэше от Xml данных в файле на диске;
  3. SqlCacheDependency – установка зависимостей от таблиц в базе данных;
  4. “Ваша собственная” – как говорилось выше класс 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 и применять эти зависимости для кэширования данных. Мы рассмотрим два способа кэширования:
  1. Кэширование данных, которые используются в SqlDataSource;
  2. Отслеживание изменений в результатах выполнения определенного запроса к базе данных.
Кэширование данных в SqlDataSource
Первым делом необходимо создать в файле конфигурации web.config строку подключения к необходимой базе данных. В примере ниже – строка соединения с базой данных 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>
Теперь необходимо создать объект SqlCacheDependency и соединить его со строкой подключения к базе данных Northwind, указанной выше. Это также можно седлать в файле конфигурации:
<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);
CacheItemRemovedCallback removeCallback = new CacheItemRemovedCallback(RemoveCustomersFromCache);
  Cache.Insert("NortwindCustomers", resultData, sqlDependency, Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration, CacheItemPriority.Normal, removeCallback); }   public void RemoveCustomersFromCache(string key, object value, CacheItemRemovedReason reason) {
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.

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

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