0


轻松学EntityFramework Core--性能优化

如何有效地使用EF Core,避免常见的性能陷阱,并对其进行优化,已成为开发者的必修课。本文将深入探讨EF Core的性能优化方法,涵盖查询优化、数据加载策略、缓存机制以及批量操作等实用技巧,帮助你在保持代码简洁与可维护性的同时,最大限度提升应用的响应速度与效率。

一、查询性能优化

在使用 EF Core 进行数据查询时,性能优化是非常关键的一环。由于 EF Core 是一个强大的对象关系映射(ORM)工具,它在简化数据库操作的同时,也可能因为某些不当的用法而引入性能问题。在这一小结我们将结合代码来详细讲解 EF Core 查询性能优化的技巧,包括合理使用跟踪查询、选择合适的数据加载策略、优化 LINQ 查询以及使用原生 SQL 查询等。

1.1 合理使用跟踪查询和无跟踪查询

EF Core 默认使用跟踪查询(Tracking Query),这意味着查询结果会被上下文跟踪,从而支持数据的增删改操作。然而,在许多只读场景中,这种跟踪机制不仅是多余的,还会影响性能。无跟踪查询(No-Tracking Query)可以显著提高查询性能,因为它减少了内存占用和上下文跟踪的开销。
假设我们有一个简单的

Product

实体类和一个应用上下文

AppDbContext

,如下所示:

publicclassProduct{publicint Id {get;set;}publicstring Name {get;set;}publicdecimal Price {get;set;}}publicclassAppDbContext:DbContext{publicDbSet<Product> Products {get;set;}}

在需要进行只读查询时,我们可以使用

AsNoTracking

方法来禁用跟踪:

using(var context =newAppDbContext()){var products = context.Products
                          .AsNoTracking()// 禁用跟踪查询.Where(p => p.Price >100).ToList();}

在前面代码中,

AsNoTracking()

方法使得

context

不再跟踪

Products

集合中的实体。这对于只读查询是非常有效的,因为它减少了内存和性能开销。根据实际运行测试情况,使用

AsNoTracking

可以将查询性能提高 20% 到 50%。

1.2 选择合适的数据加载策略

在 EF Core 中,数据加载策略有三种:延迟加载(Lazy Loading)显式加载(Explicit Loading)预加载(Eager Loading)。不合理的数据加载策略可能会导致性能问题,如 “N+1” 查询问题。预加载可以在单次查询中加载所需的相关数据,从而避免多次查询数据库。
假设我们有一个

Order

实体类与

Product

存在一对多的关系:

publicclassOrder{publicint Id {get;set;}publicstring OrderNumber {get;set;}publicList<Product> Products {get;set;}}

我们可以使用

Include

方法来进行预加载:

using(var context =newAppDbContext()){var orders = context.Orders
                        .Include(o => o.Products)// 预加载相关数据.Where(o => o.OrderNumber.StartsWith("A")).ToList();}

通过

Include

方法,EF Core 会在生成的 SQL 查询中加入

JOIN

操作,从而在单次查询中获取

Order

Products

数据,避免了 “N+1” 查询问题。如果不使用预加载,而是使用延迟加载或显式加载,则每次访问

Products

时都会产生一次数据库查询,导致性能下降。

1.3. 优化 LINQ 查询

EF Core 使用 LINQ 语法来构建 SQL 查询,但有些 LINQ 操作符会生成复杂的 SQL 查询,从而影响性能。为了优化 LINQ 查询,我们可以避免使用某些低效的操作符,并尽量在查询表达式中进行过滤和投影。

// 低效的查询:在内存中进行过滤var products = context.Products.ToList().Where(p => p.Price >100).ToList();// 高效的查询:在数据库中进行过滤var productsOptimized = context.Products
                               .Where(p => p.Price >100)// 在数据库层面进行过滤.ToList();

第一个查询先将

Products

表中的所有记录加载到内存中,然后再进行过滤。这种做法在数据量大时会导致严重的性能问题。第二个查询通过在数据库层面进行过滤操作,只返回满足条件的记录,极大地提升了查询效率。

1.4. 使用原生 SQL 查询

在某些复杂场景下,LINQ 查询生成的 SQL 可能不够高效。此时,可以使用原生 SQL 查询来提高性能。EF Core 提供了

FromSqlRaw

ExecuteSqlRaw

方法来执行原生 SQL 查询和命令。

var products = context.Products
                      .FromSqlRaw("SELECT * FROM Products WHERE Price > {0}",100).ToList();
FromSqlRaw

方法直接执行给定的 SQL 查询,并返回实体类型的集合。这样可以灵活使用 SQL 的特性和功能,同时保证查询性能的最优化。

通过合理使用无跟踪查询、选择合适的数据加载策略、优化 LINQ 查询以及在必要时使用原生 SQL 查询,开发者可以显著提升 EF Core 查询性能。针对不同的业务场景,选择适合的优化策略,不仅可以提高应用程序的响应速度,还能有效降低系统资源的消耗。

二、使用缓存

在使用 EF Core进行数据访问时,缓存是一种有效的性能优化手段。通过缓存,我们可以减少对数据库的频繁访问,降低延迟和系统资源消耗,从而提高应用程序的响应速度。EF Core 本身不提供内置的二级缓存机制,但可以使用一些外部的缓存库或实现自定义的缓存策略。

2.1. 使用内存缓存(Memory Cache)

.NET 提供了

IMemoryCache

接口,它是一个轻量级的内存缓存实现,非常适合用来缓存频繁查询但变化不大的数据。在 EF Core 中,可以使用

IMemoryCache

来缓存查询结果,从而避免重复的数据库查询。
首先,我们需要在 ASP.NET Core 项目的

Startup.cs

中注册

IMemoryCache

服务:

publicclassStartup{publicvoidConfigureServices(IServiceCollection services){
        services.AddDbContext<AppDbContext>(options => 
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
        services.AddMemoryCache();// 注册内存缓存服务}}

接着,在查询方法中使用

IMemoryCache

缓存查询结果:

publicclassProductService{privatereadonlyAppDbContext _context;privatereadonlyIMemoryCache _cache;privatereadonlyTimeSpan _cacheDuration = TimeSpan.FromMinutes(5);// 缓存持续时间publicProductService(AppDbContext context,IMemoryCache cache){
        _context = context;
        _cache = cache;}publicList<Product>GetProducts(){var cacheKey ="products_cache_key";// 尝试从缓存中获取数据if(!_cache.TryGetValue(cacheKey,outList<Product> products)){// 如果缓存不存在,从数据库查询数据
            products = _context.Products.ToList();// 设置缓存选项var cacheOptions =newMemoryCacheEntryOptions().SetAbsoluteExpiration(_cacheDuration);// 设置绝对过期时间// 将数据添加到缓存
            _cache.Set(cacheKey, products, cacheOptions);}return products;}}

在上面代码中,我们通过

services.AddMemoryCache()

方法注册内存缓存服务。在

ProductService

类中,我们定义了

GetProducts

方法来获取

Product

列表。使用

_cache.TryGetValue

方法尝试从缓存中获取数据。如果缓存中存在数据,则直接返回缓存的数据,避免重复查询数据库,如果缓存中没有数据,则从数据库查询数据,并通过

_cache.Set

方法将数据添加到缓存中,设置缓存过期时间为 5 分钟。
通过这种方式,可以减少对数据库的频繁查询,显著提高应用程序的性能,特别是对于变化不频繁且读取频繁的数据。

2.2 自定义缓存策略

内存缓存适用于单服务器场景,但在分布式环境中,我们可能需要更灵活的缓存策略。可以通过自定义缓存策略,灵活地控制缓存的生命周期、失效策略以及缓存数据的刷新。以下是一个基于

IMemoryCache

的自定义缓存策略示例,支持缓存刷新机制:

publicclassCustomCacheService<T>{privatereadonlyIMemoryCache _cache;privatereadonlyFunc<Task<T>> _dataRetrievalFunc;privatereadonlyTimeSpan _cacheDuration;publicCustomCacheService(IMemoryCache cache,Func<Task<T>> dataRetrievalFunc,TimeSpan cacheDuration){
        _cache = cache;
        _dataRetrievalFunc = dataRetrievalFunc;
        _cacheDuration = cacheDuration;}publicasyncTask<T>GetOrRefreshCacheAsync(string cacheKey){if(!_cache.TryGetValue(cacheKey,outT cachedData)){// 缓存失效或不存在时,重新获取数据并刷新缓存
            cachedData =await_dataRetrievalFunc();var cacheOptions =newMemoryCacheEntryOptions().SetAbsoluteExpiration(_cacheDuration);

            _cache.Set(cacheKey, cachedData, cacheOptions);}return cachedData;}}

解释:

  1. 定义 CustomCacheService:一个泛型类,用于缓存任意类型的数据。
  2. 支持缓存刷新机制:如果缓存失效或不存在,通过传入的 _dataRetrievalFunc 委托方法重新获取数据,并刷新缓存。
  3. 使用灵活的缓存策略:缓存持续时间、数据获取逻辑等都可以通过构造函数进行配置,实现了灵活的自定义缓存策略。

3. 使用分布式缓存(如 Redis)

在分布式系统中,内存缓存不适用,因为不同的应用实例会有不同的内存空间。使用分布式缓存(如 Redis 或 Memcached)可以解决这个问题。Redis 是一种常用的分布式缓存,它具有高性能和丰富的数据类型支持。
首先,我们配置 Redis 缓存服务:

publicclassStartup{publicvoidConfigureServices(IServiceCollection services){
        services.AddDbContext<AppDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));// 注册分布式缓存服务(Redis)
        services.AddStackExchangeRedisCache(options =>{
            options.Configuration ="localhost:6379";// Redis 服务器配置
            options.InstanceName ="SampleInstance";});}}

接着,使用

IDistributedCache

接口编写缓存查询逻辑:

publicclassProductService{privatereadonlyAppDbContext _context;privatereadonlyIDistributedCache _cache;privatereadonlyTimeSpan _cacheDuration = TimeSpan.FromMinutes(5);publicProductService(AppDbContext context,IDistributedCache cache){
        _context = context;
        _cache = cache;}publicasyncTask<List<Product>>GetProductsAsync(){var cacheKey ="products_cache_key";var cachedProducts =await _cache.GetStringAsync(cacheKey);if(cachedProducts !=null){// 从缓存中反序列化数据return JsonSerializer.Deserialize<List<Product>>(cachedProducts);}// 如果缓存中没有数据,从数据库查询var products =await _context.Products.ToListAsync();// 序列化数据并添加到缓存var serializedProducts = JsonSerializer.Serialize(products);await _cache.SetStringAsync(cacheKey, serializedProducts,newDistributedCacheEntryOptions{
            AbsoluteExpirationRelativeToNow = _cacheDuration
        });return products;}}

通过使用内存缓存、自定义缓存策略和分布式缓存(如 Redis),EF Core 可以在不同的场景下有效地提高查询性能和应用程序的响应速度。选择合适的缓存策略,不仅可以减少对数据库的压力,还能显著提高系统的稳定性和可扩展性。

三、总结

本文介绍了如何优化 EF Core 性能,避免常见陷阱。我们探讨了查询优化、数据加载策略、缓存机制等实用技巧。首先,通过合理使用无跟踪查询、选择合适的数据加载策略(如预加载)、优化 LINQ 查询,以及在需要时使用原生 SQL,开发者可以显著提高查询性能。其次,利用内存缓存、自定义缓存策略或分布式缓存(如 Redis),可以有效减少数据库访问频率,提升应用响应速度。这些优化策略有助于在保持代码简洁和可维护性的同时,最大化应用的性能和稳定性。

标签: 性能优化

本文转载自: https://blog.csdn.net/gangzhucoll/article/details/142152085
版权归原作者 喵叔哟 所有, 如有侵权,请联系我们删除。

“轻松学EntityFramework Core--性能优化”的评论:

还没有评论