Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В предыдущем руководстве мы узнали, как применить кэширование на уровне презентации. В этом руководстве мы узнаем, как использовать нашу многоуровневую архитектуру для кэширования данных на уровне бизнес-логики. Это можно сделать, расширив архитектуру, чтобы включить слой кэширования.
Введение
Как мы видели в предыдущем руководстве, кэширование данных ObjectDataSource является простым, как задание нескольких свойств. К сожалению, ObjectDataSource применяет кэширование на уровне представления, что тесно связывает политики кэширования с ASP.NET страницей. Одной из причин создания многоуровневой архитектуры является разрешение разрыва таких связей. Например, уровень бизнес-логики отделяет бизнес-логику от страниц ASP.NET, а уровень доступа к данным отделяет сведения о доступе к данным. Это разделение бизнес-логики и сведений о доступе к данным предпочтительнее, в частности, поскольку это делает систему более удобочитаемой, более доступной и более гибкой для изменения. Кроме того, это позволяет учитывать знания о домене и разделение труда: разработчику, работающему на уровне представления, не нужно знать подробности базы данных, чтобы выполнять свою работу. Разобщение политики кэширования с уровня презентации дает аналогичные преимущества.
В этом руководстве мы дополним нашу архитектуру включением уровня кэширования (сокращенно CL), который использует нашу политику кэширования. Уровень кэширования будет включать ProductsCL
класс, предоставляющий доступ к сведениям о продукте с такими методами, как GetProducts()
, GetProductsByCategoryID(categoryID)
и т. д., что при вызове сначала попытается получить данные из кэша. Если кэш пуст, эти методы вызовут соответствующий ProductsBLL
метод в BLL, который, в свою очередь, получит данные из DAL. Методы ProductsCL
кэшируют данные, полученные из BLL перед возвратом.
Как показано на рисунке 1, среда CL находится между уровнями презентации и бизнес-логики.
Рис. 1. Уровень кэширования (CL) является еще одним уровнем в нашей архитектуре
Шаг 1. Создание классов слоев кэширования
В этом руководстве мы создадим очень простую среду CL с одним классом ProductsCL
, который содержит только несколько методов. Создание полного слоя кэширования для всего приложения потребует создания CategoriesCL
и EmployeesCL
SuppliersCL
классов и предоставления метода в этих классах уровня кэширования для каждого метода доступа к данным или изменения в BLL. Как и в случае с BLL и DAL, уровень кэширования должен быть в идеале реализован как отдельный проект библиотеки классов; однако мы реализуем его как класс в папке App_Code
.
Чтобы более четко разделить классы CL от классов DAL и BLL, давайте создадим новую вложенную папку в папке App_Code
. Щелкните правой кнопкой мыши App_Code
папку в обозревателе решений, выберите "Создать папку" и назовите новую папку CL
. После создания этой папки добавьте в него новый класс с именем ProductsCL.cs
.
Рис. 2. Добавление новой папки с именем CL
и классом ProductsCL.cs
Класс ProductsCL
должен включать тот же набор методов доступа к данным и изменений, что и в соответствующем классе уровня бизнес-логики (ProductsBLL
). Вместо создания всех этих методов давайте просто создадим пару здесь, чтобы получить представление о шаблонах, используемых средой CL. В частности, мы добавим методы GetProducts()
на шаге 3 и GetProductsByCategoryID(categoryID)
, а перегрузку метода UpdateProduct
на шаге 4. Вы можете добавить оставшиеся ProductsCL
методы и CategoriesCL
, EmployeesCL
, SuppliersCL
классы на досуге.
Шаг 2. Чтение и запись в кэш данных
Функция кэширования ObjectDataSource, рассмотренная в предыдущем руководстве, использует кэш данных ASP.NET для хранения данных, полученных из BLL. Кэш данных также можно получить программным способом из классов кода ASP.NET страниц или из классов в архитектуре веб-приложения. Для чтения и записи в кэш данных из класса кода страницы ASP.NET используйте следующий паттерн:
// Read from the cache
object value = Cache["key"];
// Add a new item to the cache
Cache["key"] = value;
Cache.Insert(key, value);
Cache.Insert(key, value, CacheDependency);
Cache.Insert(key, value, CacheDependency, DateTime, TimeSpan);
Метод Cache
классаInsert
имеет ряд перегрузок.
Cache["key"] = value
и Cache.Insert(key, value)
являются синонимами и добавляют элемент в кэш с помощью указанного ключа без определенного истечения срока действия. Как правило, при добавлении элемента в кэш мы хотим указать срок действия: либо как зависимость, либо в виде временного ограничения, либо обеими способами. Используйте одну из других перегрузок метода Insert
для предоставления сведений о сроке действия на основе зависимостей или времени.
Методы слоя кэширования должны сначала проверить, находятся ли запрошенные данные в кэше и, если да, вернуть их оттуда. Если запрошенные данные не в кэше, необходимо вызвать соответствующий метод BLL. Возвращаемое значение должно быть кэшировано, а затем возвращено, как показано на следующей схеме последовательности.
Рис. 3. Методы слоя кэширования возвращают данные из кэша, если он доступен
Последовательность, показанная на рис. 3, выполняется в классах CL с помощью следующего шаблона:
Type instance = Cache["key"] as Type;
if (instance == null)
{
instance = BllMethodToGetInstance();
Cache.Insert(key, instance, ...);
}
return instance;
Здесь тип данных, хранящихся в кэше Northwind.ProductsDataTable
, например ключом является ключ , который однозначно идентифицирует элемент кэша. Если элемент с указанным ключом не находится в кэше, экземплярnull
будет и данные будут получены из соответствующего метода BLL и добавлены в кэш. К моменту, когда достигнута return instance
, инстанция содержит ссылку на данные, либо из кэша, либо извлечённые из BLL.
Не забудьте использовать приведенный выше шаблон при доступе к данным из кэша. Следующий шаблон, который, на первый взгляд, выглядит эквивалентно, содержит тонкое различие, которое вызывает условие гонки. Условия гонки трудно выявлять ошибки, потому что они проявляются спорадически и трудно воспроизвести.
if (Cache["key"] == null)
{
Cache.Insert(key, BllMethodToGetInstance(), ...);
}
return Cache["key"];
Разница во втором, неправильном фрагменте кода заключается в том, что вместо хранения ссылки на кэшированный элемент в локальной переменной кэш данных осуществляется непосредственно в условной инструкции и в ней return
. Представьте, что при достижении Cache["key"]
он не является null
, но перед достижением инструкции return
система вытесняет ключ из кэша. В этом редком случае код вернет null
значение, а не объект ожидаемого типа.
Замечание
Кэш данных является потокобезопасной, поэтому не требуется синхронизировать доступ к потокам для простых операций чтения или записи. Однако если необходимо выполнить несколько операций с данными в кэше, которые должны быть атомарными, вы несете ответственность за реализацию блокировки или другого механизма, чтобы обеспечить безопасность потока. Дополнительные сведения см. в разделе "Синхронизация доступа к кэшу ASP.NET ".
Элемент можно программно вытеснить из кэша данных с помощью Remove
метода , как показано ниже.
Cache.Remove(key);
Шаг 3: Возвращаем сведения о продукте из классаProductsCL
В этом руководстве описано, как реализовать два метода возврата сведений о продукте ProductsCL
из класса: GetProducts()
и GetProductsByCategoryID(categoryID)
. Как и в классе ProductsBL
на уровне бизнес-логики, метод GetProducts()
в CL возвращает сведения обо всех продуктах в виде объекта Northwind.ProductsDataTable
, а GetProductsByCategoryID(categoryID)
возвращает все продукты из указанной категории.
В следующем коде показана часть методов в ProductsCL
классе:
[System.ComponentModel.DataObject]
public class ProductsCL
{
private ProductsBLL _productsAPI = null;
protected ProductsBLL API
{
get
{
if (_productsAPI == null)
_productsAPI = new ProductsBLL();
return _productsAPI;
}
}
[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
public Northwind.ProductsDataTable GetProducts()
{
const string rawKey = "Products";
// See if the item is in the cache
Northwind.ProductsDataTable products = _
GetCacheItem(rawKey) as Northwind.ProductsDataTable;
if (products == null)
{
// Item not found in cache - retrieve it and insert it into the cache
products = API.GetProducts();
AddCacheItem(rawKey, products);
}
return products;
}
[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Select, false)]
public Northwind.ProductsDataTable GetProductsByCategoryID(int categoryID)
{
if (categoryID < 0)
return GetProducts();
else
{
string rawKey = string.Concat("ProductsByCategory-", categoryID);
// See if the item is in the cache
Northwind.ProductsDataTable products = _
GetCacheItem(rawKey) as Northwind.ProductsDataTable;
if (products == null)
{
// Item not found in cache - retrieve it and insert it into the cache
products = API.GetProductsByCategoryID(categoryID);
AddCacheItem(rawKey, products);
}
return products;
}
}
}
Сначала обратите внимание на атрибуты DataObject
и DataObjectMethodAttribute
, применяемые к классу и методам. Эти атрибуты предоставляют сведения мастеру ObjectDataSource, указывая, какие классы и методы должны отображаться в шагах мастера. Так как классы и методы CL будут доступны из ObjectDataSource на уровне презентации, я добавил эти атрибуты, чтобы улучшить работу во время разработки. Вернитесь к пошаговому руководству по созданию уровня бизнес-логики, чтобы получить более подробное описание этих атрибутов и их влияние.
В методах GetProducts()
и GetProductsByCategoryID(categoryID)
данные, возвращаемые из метода GetCacheItem(key)
, присваиваются локальной переменной. Метод GetCacheItem(key)
, который мы рассмотрим вскоре, возвращает определенный элемент из кэша на основе указанного ключа. Если такие данные не найдены в кэше, он извлекается из соответствующего ProductsBLL
метода класса, а затем добавляется в кэш с помощью AddCacheItem(key, value)
метода.
Методы GetCacheItem(key)
и AddCacheItem(key, value)
взаимодействуют с кэшем данных, соответственно, читая и записывая значения. Этот GetCacheItem(key)
метод является более простым из двух. Он просто возвращает значение из класса Cache с помощью переданного ключа:
private object GetCacheItem(string rawKey)
{
return HttpRuntime.Cache[GetCacheKey(rawKey)];
}
private readonly string[] MasterCacheKeyArray = {"ProductsCache"};
private string GetCacheKey(string cacheKey)
{
return string.Concat(MasterCacheKeyArray[0], "-", cacheKey);
}
GetCacheItem(key)
не использует значение ключа , как указано, но вместо этого вызывает GetCacheKey(key)
метод, который возвращает ключ , подготовленный с помощью ProductsCache-. Объект MasterCacheKeyArray
, содержащий строку ProductsCache, также используется методом AddCacheItem(key, value)
, как мы увидим вскоре.
С класса программной части страницы ASP.NET можно получить доступ к кэшу данных, используя свойство Page
Cache
, и применять такой синтаксис, как Cache["key"] = value
, как описано на шаге 2. Из класса внутри архитектуры кэш данных можно получить с помощью любого HttpRuntime.Cache
или HttpContext.Current.Cache
. Запись блога Питера ДжонсонаHttpRuntime.Cache vs. HttpContext.Current.Cache отмечает небольшое преимущество производительности при использовании HttpRuntime
вместо HttpContext.Current
; следовательно, ProductsCL
использует HttpRuntime
.
Замечание
Если архитектура реализована с помощью проектов библиотеки классов, необходимо добавить ссылку на System.Web
сборку, чтобы использовать классы HttpRuntime и HttpContext .
Если элемент не найден в кэше, ProductsCL
методы класса получают данные из BLL и добавляют его в кэш с помощью AddCacheItem(key, value)
метода. Чтобы добавить значение в кэш, можно использовать следующий код, который использует срок действия 60 секунд:
const double CacheDuration = 60.0;
private void AddCacheItem(string rawKey, object value)
{
HttpRuntime.Cache.Insert(GetCacheKey(rawKey), value, null,
DateTime.Now.AddSeconds(CacheDuration), Caching.Cache.NoSlidingExpiration);
}
DateTime.Now.AddSeconds(CacheDuration)
указывает истечение срока действия по времени через 60 секунд, а System.Web.Caching.Cache.NoSlidingExpiration
означает, что отсутствует скользящий срок действия. Хотя эта Insert
перегрузка метода имеет входные параметры для абсолютной и скользящей даты истечения, вы можете указать только один из них. Если вы пытаетесь указать абсолютное время и интервал времени, Insert
метод вызовет ArgumentException
исключение.
Замечание
Эта реализация AddCacheItem(key, value)
метода в настоящее время имеет некоторые недостатки. Мы рассмотрим и преодолеем эти проблемы на шаге 4.
Шаг 4. Аннулирование кеша при изменении данных через архитектуру
Наряду с методами извлечения данных уровень кэширования должен предоставлять те же методы, что и BLL для вставки, обновления и удаления данных. Методы изменения данных CL не изменяют кэшированные данные, а вместо этого вызывают соответствующий метод изменения данных BLL, а затем инвалидация кэша. Как мы видели в предыдущем руководстве, это то же самое поведение, которое применяется объектом ObjectDataSource, когда его функции кэширования включены и вызываются его методы Insert
, Update
или Delete
.
Следующая UpdateProduct
перегрузка иллюстрирует реализацию методов изменения данных в среде CL:
[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, int productID)
{
bool result = API.UpdateProduct(productName, unitPrice, productID);
// TODO: Invalidate the cache
return result;
}
Вызывается соответствующий метод уровня бизнес-логики изменения данных, но перед возвратом ответа необходимо сделать кэш недействительным. К сожалению, очистка кэша не является простой задачей, так как класс ProductsCL
и методы GetProducts()
и GetProductsByCategoryID(categoryID)
добавляют элементы в кэш с разными ключами, а метод GetProductsByCategoryID(categoryID)
добавляет другой элемент кэша для каждого уникального идентификатора категории.
При недопустимом удалении кэша необходимо удалить все элементы, которые могли быть добавлены классом ProductsCL
. Это можно сделать, связав зависимость кэша с каждым элементом, добавленным в кэш в методе AddCacheItem(key, value)
. Как правило, зависимость кэша может быть другим элементом в кэше, файлом файловой системы или данными из базы данных Microsoft SQL Server. При изменении зависимостей или удалении из кэша элементы кэша, с которыми он связан, автоматически вытеснимы из кэша. В этом руководстве мы хотим создать дополнительный элемент в кэше, который служит зависимостью кэша для всех элементов, добавленных через ProductsCL
класс. Таким образом, все эти элементы можно удалить из кэша, просто удалив зависимость кэша.
Давайте обновим AddCacheItem(key, value)
метод таким образом, чтобы каждый элемент, добавленный в кэш с помощью этого метода, связан с одной зависимостью кэша:
private void AddCacheItem(string rawKey, object value)
{
System.Web.Caching.Cache DataCache = HttpRuntime.Cache;
// Make sure MasterCacheKeyArray[0] is in the cache - if not, add it
if (DataCache[MasterCacheKeyArray[0]] == null)
DataCache[MasterCacheKeyArray[0]] = DateTime.Now;
// Add a CacheDependency
System.Web.Caching.CacheDependency dependency =
new CacheDependency(null, MasterCacheKeyArray);
DataCache.Insert(GetCacheKey(rawKey), value, dependency,
DateTime.Now.AddSeconds(CacheDuration),
System.Web.Caching.Cache.NoSlidingExpiration);
}
MasterCacheKeyArray
— это строковый массив, содержащий одно значение ProductsCache. Во-первых, элемент кэша добавляется в кэш и назначается текущая дата и время. Если элемент кэша уже существует, он обновляется. Затем создается зависимость кэша. Конструктор CacheDependency
класса имеет ряд перегрузок, но тот, который используется здесь, ожидает два string
входных данных массива. Первый указывает набор файлов, используемых в качестве зависимостей. Так как мы не хотим использовать зависимости на основе файлов, значение null
используется для первого входного параметра. Второй входной параметр задает набор ключей кэша, используемых в качестве зависимостей. Здесь мы указываем одну зависимость. MasterCacheKeyArray
Затем CacheDependency
передается в метод Insert
.
При таком изменении AddCacheItem(key, value)
инвалидировать кэш так же просто, как удалить зависимость.
[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, int productID)
{
bool result = API.UpdateProduct(productName, unitPrice, productID);
// Invalidate the cache
InvalidateCache();
return result;
}
public void InvalidateCache()
{
// Remove the cache dependency
HttpRuntime.Cache.Remove(MasterCacheKeyArray[0]);
}
Шаг 5. Вызов слоя кэширования из слоя презентации
Классы и методы уровня кэширования можно использовать для работы с данными с помощью методов, которые мы изучили в этих руководствах. Чтобы проиллюстрировать работу с кэшируемыми данными, сохраните изменения в ProductsCL
классе, а затем откройте FromTheArchitecture.aspx
страницу в папке Caching
и добавьте GridView. В смарт-теге GridView создайте объект ObjectDataSource. На первом шаге мастера вы увидите ProductsCL
класс как один из вариантов из раскрывающегося списка.
Рис. 4.ProductsCL
Класс включен в список Drop-Down бизнес-объектов (щелкните, чтобы просмотреть изображение полного размера)
После выбора ProductsCL
нажмите кнопку "Далее". Раскрывающийся список на вкладке SELECT содержит два элемента - GetProducts()
, а GetProductsByCategoryID(categoryID)
, и вкладка UPDATE имеет единственную UpdateProduct
перегрузку.
GetProducts()
Выберите метод на вкладке SELECT и метод на вкладке UPDATE и UpdateProducts
нажмите кнопку "Готово".
Рис. 5.ProductsCL
Методы класса перечислены в списках Drop-Down (щелкните, чтобы просмотреть изображение полного размера)
После завершения мастера Visual Studio установит свойство OldValuesParameterFormatString
для ObjectDataSource original_{0}
и добавит соответствующие поля в GridView. Измените OldValuesParameterFormatString
свойство обратно на значение по умолчанию {0}
и настройте GridView для поддержки разбиения по страницам, сортировки и редактирования. Так как перегрузка UploadProducts
, используемая средой CL, принимает только измененное имя продукта и его цену, ограничьте GridView, чтобы редактируемыми были только эти поля.
В предыдущем руководстве мы определили GridView для включения полей для ProductName
, CategoryName
и UnitPrice
. Вы можете свободно использовать это форматирование и структуру, в этом случае декларативная разметка GridView и ObjectDataSource должна выглядеть примерно так:
<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ProductsDataSource"
AllowPaging="True" AllowSorting="True">
<Columns>
<asp:CommandField ShowEditButton="True" />
<asp:TemplateField HeaderText="Product" SortExpression="ProductName">
<EditItemTemplate>
<asp:TextBox ID="ProductName" runat="server"
Text='<%# Bind("ProductName") %>' />
<asp:RequiredFieldValidator ID="RequiredFieldValidator1"
ControlToValidate="ProductName" Display="Dynamic"
ErrorMessage="You must provide a name for the product."
SetFocusOnError="True"
runat="server">*</asp:RequiredFieldValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server"
Text='<%# Bind("ProductName") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField DataField="CategoryName" HeaderText="Category"
ReadOnly="True" SortExpression="CategoryName" />
<asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
<EditItemTemplate>
$<asp:TextBox ID="UnitPrice" runat="server" Columns="8"
Text='<%# Bind("UnitPrice", "{0:N2}") %>'></asp:TextBox>
<asp:CompareValidator ID="CompareValidator1" runat="server"
ControlToValidate="UnitPrice" Display="Dynamic"
ErrorMessage="You must enter a valid currency value with
no currency symbols. Also, the value must be greater than
or equal to zero."
Operator="GreaterThanEqual" SetFocusOnError="True"
Type="Currency" ValueToCompare="0">*</asp:CompareValidator>
</EditItemTemplate>
<ItemStyle HorizontalAlign="Right" />
<ItemTemplate>
<asp:Label ID="Label1" runat="server"
Text='<%# Bind("UnitPrice", "{0:c}") %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server"
OldValuesParameterFormatString="{0}" SelectMethod="GetProducts"
TypeName="ProductsCL" UpdateMethod="UpdateProduct">
<UpdateParameters>
<asp:Parameter Name="productName" Type="String" />
<asp:Parameter Name="unitPrice" Type="Decimal" />
<asp:Parameter Name="productID" Type="Int32" />
</UpdateParameters>
</asp:ObjectDataSource>
На этом этапе у нас есть страница, использующая слой кэширования. Чтобы просмотреть кэш в действии, задайте точки останова в классе ProductsCL
и методах GetProducts()
и UpdateProduct
. Перейдите на страницу в браузере и выполните шаги по коду при сортировке и разбиении по страницам, чтобы просмотреть данные, извлекаемые из кэша. Затем обновите запись и обратите внимание, что кэш недопустим и, следовательно, извлекается из BLL, когда данные возвращаются в GridView.
Замечание
Слой кэширования, предоставленный в загрузке, сопровождающей эту статью, не является завершенным. Он содержит только один класс, ProductsCL
который имеет только горстку методов. Кроме того, только одна страница ASP.NET использует CL (~/Caching/FromTheArchitecture.aspx
), все остальные по-прежнему ссылаются на BLL непосредственно. Если вы планируете использовать среду CL в приложении, все вызовы уровня презентации должны перейти в среду CL, которая потребует, чтобы классы и методы CL охватывали эти классы и методы в BLL, которые в настоящее время используются уровнем презентации.
Сводка
Хотя кэширование можно применять на уровне презентации, используя элементы управления SqlDataSource и ObjectDataSource в ASP.NET 2.0, в идеале функции кэширования должны быть делегированы отдельному уровню в архитектуре. В этом руководстве мы создали слой кэширования, который находится между уровнем презентации и уровнем бизнес-логики. Уровень кэширования должен предоставить тот же набор классов и методов, которые существуют в BLL и вызываются из уровня презентации.
Примеры слоя кэширования, которые мы изучили в этом и предыдущих руководствах, демонстрируют реактивную загрузку. При реактивной загрузке данные загружаются в кэш только когда на них поступает запрос и этих данных нет в кэше. Данные также можно заранее загрузить в кэш, метод, который загружает данные в кэш, прежде чем он действительно необходим. В следующем руководстве мы увидим пример упреждающей загрузки при просмотре способа хранения статических значений в кэш при запуске приложения.
Счастливое программирование!
Сведения о авторе
Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга — Sams Teach Yourself ASP.NET 2.0 за 24 часа. С ним можно связаться по адресу mitchell@4GuysFromRolla.com.
Особое спасибо кому
Эта серия учебников была проверена многими полезными рецензентами. Тереза Мерф была ведущим рецензентом для этого учебного пособия. Хотите просмотреть мои предстоящие статьи MSDN? Если да, напишите мне на mitchell@4GuysFromRolla.com.