Кэширование

Кэширование с помощью MemoryCache

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 19.12.2019

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

Самым простым способом кэширования в ASP.NET Core предствляет использование объекта Microsoft.Extensions.Caching.Memory.IMemoryCache, который позволяет сохранять данные в кэше на сервере.

Для рассмотрения механизма кэширования возьмем какую-нибудь простенькую задачу. Допустим, нам надо кэшировать профиль пользователя или некоторую информацию о пользователе, которая может не изменяться в течение более долгого периода времени, и поэтому эту информацию мы можем кэшировать, чтобы в будущем избежать лишних обращений к бд.

Для решения этой задачи создадим проект ASP.NET Core по типу ASP.NET Core Web App (Model-View-Controller). Пусть он будет называться CachingMVC. Взаимодействовать с базой данных мы будем через Entity Framework.

MemoryCache в ASP.NET Core

Вначале добавим в проект в папку Models новый класс User, который будет описывать используемые данные:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public int Age { get; set; }
}

Для взаимодействия с MS SQL Server через Entity Framework добавим в проект через Nuget пакет Microsoft.EntityFrameworkCore.SqlServer. А затем добавим в папку Models класс контекста данных ApplicationContext:

using Microsoft.EntityFrameworkCore;

namespace CachingMVC.Models
{
    public class ApplicationContext : DbContext
    {
        public DbSet<User> Users { get; set; }
        public ApplicationContext(DbContextOptions<ApplicationContext> options)
            : base(options)
        {
            Database.EnsureCreated();
        }
    }
}

Для взаимодействия с контекстом и бд создадим специальный сервис. Для этого вначале добавим в проект папку Services и в ней определим новый класс UserService:

using CachingMVC.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace CachingMVC.Services
{
    public class UserService
    {
        private ApplicationContext db;
        private IMemoryCache cache;
        public UserService(ApplicationContext context, IMemoryCache memoryCache)
        {
            db = context;
            cache = memoryCache;
        }

        public void Initialize()
        {
            if (!db.Users.Any())
            {
                db.Users.AddRange(
                    new User { Name = "Tom", Email = "tom@gmail.com", Age = 35 },
                    new User { Name = "Alice", Email = "alice@yahoo.com", Age = 29 },
                    new User { Name = "Sam", Email = "sam@online.com", Age = 37 }
                );
                db.SaveChanges();
            }
        }

        public async Task<IEnumerable<User>> GetUsers()
        {
            return await db.Users.ToListAsync();
        }

        public async Task AddUser(User user)
        {
            db.Users.Add(user);
            int n = await db.SaveChangesAsync();
            if (n > 0)
            {
                cache.Set(user.Id, user, new MemoryCacheEntryOptions
                {
                    AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
                });
            }
        }

        public async Task<User> GetUser(int id)
        {
            User user = null;
            if (!cache.TryGetValue(id, out user))
            {
                user = await db.Users.FirstOrDefaultAsync(p => p.Id == id);
                if (user != null)
                {
                    cache.Set(user.Id, user,
                    new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(5)));
                }
            }
            return user;
        }
    }
}

Данный сервис через встроенный механизм внедрения зависимостей будет получать контекст данных и использовать его для взаимодействия с бд. Кроме того, данный класс реализует логику кэширования.

Через встроенный механизм внедрения зависимостей в конструкторе мы можем получить объект кэша IMemoryCache. Применяя методы интефейса IMemoryCache, мы можем управлять кэшем:

  • bool TryGetValue(object key, out object value): пытаемся получить элемент по ключу key. При успешном получении параметр value заполняется полученным элементом, а метод возвращает true

  • object Get(object key): дополнительный метод расширения, который получает по ключу key элемент и возвращает его

  • void Remove(object key): удаляет из кэша элемент по ключу key

  • object Set(object key, object value, MemoryCacheEntryOptions options): добавляет в кэш элемент с ключом key и значением value, применяя опции кэширования MemoryCacheEntryOptions

По сути встроенная реализация интерфейса IMemoryCache - класс MemoryCache, который используется по умолчанию, инкапсулирует все объекты кэша в виде словаря Dictionary.

Здесь же кэширование реализуется в двух случаях: при получение объекта по id из бд и при добавлении этого объекта.

При добавлении объект вначале добавляется в базу данных, и в случае удачного добавления, также добавляется в кэш:

public void AddUser(User user)
{
	db.Users.Add(user);
	int n = db.SaveChanges();
	if (n > 0)
	{
		cache.Set(user.Id, user, new MemoryCacheEntryOptions
		{
			AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
		});
	}
}

При сохранении объекта в кэше в качестве его ключа выступает значение свойства Id.

С помощью параметра AbsoluteExpirationRelativeToNow здесь устанавливатся время кэширования - 5 минут.

При получении объекта по id вначале пытаемся найти этот объект в кэше, и если там не оказалось, то извлеаем его и бд и затем добавляем в кэш.

public async Task<User> GetUser(int id)
{
	User user = null;
	if (!cache.TryGetValue(id, out user))
	{
		user = await db.Users.FirstOrDefaultAsync(p => p.Id == id);
		if (user != null)
		{
			cache.Set(user.Id, user,
				new MemoryCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromMinutes(5)));
		}
	}
	return user;
}

Если ключ в кэше был найден, то в объект user передается извлекаемое из кэша значение, а метод TryGetValue() возвращает true. Для установки времени кэширования здесь применяется альтернативный способ - метод SetAbsoluteExpiration, который в данном случае таже устанавливает 5 минут.

В итоге у нас получится следующая структура проекта:

Кэширование в ASP.NET Core

Затем нам надо зарегистрировать сервисы кэширования и entity framework и применить компонент middleware в классе Startup:

using CachingMVC.Models;
using CachingMVC.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

namespace CachingMVC
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            string connectionString = @"Server=(localdb)\MSSQLLocalDB;Database=cacheappdb;Trusted_Connection=True;";
            // внедрение зависимости Entity Framework
            services.AddDbContext<ApplicationContext>(options =>
                options.UseSqlServer(connectionString));
            // внедрение зависимости UserService
            services.AddTransient<UserService>();
            // добавление кэширования
            services.AddMemoryCache();

            services.AddControllersWithViews();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            app.UseDeveloperExceptionPage();

            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}

Прежде всего для добавления возможности кэширования нам надо добавить сервис в методе ConfigureServices():

services.AddMemoryCache()

По сути этот сервис устанавливает зависимость для IMemoryCache, создавая объект синглтон:

services.TryAdd(ServiceDescriptor.Singleton<IMemoryCache, MemoryCache>());

И в классе контроллера HomeController определим следующий код:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using CachingMVC.Models;
using CachingMVC.Services;

namespace CachingMVC.Controllers
{
    public class HomeController : Controller
    {
        UserService userService;
        public HomeController(UserService service)
        {
            userService = service;
            userService.Initialize();
        }
        public async Task<IActionResult> Index(int id)
        {
            User user = await userService.GetUser(id);
            if (user != null)
                return Content($"User: {user.Name}");
            return Content("User not found");
        }
    }
}

В итоге при первом обращении к приложению данные будут извлекаться из базы данных и сохраняться в кэш. При всех последующих обращениях в пределах времени кэширования (в данном случае в течение 5 минут) данные будут извлекаться из кэша:

Кэширование в ASP.NET Core
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850