告别SQL拼接噩梦:Dapper.SqlBuilder动态查询实战指南

告别SQL拼接噩梦:Dapper.SqlBuilder动态查询实战指南

【免费下载链接】Dapper 【免费下载链接】Dapper 项目地址: https://gitcode.com/gh_mirrors/dapper3/Dapper

在数据访问层开发中,你是否还在手写冗长的条件判断来拼接SQL?是否因参数化查询处理不当导致安全隐患?Dapper.SqlBuilder组件提供了优雅的解决方案,让动态查询构建如同搭积木般简单。本文将通过实战案例,带你掌握从基础查询到复杂筛选器的实现技巧,彻底摆脱字符串拼接的困扰。

核心组件与工作原理

Dapper.SqlBuilder位于Dapper.SqlBuilder/SqlBuilder.cs,其核心设计采用模板替换机制,通过/**标记**/占位符动态注入SQL片段。主要包含两大核心类:

  • SqlBuilder:负责管理查询片段(Where/OrderBy等)和参数集合,提供链式API构建查询逻辑
  • Template:处理SQL模板渲染,自动合并参数并生成最终可执行SQL

Dapper查询构建流程

基础架构解析

SqlBuilder内部通过字典维护不同类型的查询子句,关键代码如下:

private readonly Dictionary<string, Clauses> _data = new Dictionary<string, Clauses>();

protected SqlBuilder AddClause(string name, string sql, object parameters, 
    string joiner, string prefix = "", string postfix = "", bool isInclusive = false)
{
    if (!_data.TryGetValue(name, out var clauses))
    {
        clauses = new Clauses(joiner, prefix, postfix);
        _data[name] = clauses;
    }
    clauses.Add(new Clause(sql, parameters, isInclusive));
    _seq++;
    return this;
}

当调用Where()OrderBy()等方法时,实际上是向对应Clause集合添加SQL片段,最终由Clauses类负责按规则拼接(如Where子句默认使用AND连接)。

快速上手:从静态查询到动态构建

基础查询构建

使用SqlBuilder构建简单查询仅需三步:

  1. 创建SqlBuilder实例并添加查询条件
  2. 使用AddTemplate定义SQL模板
  3. 执行渲染并获取结果
var builder = new SqlBuilder()
    .Where("Status = @status", new { status = 1 })
    .OrderBy("CreateTime DESC");

var template = builder.AddTemplate("SELECT * FROM Orders /**where**/ /**orderby**/");

// 最终生成: SELECT * FROM Orders WHERE Status = @status ORDER BY CreateTime DESC
using (var connection = new SqlConnection("..."))
{
    var orders = connection.Query<Order>(template.RawSql, template.Parameters);
}

动态条件筛选

业务场景中常需根据不同条件动态添加筛选器,SqlBuilder的条件判断变得异常简洁:

var builder = new SqlBuilder();
var template = builder.AddTemplate("SELECT * FROM Products /**where**/");

// 价格区间筛选
if (minPrice.HasValue)
    builder.Where("Price >= @minPrice", new { minPrice });
    
if (maxPrice.HasValue)
    builder.Where("Price <= @maxPrice", new { maxPrice });

// 分类筛选(多选)
if (categoryIds?.Any() == true)
    builder.Where("CategoryId IN @categoryIds", new { categoryIds });

与传统字符串拼接相比,这种方式避免了繁琐的AND拼接判断,且天然支持参数化查询,有效防止SQL注入。

高级实战:复杂场景解决方案

多模板复用

在分页查询场景中,通常需要同时构建列表查询和总数查询,SqlBuilder支持多模板共享查询条件:

var builder = new SqlBuilder()
    .Where("IsDeleted = 0");

// 列表查询模板
var listTemplate = builder.AddTemplate(@"
    SELECT * FROM (
        SELECT *, ROW_NUMBER() OVER (/**orderby**/) AS RowNum 
        FROM Users /**where**/
    ) t WHERE RowNum BETWEEN @Start AND @End", new { Start = 1, End = 20 });

// 总数查询模板
var countTemplate = builder.AddTemplate("SELECT COUNT(*) FROM Users /**where**/");

// 动态添加排序条件
builder.OrderBy("CreateTime DESC");

// 执行查询
var users = connection.Query<User>(listTemplate.RawSql, listTemplate.Parameters);
var total = connection.ExecuteScalar<int>(countTemplate.RawSql, countTemplate.Parameters);

高级筛选器组合

通过OrWhere方法可实现复杂的OR条件组合,需注意SqlBuilder会将所有OR条件包裹在括号中:

builder.Where("Role = 'Admin'")
       .OrWhere("Department = @dept", new { dept = "IT" })
       .OrWhere("Title LIKE @title", new { title = "%Manager%" });

生成的WHERE子句为: WHERE Role = 'Admin' AND ( Department = @dept OR Title LIKE @title )

这种处理方式避免了复杂的逻辑运算符优先级问题,确保查询逻辑正确性。

最佳实践与避坑指南

参数管理技巧

  1. 集中参数添加:通过AddParameters方法添加全局参数
builder.AddParameters(new { TenantId = currentTenantId })
       .Where("TenantId = @TenantId");
  1. 动态参数名处理:使用nameof避免硬编码
var keyWord = "test";
builder.Where($"{nameof(Product.Name)} LIKE @{nameof(keyWord)}", new { keyWord });

常见陷阱解析

  1. OrWhere逻辑组合:所有OrWhere条件会被合并为(A OR B OR C),与普通Where条件用AND连接,如Dapper.SqlBuilder/Readme.md中所述

  2. 模板标记冲突:避免自定义标记与SQL注释冲突,建议使用独特命名如/**custom_where**/

  3. 重复添加问题:同一类型子句多次添加会自动合并,无需手动处理分隔符

性能优化与扩展

查询缓存策略

对于频繁执行的动态查询,可结合Dapper的缓存机制优化性能:

var cacheKey = $"Query_{queryParams.GetHashCode()}";
var cachedSql = MemoryCache.Get(cacheKey) as string;

if (cachedSql == null)
{
    var template = builder.AddTemplate(baseSql);
    cachedSql = template.RawSql;
    MemoryCache.Set(cacheKey, cachedSql, TimeSpan.FromMinutes(30));
}

// 使用缓存的SQL执行查询
var result = connection.Query(cachedSql, queryParams);

自定义查询片段

通过继承SqlBuilder扩展自定义查询类型,例如添加FullText搜索子句:

public class AdvancedSqlBuilder : SqlBuilder
{
    public AdvancedSqlBuilder FullTextSearch(string column, string keyword)
    {
        return AddClause("fulltext", 
            $"CONTAINS({column}, @keyword)", 
            new { keyword }, 
            " AND ", "AND ");
    }
}

项目资源与学习路径

掌握SqlBuilder不仅能提升代码质量,更能显著降低维护成本。下一篇我们将探讨如何结合表达式树构建类型安全的动态查询,敬请关注!

本文示例代码已同步至项目示例库,可通过git clone https://gitcode.com/gh_mirrors/dapper3/Dapper获取完整项目。

【免费下载链接】Dapper 【免费下载链接】Dapper 项目地址: https://gitcode.com/gh_mirrors/dapper3/Dapper

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值