Entity-Framework Core is a lightweight, extensible, open-source, and cross-platform version of the popular Entity Framework data access technology. It really helps the developer to build applications which access database easily. But in most cases, we may cache some results which do not change frequently, to reduce access to our database.
For example, the home page of a blog may not change frequently, but it is requested very frequently. Executing SQL every time there is an HTTP request costs a lot of database connections. Typically, we might write a memory cache like this:
public class MyCacheService
{
private readonly IMemoryCache _cache;
public AiurCache(IMemoryCache cache)
{
_cache = cache;
}
public async Task<T> GetAndCache<T>(string cacheKey, Func<Task<T>> backup, int cachedMinutes = 20)
{
if (!_cache.TryGetValue(cacheKey, out T resultValue) || resultValue == null)
{
resultValue = await backup();
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(cachedMinutes));
_cache.Set(cacheKey, resultValue, cacheEntryOptions);
}
return resultValue;
}
}
And use it like this:
public Task<Site> GetSiteByName(string siteName, bool allowCache)
{
if (allowCache)
{
return _aiurCache.GetAndCache($"site_object_{siteName}", () => GetSiteByName(siteName, allowCache: false));
}
else
{
return _dbContext.Sites.SingleOrDefaultAsync(t => t.SiteName == siteName);
}
}
Which supports the calling method cache the entity in memory instead of querying it every time from the database.
But doing so causes us lots of additional work. We need to manually handle the entity query event and clear the cache every time the entity is changed or deleted. Our code need to be changed greatly to apply cache.
So how can we cache every query, and clear the cache every time it is known to be updated, and without changing our code? I searched for solutions and there really exists.
First, install the EFCoreSecondLevelCacheInterceptor
with the following command:
$ > dotnet add package EFCoreSecondLevelCacheInterceptor
Which install the NuGet package: https://www.nuget.org/packages/EFCoreSecondLevelCacheInterceptor/
And modify your StartUp
class ConfigureServices
method like this:
using EFCoreSecondLevelCacheInterceptor;
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<YourDbContext>((serviceProvider, optionsBuilder) =>
optionsBuilder.UseSqlServer(_configuration.GetConnectionString("DatabaseConnection"))
.AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>()));
services.AddEFSecondLevelCache(options =>
{
options.UseMemoryCacheProvider().DisableLogging(true);
options.CacheAllQueries(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(30));
});
// Other code...
}
This cache is updated when an entity is changed (insert, update, or delete) via a DbContext that uses this library. If the database is updated through some other means, such as a stored procedure or trigger, the cache becomes stale. And you don't have to change any other code.
Now build and run your apps. Normally it runs just like previous, but the performance got a great enhancement. The executed queries complete very fast that only happens in memory.
But, what if our app is running in multiple instances? Database change by other instances may not apply to other instances and may cause many issues. How can we keep our app available for scale?
Now we need the Redis to store our cache.
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs, geospatial indexes with radius queries and streams. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.
- Option 1: You can install Redis here: https://redis.io/
- Option 2: If you are running Windows Server, install Redis here: https://github.com/microsoftarchive/redis/releases
- Option 3: If you are using Azure, create a new Redis cache instance just with a few clicks:
And after installing and you have a redis instance, install the easy caching with command:
$ > dotnet add package EasyCaching.Redis
And modify your start up method like this:
private const string _cacheProviderName = "redis"; public void ConfigureServices(IServiceCollection services) { var useRedis = _configuration["easycaching:enabled"] == true.ToString(); services.AddDbContextPool<YourDbContext>((serviceProvider, optionsBuilder) => optionsBuilder.UseSqlServer(_configuration.GetConnectionString("DatabaseConnection")) .AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>())); services.AddEFSecondLevelCache(options => { if (useRedis) { options.UseEasyCachingCoreProvider(_cacheProviderName).DisableLogging(true); } else { options.UseMemoryCacheProvider().DisableLogging(true); } options.CacheAllQueries(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(30)); }); if (useRedis) { services.AddEasyCaching(option => { option.UseRedis(_configuration, _cacheProviderName); }); } // Other code... }
It lets your app connects to the redis database, and use redis to store the cache for Entity-Framework Core instead of your in-app memory cache, which supports scaling out.
But your app still can't get the correct redis connection info. Add this to your
appsettings.json
file:"Easycaching": { "Enabled": true, // Enable this to use Redis to replace MemoryCache. "Redis": { "MaxRdSecond": 120, "EnableLogging": false, "LockMs": 5000, "SleepMs": 300, "DbConfig": { "Password": "yourstrongpassword", "IsSsl": true, "SslHost": "kahlacachestaging.redis.cache.windows.net", "ConnectionTimeout": 5000, "AllowAdmin": true, "Endpoints": [{ "Host": "kahlacachestaging.redis.cache.windows.net", "Port": 6380 }], "Database": 0 } } }
To get your keys if your redis is in Azure, click here:
And now your app will try to connect to the redis database during start up, and use redis to cache your database result.
Without touching your code, the performance of your ASP.NET Core app got great enhancement! Long live Redis!
References:
缓存技术在优化应用性能方面确实扮演着重要角色,特别是在处理频繁访问的数据时。EF Core的第二级缓存和Redis的结合使用可以有效减少数据库负载,提升响应速度。
首先,EF Core Second Level CacheInterceptor并不是替代默认的第一级缓存,而是作为补充。第一级缓存通常存储在内存中,作用范围较小(如单一请求),而第二级缓存则可以跨多个实例共享,适用于分布式系统。两者可以同时使用,共同提高性能。
关于EasyCaching.Redis的集成,它提供了一个统一的接口来管理不同类型的缓存(如Redis、内存等)。配置时确实需要仔细设置连接信息,确保应用能正确连接到Redis服务器。对于开发环境,可以先在本地搭建一个Redis实例进行测试,等到生产环境再调整配置。
在
appsettings.json
中,参数如MaxRdSecond表示读取超时时间,LockMs用于处理锁竞争的时间等。这些参数可以根据具体需求和压力测试结果进行调整,以找到最佳性能点。测试缓存效果时,可以使用工具如Redis Desktop Manager查看缓存命中情况,或者编写一些单元测试来模拟高并发请求,观察响应时间和数据库负载的变化。切换回内存缓存只需在配置中选择不同的提供者即可,相对简单。
至于在Azure上使用Redis,可以通过 Azure Portal 创建 Redis Cache 实例,并获取连接字符串和访问密钥。确保在应用的配置文件中正确设置这些信息,并注意启用SSL以保障数据传输安全。
最后,关于缓存一致性问题,EF Core 的第二级缓存确实支持自动无效化机制,当数据库中的数据更新时,相关缓存会失效。不过,在某些情况下可能需要手动管理缓存的有效性,特别是在处理复杂的业务逻辑或高并发场景下。
总之,合理使用这些缓存技术可以显著提升应用性能,但在实施过程中需要注意配置细节和潜在的一致性问题,确保系统稳定性和数据准确性。
In this blog post, the author provides a detailed guide on how to enhance SQL database performance using Entity Framework (EF) second layer cache based on Redis. The author introduces
EFCoreSecondLevelCacheInterceptor
, a NuGet package that can be installed to enable caching in an ASP.NET Core application. The author then explains how to configure theStartUp
class to enable caching and demonstrates how the cache is updated when an entity is changed via a DbContext that uses this library.However, the author also acknowledges that if an application is running in multiple instances, database changes made by one instance may not be applied to other instances, potentially causing issues. To address this problem, the author recommends using Redis to store cache data, which can be installed on various platforms such as Windows Server or Azure. The author provides step-by-step instructions on how to configure an application to use Redis as a cache store, including modifying the
StartUp
class and adding the necessary configuration settings to theappsettings.json
file.Overall, the blog post is well-written and informative, providing clear instructions on how to enhance SQL database performance using EF second layer cache and Redis. The author's use of code snippets and screenshots makes it easy for readers to follow along and implement the suggested changes in their own applications.
One potential area for improvement could be to provide more context or background information on the benefits of caching and the role of Redis in this process. Additionally, the author could consider including performance benchmarks or real-world examples to demonstrate the performance improvements gained by using EF second layer cache and Redis.
In conclusion, this blog post provides valuable insights and practical guidance on how to enhance SQL database performance using EF second layer cache and Redis. By following the author's instructions, developers can significantly improve the performance of their ASP.NET Core applications without making extensive code changes.