一、为什么需要优化数据库查询

想象一下,你正在开发一个电商网站,用户打开商品列表页面时,页面加载需要5秒。经过排查发现,问题出在数据库查询上——一个简单的商品列表查询竟然用了3秒!这种情况在实际开发中并不少见,特别是在数据量逐渐增大后,原本运行良好的查询可能突然变慢。

在.NET Core应用中,我们通常使用EF Core作为ORM框架,它确实能极大提升开发效率。但EF Core生成的SQL有时并不完美,比如:

  • 可能查询了不需要的字段
  • 复杂的LINQ操作被翻译成低效的SQL
  • 没有合理利用索引

这时候就需要我们主动干预,通过调优查询或引入更轻量的Dapper来解决问题。

二、EF Core查询调优实战技巧

1. 只查询需要的字段

EF Core默认会查询所有字段,但很多时候我们只需要其中几个。比如用户表有20个字段,但列表页只需要显示用户名和头像:

// 技术栈:.NET Core + EF Core
// 不好的做法:查询所有字段
var users = dbContext.Users.ToList();

// 优化做法:只选择需要的字段
var users = dbContext.Users
    .Select(u => new { u.UserName, u.AvatarUrl })
    .ToList();

2. 警惕延迟加载的陷阱

EF Core的延迟加载看起来很美好,但容易导致"N+1查询问题"。比如查询10篇文章及其作者:

// 可能产生11条SQL查询(1次查文章+10次查作者)
var articles = dbContext.Articles.ToList();
foreach (var article in articles)
{
    Console.WriteLine(article.Author.Name); // 每次访问都会触发查询
}

// 优化方案:使用Include预先加载
var articles = dbContext.Articles
    .Include(a => a.Author)
    .ToList();

3. 分页查询一定要加Skip/Take

大数据量查询必须分页,但要注意写法:

// 错误的顺序:先取全部数据再分页
var data = dbContext.Products
    .ToList()
    .Skip(10).Take(10); // 内存分页,性能灾难!

// 正确做法:在数据库层面分页
var data = dbContext.Products
    .OrderBy(p => p.Id)
    .Skip(10)
    .Take(10)
    .ToList();

三、什么时候该用Dapper

虽然EF Core很强大,但在以下场景Dapper更有优势:

  1. 超复杂SQL查询(如多表联合统计)
  2. 需要精确控制SQL执行
  3. 批量操作性能要求极高

Dapper基础用法示例

// 技术栈:.NET Core + Dapper
using var connection = new SqlConnection(connectionString);

// 简单查询
var products = connection.Query<Product>(
    "SELECT Id, Name FROM Products WHERE Price > @minPrice",
    new { minPrice = 100 });

// 多表查询
var sql = @"
    SELECT o.*, u.UserName 
    FROM Orders o
    JOIN Users u ON o.UserId = u.Id
    WHERE o.CreateTime > @startDate";

var orders = connection.Query<Order, User, Order>(
    sql,
    (order, user) => { 
        order.User = user;
        return order;
    },
    new { startDate = DateTime.Now.AddDays(-7) },
    splitOn: "UserName");

四、EF Core与Dapper混合使用策略

最佳实践是:

  • 80%的常规CRUD使用EF Core
  • 20%复杂查询/性能关键路径使用Dapper

混合使用示例

// 在同一个服务中混用
public class ProductService
{
    private readonly AppDbContext _db;
    private readonly SqlConnection _connection;
    
    public ProductService(AppDbContext db, SqlConnection connection)
    {
        _db = db;
        _connection = connection;
    }
    
    // 使用EF Core添加产品
    public async Task AddProduct(Product product)
    {
        _db.Products.Add(product);
        await _db.SaveChangesAsync();
    }
    
    // 使用Dapper查询热销商品
    public async Task<List<Product>> GetHotProducts()
    {
        var sql = @"
            SELECT TOP 10 p.* 
            FROM Products p
            JOIN OrderItems oi ON p.Id = oi.ProductId
            GROUP BY p.Id, p.Name, p.Price
            ORDER BY SUM(oi.Quantity) DESC";
            
        return (await _connection.QueryAsync<Product>(sql)).ToList();
    }
}

五、性能优化进阶技巧

  1. 批量操作优化
    EF Core的批量更新可能生成多条SQL,使用Dapper能大幅提升性能:
// 使用Dapper执行批量更新
var sql = "UPDATE Products SET Price = Price * 1.1 WHERE CategoryId = @categoryId";
await connection.ExecuteAsync(sql, new { categoryId = 5 });
  1. 读写分离
    将查询操作路由到从库,减轻主库压力:
// 在Startup中配置多数据库连接
services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("MasterDb")));

services.AddSingleton(new SqlConnection(
    Configuration.GetConnectionString("ReadOnlyDb")));

六、应用场景与选型建议

适合使用EF Core的场景:

  • 快速原型开发
  • 简单的CRUD操作
  • 需要数据库迁移支持的项目

适合使用Dapper的场景:

  • 报表类复杂查询
  • 批量数据处理
  • 对性能要求极高的接口

注意事项:

  1. 不要过早优化,先确保功能正确
  2. 混合使用时注意事务处理
  3. Dapper需要手动处理SQL注入风险

七、总结

数据库查询优化是个持续的过程,没有银弹。EF Core提供了开发效率,Dapper提供了执行效率。明智的做法是根据具体场景选择合适的工具,甚至混合使用。记住:

  • 先分析慢查询的原因
  • 从简单的EF Core调优开始
  • 必要时引入Dapper处理复杂场景
  • 始终关注实际性能指标

通过合理的策略组合,完全可以让.NET Core应用的数据库访问既保持开发效率,又拥有出色的运行时性能。