C# Dapper高效查询:轻量级ORM性能优化指南


Dapper作为.NET生态中广受欢迎的轻量级ORM(对象关系映射)工具,以其卓越的性能和简洁的API著称。本文将全面介绍Dapper的核心功能、高效查询技巧以及实际应用场景,帮助开发者充分发挥其性能优势。

一、Dapper基础入门

1.1 Dapper核心优势

  • 超高性能:接近原生ADO.NET的执行效率
  • 简洁API:扩展了IDbConnection接口
  • 最小开销:没有复杂的变更跟踪机制
  • 灵活映射:支持对象与关系数据的多种映射方式

1.2 安装与基本配置

通过NuGet安装Dapper:

Install-Package Dapper

基础使用示例:

using System.Data.SqlClient;
using Dapper;

// 创建数据库连接
using (var connection = new SqlConnection(connectionString))
{
    connection.Open();

    // 执行查询
    var products = connection.Query<Product>("SELECT * FROM Products WHERE Price > @price", 
        new { price = 100 });
}

二、基本查询操作

2.1 简单查询与参数化

// 查询单个对象
var product = connection.QueryFirstOrDefault<Product>(
    "SELECT * FROM Products WHERE Id = @id", 
    new { id = 1 });

// 查询列表
var expensiveProducts = connection.Query<Product>(
    "SELECT * FROM Products WHERE Price > @minPrice ORDER BY Price DESC",
    new { minPrice = 500 });

// 多参数查询
var filteredProducts = connection.Query<Product>(
    "SELECT * FROM Products WHERE Price BETWEEN @minPrice AND @maxPrice AND CategoryId = @categoryId",
    new { 
        minPrice = 100, 
        maxPrice = 1000, 
        categoryId = 5 
    });

2.2 执行非查询操作

// 插入数据
var affectedRows = connection.Execute(
    "INSERT INTO Products (Name, Price) VALUES (@name, @price)",
    new { name = "无线耳机", price = 299.99m });

// 批量插入
var products = new List<Product>
{
    new Product { Name = "键盘", Price = 199.99m },
    new Product { Name = "鼠标", Price = 99.99m }
};

var totalInserted = connection.Execute(
    "INSERT INTO Products (Name, Price) VALUES (@Name, @Price)",
    products);

三、高级查询技巧

3.1 多映射(一对多关系)

var sql = @"
    SELECT p.*, c.*
    FROM Products p
    INNER JOIN Categories c ON p.CategoryId = c.Id
    WHERE p.Price > @minPrice";

var products = connection.Query<Product, Category, Product>(
    sql,
    (product, category) => 
    {
        product.Category = category;
        return product;
    },
    new { minPrice = 100 },
    splitOn: "Id");  // 指定分割列

// 结果处理
foreach (var p in products)
{
    Console.WriteLine($"{p.Name} - {p.Category.Name}");
}

3.2 多结果集查询

var sql = @"
    SELECT * FROM Products WHERE Price > 500;
    SELECT * FROM Categories;";

using (var multi = connection.QueryMultiple(sql))
{
    var products = multi.Read<Product>().ToList();
    var categories = multi.Read<Category>().ToList();

    // 处理结果...
}

3.3 存储过程调用

var parameters = new DynamicParameters();
parameters.Add("@categoryId", 5);
parameters.Add("@totalCount", dbType: DbType.Int32, direction: ParameterDirection.Output);

var products = connection.Query<Product>(
    "GetProductsByCategory",
    parameters,
    commandType: CommandType.StoredProcedure);

int totalCount = parameters.Get<int>("@totalCount");

四、性能优化策略

4.1 缓冲与无缓冲查询

// 缓冲查询(默认)- 所有结果读入内存
var products = connection.Query<Product>("SELECT * FROM Products");

// 无缓冲查询 - 流式处理大数据
var products = connection.QueryUnbuffered<Product>("SELECT * FROM LargeProductsTable");

foreach (var product in products)  // 一次处理一行
{
    ProcessProduct(product);
}

4.2 批量操作优化

// 使用Table-Valued参数进行高效批量操作
DataTable productTable = new DataTable();
productTable.Columns.Add("Name", typeof(string));
productTable.Columns.Add("Price", typeof(decimal));

// 添加数据行...
foreach (var p in productsToInsert)
{
    productTable.Rows.Add(p.Name, p.Price);
}

var param = new { Products = productTable.AsTableValuedParameter("dbo.ProductTableType") };
connection.Execute("INSERT INTO Products SELECT * FROM @Products", param);

4.3 自定义类型处理

// 自定义类型处理器(处理特殊类型如JSON)
SqlMapper.AddTypeHandler(new JsonTypeHandler<ProductDetails>());

public class JsonTypeHandler<T> : SqlMapper.TypeHandler<T>
{
    public override T Parse(object value)
    {
        return JsonSerializer.Deserialize<T>(value.ToString());
    }

    public override void SetValue(IDbDataParameter parameter, T value)
    {
        parameter.Value = JsonSerializer.Serialize(value);
    }
}

// 使用
var product = connection.Query<Product>("SELECT * FROM ProductsWithDetails");

五、实际应用模式

5.1 仓储模式实现

public interface IProductRepository
{
    Task<Product> GetByIdAsync(int id);
    Task<IEnumerable<Product>> GetByCategoryAsync(int categoryId);
    Task<int> AddAsync(Product product);
}

public class DapperProductRepository : IProductRepository
{
    private readonly IDbConnectionFactory _connectionFactory;

    public DapperProductRepository(IDbConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public async Task<Product> GetByIdAsync(int id)
    {
        using (var connection = _connectionFactory.CreateConnection())
        {
            return await connection.QueryFirstOrDefaultAsync<Product>(
                "SELECT * FROM Products WHERE Id = @id",
                new { id });
        }
    }

    // 其他方法实现...
}

5.2 与DI容器集成

// 在Startup.cs中配置
services.AddScoped<IDbConnectionFactory>(provider => 
    new SqlConnectionFactory(Configuration.GetConnectionString("Default")));

services.AddScoped<IProductRepository, DapperProductRepository>();

// SQL连接工厂实现
public class SqlConnectionFactory : IDbConnectionFactory
{
    private readonly string _connectionString;

    public SqlConnectionFactory(string connectionString)
    {
        _connectionString = connectionString;
    }

    public IDbConnection CreateConnection()
    {
        var connection = new SqlConnection(_connectionString);
        connection.Open();
        return connection;
    }
}

六、Dapper扩展与生态系统

6.1 Dapper.Contrib

// 安装扩展包
Install-Package Dapper.Contrib

// 简化CRUD操作
using Dapper.Contrib.Extensions;

// 获取所有产品
var products = connection.GetAll<Product>().ToList();

// 插入新记录
var newId = connection.Insert(new Product { Name = "新品", Price = 199.99m });

// 更新记录
product.Price = 299.99m;
connection.Update(product);

// 删除记录
connection.Delete(product);

6.2 Dapper Plus(批量操作)

// 安装扩展包
Install-Package Z.Dapper.Plus

// 批量插入
connection.BulkInsert(products);

// 批量更新
connection.BulkUpdate(products);

// 批量删除
connection.BulkDelete(products);

七、常见问题解决方案

7.1 处理复杂查询结果

// 动态结果处理
var results = connection.Query("SELECT p.*, c.Name as CategoryName FROM Products p JOIN Categories c ON p.CategoryId = c.Id");

foreach (dynamic row in results)
{
    Console.WriteLine($"{row.Name} - {row.CategoryName}");
}

// 多结果映射到不同对象
var queryResult = connection.Query<Product, Category, Product>(
    "SELECT p.*, c.* FROM Products p JOIN Categories c ON p.CategoryId = c.Id",
    (product, category) => 
    {
        product.Category = category;
        return product;
    },
    splitOn: "Id");

7.2 性能监控与调优

// 安装MiniProfiler
Install-Package MiniProfiler.AspNetCore
Install-Package MiniProfiler.Providers.SqlServer

// 配置
services.AddMiniProfiler(options =>
{
    options.RouteBasePath = "/profiler";
    options.SqlFormatter = new StackExchange.Profiling.SqlFormatters.InlineFormatter();
}).AddEntityFramework();

// 在查询中使用
using (MiniProfiler.Current.Step("查询产品数据"))
{
    var products = connection.Query<Product>("SELECT * FROM Products WHERE Price > @price", 
        new { price = 100 });
}

八、Dapper最佳实践

  1. 连接管理
  • 使用using语句确保连接及时关闭
  • 考虑使用连接池优化性能
  1. 参数化查询
  • 始终使用参数化查询防止SQL注入
  • 对复杂参数使用DynamicParameters
  1. 性能平衡
  • 简单查询使用Dapper
  • 复杂领域模型考虑结合EF Core
  1. 代码组织
  • 将SQL查询集中管理
  • 使用仓储模式抽象数据访问
// SQL集中管理示例
public static class ProductSql
{
    public static string GetById => "SELECT * FROM Products WHERE Id = @id";
    public static string GetByCategory => """
        SELECT p.*, c.Name as CategoryName 
        FROM Products p 
        JOIN Categories c ON p.CategoryId = c.Id 
        WHERE p.CategoryId = @categoryId
        """;
}

// 在仓储中使用
public async Task<Product> GetByIdAsync(int id)
{
    using (var connection = _connectionFactory.CreateConnection())
    {
        return await connection.QueryFirstOrDefaultAsync<Product>(
            ProductSql.GetById, 
            new { id });
    }
}

结语

Dapper作为.NET平台上的高性能微ORM,在需要极致性能的场景下表现出色。通过本指南,您应该已经掌握:

  1. Dapper核心功能:基本CRUD操作、参数化查询
  2. 高级技巧:多映射、多结果集、存储过程调用
  3. 性能优化:批量操作、缓冲控制、类型处理
  4. 架构模式:仓储模式实现、DI集成

实际项目中使用建议:

  • 在简单到中等复杂度查询中优先使用Dapper
  • 对性能关键路径进行基准测试
  • 合理组织SQL查询,保持可维护性
  • 结合Dapper扩展增强功能

Dapper与Entity Framework Core不是非此即彼的选择,根据场景合理搭配使用,可以构建出既高效又可维护的数据访问层。


发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注