SmartDapper
SmartDapper 是一个基于 Dapper 的轻量级扩展库,提供表达式树转 SQL、链式构建器(Fluent Builder:QuerySet/InsertSet/UpdateSet/DeleteSet,支持 Where/Select/Join/GroupBy/OrderBy/Union/Set/Fill 等)、通用 CRUD、分页查询与多数据库适配(SQL Server / MySQL / SQLite)。默认参数化执行,以降低 SQL 注入风险。
安装
/* by yours.tools - online tools website : yours.tools/zh/post.html */ dotnet add package SmartDapper快速开始(SQL 生成 + Dapper 执行)
/* by yours.tools - online tools website : yours.tools/zh/post.html */ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using Dapper; using Microsoft.Data.SqlClient; using SmartDapper.SqlGenerator; [Table("Users")] public class User { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } public string UserName { get; set; } = string.Empty; public bool IsActive { get; set; } } var generator = SqlGeneratorFactory.CreateSqlServer<User>(); var (sql, parameters) = generator.GetSelectAll(u => u.IsActive); using var conn = new SqlConnection("Server=.;Database=MyDb;Trusted_Connection=True;TrustServerCertificate=True;"); var users = conn.Query<User>(sql, parameters).ToList();使用扩展方法(IDbConnection)
using Microsoft.Data.SqlClient; using SmartDapper.Extensions; using var conn = new SqlConnection("Server=.;Database=MyDb;Trusted_Connection=True;TrustServerCertificate=True;"); // --------------------------- // 结果映射(可选,但推荐:列名/属性名不一致时) // --------------------------- // 让 Dapper 在 materialize(结果集->实体)时优先使用 [Column("DbColumn")] 映射, // 并可选开启下划线匹配(user_name -> UserName)。 // // 注意:这是 Dapper 的全局设置(进程内对该实体类型的所有查询都会受影响),建议只在启动阶段执行一次。 DapperMappingExtensions.UseColumnAttributeTypeMap<User>(matchNamesWithUnderscores: true); // 或者:一次性对“实体所在程序集”内的类型批量注册(默认按 [Table]/[Key]/[Column] 识别实体) // DapperMappingExtensions.UseColumnAttributeTypeMap(matchNamesWithUnderscores: true, typeof(User).Assembly); // --------------------------- // 查询(非链式 / 非 Fluent) // --------------------------- var list = await conn.GetAllListAsync<User>(u => u.IsActive); var (items, total) = await conn.GetPagedListAsync<User>(skip: 0, take: 20); // 按主键查单条(不存在返回 null) var one = await conn.GetAsync<User>(id: 1); // First / Single(注意:Single 在多条命中时会抛异常) var first = await conn.FirstOrDefaultAsync<User>(u => u.IsActive); var single = await conn.SingleOrDefaultAsync<User>(u => u.Id == 1); // 是否存在 / 计数 var exists = await conn.AnyAsync<User>(u => u.IsActive); var count = await conn.CountAsync<User>(u => u.IsActive); // --------------------------- // 新增(非链式) // --------------------------- var newUser = new User { UserName = "Alice", IsActive = true }; await conn.InsertAsync(newUser); // 插入并返回自增ID(要求实体主键标注 [Key] 且为 Identity) var newId = await conn.InsertAndGetIdAsync(newUser); // --------------------------- // 更新(非链式) // --------------------------- // 1) 按主键更新指定字段 await conn.UpdateAsync<User>( updateFields: u => new { IsActive = false }, keyValue: 1); // 2) 按条件更新指定字段 await conn.UpdateAsync<User>( updateFields: u => new { IsActive = true }, predicate: u => u.UserName == "Alice"); // --------------------------- // 删除(非链式) // --------------------------- // 按主键删除 await conn.DeleteAsync<User>(id: 1); // 按条件删除 await conn.DeleteAsync<User>(u => u.IsActive == false); // --------------------------- // 软删除(SoftDelete,非链式) // --------------------------- // 前提:实体需要标注 [SoftDelete](可标在实体类上或 IsDeleted 字段属性上),否则会抛异常(避免误以为“已软删”)。 // // 1) 可选:开启查询自动过滤已删除数据(全局开关) DapperExtensions.ConfigureSoftDelete(o => { o.FilterSoftDeleted = true; }); // 2) 逻辑删除 / 恢复:按主键 await conn.SoftDeleteAsync<User>(id: 1); await conn.RestoreAsync<User>(id: 1); // 3) 逻辑删除 / 恢复:按条件 await conn.SoftDeleteAsync<User>(u => u.UserName == "Alice"); await conn.RestoreAsync<User>(u => u.UserName == "Alice");多连接统一注册(可选,DI 场景)
SmartDapper 核心是“以你传入的IDbConnection为准”,你可以在一个项目里自由创建/使用多个连接。
如果你希望在 DI 中统一注册多个连接配置,可以使用AddSmartDapperConnections(...)注册一个按 key 创建连接的工厂:
using System.Data; using Microsoft.Data.SqlClient; using SmartDapper.Extensions; var builder = WebApplication.CreateBuilder(args); builder.Services.AddSmartDapperConnections(o => { // 方式 1:按连接字符串 + 工厂 o.Add("Main", builder.Configuration.GetConnectionString("Main")!, cs => new SqlConnection(cs)); o.Add("Log", builder.Configuration.GetConnectionString("Log")!, cs => new SqlConnection(cs)); // 可选:指定默认 key(不设则默认取第一个 Add 的 key) o.DefaultKey = "Main"; }); // 使用:按 key 创建连接(连接由你负责释放;可手写 using,也可用辅助方法自动 using) // var factory = sp.GetRequiredService<ISmartDapperConnectionFactory>(); // // 方式 1:手写 using // using var conn = factory.Create("Main"); // var users = await conn.GetAllListAsync<User>(); // // 方式 2:辅助方法(内部 Create + Dispose,减少重复代码) // var users2 = await factory.UseConnectionAsync("Main", c => c.GetAllListAsync<User>()); // // 默认连接(等价于 factory.CreateDefault() / factory.UseDefaultXxx...) // var usersDefault = await factory.UseDefaultConnectionAsync(c => c.GetAllListAsync<User>()); // // 方式 3:事务辅助方法(内部 BeginTransaction + Commit/Rollback + Dispose) // await factory.UseTransactionAsync("Main", async (c, tx) => // { // await c.InsertAsync(new User { /* ... */ }, transaction: tx); // await c.UpdateAsync(new User { /* ... */ }, transaction: tx); // }); // // 默认连接的事务版本 // await factory.UseDefaultTransactionAsync(async (c, tx) => // { // await c.InsertAsync(new User { /* ... */ }, transaction: tx); // await c.UpdateAsync(new User { /* ... */ }, transaction: tx); // });SQL 日志输出配置(可选,推荐在启动时设置)
SmartDapper 在执行 CRUD/查询时会记录“生成的 SQL”。默认策略:
- Information:仅输出 SQL(不输出参数,避免敏感信息泄露)
- Debug:输出 SQL + 参数(便于本地/测试环境排查)
你可以在程序初始化阶段通过以下方式进行配置:
方式 1:DI 风格(推荐)
using Microsoft.Extensions.Logging; using SmartDapper.Extensions; var builder = WebApplication.CreateBuilder(args); // 1) 默认无需配置:Info 只输出 SQL;Debug 输出 SQL + 参数 // 2) 如需在 Info 级别也输出参数(注意敏感信息风险) builder.Services.AddSmartDapper(configureSqlLogging: o => { o.SqlLogLevel = LogLevel.Information; o.ParameterLogLevel = LogLevel.Information; }); // 3) 如需彻底禁用参数输出(即使 Debug) builder.Services.AddSmartDapper(configureSqlLogging: o => { o.ParameterLogLevel = LogLevel.None; });方式 1.1:统一注入(日志 + 软删一起配置,推荐)
using Microsoft.Extensions.Logging; using SmartDapper.Extensions; var builder = WebApplication.CreateBuilder(args); builder.Services.AddSmartDapper( configureSqlLogging: o => { o.SqlLogLevel = LogLevel.Information; o.ParameterLogLevel = LogLevel.Information; }, configureSoftDelete: o => { o.FilterSoftDeleted = true; }, configureSqlServer: o => { // SQL Server:是否在 SELECT 的 FROM 表名后追加 WITH (NOLOCK) o.UseNoLock = true; });结果映射(TypeMap)注册(可选,DI 场景推荐)
当你的“数据库列名”和“实体属性名”不一致时(例如user_namevsUserName),建议在启动阶段注册一次映射规则:
using SmartDapper.Extensions; var builder = WebApplication.CreateBuilder(args); // 按“实体所在程序集”批量注册(默认按 [Table]/[Key]/[Column] 识别实体) builder.Services.AddSmartDapperColumnMapping( matchNamesWithUnderscores: true, typeof(User).Assembly);全局 QueryFilter(可选,类似其它 ORM 的全局过滤)
SmartDapper 支持注册“全局 QueryFilter”,在查询时自动把过滤条件注入到WHERE(并与用户传入的Where(...)条件用AND组合)。
典型用途:
- 软删除(接口风格):
ISoftDelete(可选接口)+it => it.IsDeleted == false - 多租户/数据隔离:
IHasTenant+it => it.TenantId == tenantId - 数据权限/只看有效数据:
IHasCompany/IIsActive等
兼容说明:QueryFilter 与现有软删开关
FilterSoftDeleted完全兼容,可以同时开启;最终会一起AND到WHERE里。
提示:如果你同时开启了FilterSoftDeleted=true,又通过 QueryFilter 写了同样的软删条件,会产生“重复条件”(不影响结果,只是冗余)。
方式 1:DI 风格(推荐)
注意:
AddSmartDapper(...)的参数较多,建议使用命名参数,避免顺序误用。
using SmartDapper.Extensions; builder.Services.AddSmartDapper( configureQueryFilters: o => { // 例:接口软删除(需要实体实现 ISoftDelete,或你自定义的接口/基类) o.AddTableFilter<ISoftDelete>(it => it.IsDeleted == false); });方式 2:非 DI 场景(直接配置)
using SmartDapper.Extensions; DapperExtensions.ConfigureQueryFilters(o => { o.AddTableFilter<ISoftDelete>(it => it.IsDeleted == false); });方式 3:IDbConnection语法糖(更接近db.QueryFilter...)
说明:C# 不支持扩展“属性”,因此调用形态为
db.QueryFilter().AddTableFilter(...)。
using SmartDapper.Extensions; using var conn = new SqlConnection("..."); conn.QueryFilter() .AddTableFilter<ISoftDelete>(it => it.IsDeleted == false);方式 2:非 DI 场景(直接配置)
using Microsoft.Extensions.Logging; using SmartDapper.Extensions; // SQL 日志 DapperExtensions.ConfigureSqlLogging(o => { o.SqlLogLevel = LogLevel.Information; o.ParameterLogLevel = LogLevel.Debug; // 默认值 }); // 软删(查询是否自动过滤已删除) DapperExtensions.ConfigureSoftDelete(o => { o.FilterSoftDeleted = true; }); // SQL Server DapperExtensions.ConfigureSqlServer(o => { o.UseNoLock = true; });表达式树支持范围(WHERE / IN / LIKE / UPDATE SET)
SmartDapper 的“表达式树转 SQL”主要用于Expression<Func<T, bool>>(WHERE 谓词),以及更新场景的Expression<Func<T, object>>(SET 字段选择/赋值)。
WHERE(Expression<Func<T, bool>>)支持项
| 场景 | 示例表达式(C#) | SQL 形态(概念) | 备注 |
|---|---|---|---|
| 等值 / 不等 | x => x.Id == 1/x => x.Id != 1 | Id = @Id/Id <> @Id | 参数名默认按字段名生成 |
| 比较 | x => x.Age > 18/>=/</<= | Age > @Age等 | 仅支持比较运算符集合 |
| 逻辑组合 | x => x.IsActive && x.Age >= 18 | (... ) AND (... ) | 支持&&/ ` |
| NULL 判断 | x => x.Name == null/!= null | Name IS NULL/IS NOT NULL | 仅对== null/!= null特判 |
| bool 直接使用 | x => x.IsActive | IsActive = @IsActive(true) | 避免生成裸列名导致 SQL 不可执行 |
| bool 取反 | x => !x.IsDeleted | IsDeleted = 0 | 仅对实体 bool 字段取反做优化 |
| 字符串 Like | Contains/StartsWith/EndsWith | LIKE @p | 仅支持x => x.Column.Contains(value)这类“实体字段在左侧”形态 |
| IN(集合包含) | x => ids.Contains(x.Id) | Id IN (@Id_0, @Id_1, ...) | 仅支持list.Contains(x.Property);空集合会生成1 = 0 |
| All(全满足) | x => ages.All(a => x.Age >= a) | 展开为AND串 | All(empty)会生成1 = 1(见下方安全限制) |
UPDATE 的 SET(Expression<Func<T, object>>)支持写法
适用于:
conn.UpdateSet<T>().Set(...)/conn.Update(updateFields, predicate)等。
| 写法 | 示例 | 建议 |
|---|---|---|
| 对象初始化器(推荐) | x => new User { Name = name, IsActive = true } | 表达力最强,支持常量/闭包变量 |
| 单字段 | x => x.Name | 支持,但通常仍需搭配明确赋值(更推荐对象初始化器) |
| 匿名 new | x => new { x.Name, x.IsActive } | 可用,但更像“字段选择”,不如对象初始化器语义清晰 |
典型不支持/慎用
| 类型 | 示例 | 说明 |
|---|---|---|
| 算术运算 | x => x.Age + 1 > 18 | 不支持(WHERE 二元运算仅处理比较与 AND/OR) |
| 未知方法调用 | x => x.Name.ToLower() == "a" | 不支持(仅支持 string 的 Contains/StartsWith/EndsWith) |
| 反向 Contains | x => x.Tags.Contains("a") | 不支持(仅支持list.Contains(x.Prop)) |
安全限制(重要)
- UPDATE/DELETE 禁止恒真条件:例如
x => true、或All(empty)导致的1 = 1,会被拦截(防止误全表操作)。 - Fluent 查询限制:
Paging(skip, take)不能与自定义OrderBy(...)组合使用(分页 SQL 已内置排序规则)。- 投影查询(
Select(projection))不支持Paging(...),也不支持与SelectColumns(...)混用(SelectColumns已标记 Obsolete,仅为兼容保留)。
链式扩展(Fluent Builder)
SmartDapper 提供了一组面向IDbConnection的链式构建器,用于以“命令式链式 API”组织查询/插入/更新/删除,并在内部复用 SmartDapper 的 SQL 生成能力。
命名约定:
QuerySet<T>() / InsertSet<T>() / UpdateSet<T>() / DeleteSet<T>()。构建器对象非线程安全,建议按一次请求/一次操作创建并使用一次。
线程安全说明(重要)
- 为什么“线程不安全”:构建器内部会保存可变状态(例如:
Where谓词、Select列、OrderBy排序、分页、事务、超时等)。如果多个线程/任务共享同一个构建器实例并并发修改,会互相覆盖这些状态,导致生成的 SQL/参数混乱。 - 推荐用法:每次操作都从
conn.QuerySet<T>() / InsertSet<T>() / ...新建一个构建器,用完即弃;不要把构建器缓存为静态/单例,也不要在并发场景共享同一个实例。
查询(Select)
using SmartDapper.Extensions; public sealed class UserBriefDto { public int Id { get; set; } public string UserName { get; set; } = string.Empty; } // 1) 全字段查询 + Where + OrderBy var list = await conn.QuerySet<User>() .Where(u => u.IsActive && u.Age >= 18) .OrderBy(u => u.Id) .ToListAsync(); // 1.3) DISTINCT(查询去重) var distinctList = await conn.QuerySet<User>() .Distinct() .Where(u => u.IsActive) .ToListAsync(); // 1.1) 多字段排序(支持 new { ... }) var list2 = await conn.QuerySet<User>() .Where(u => u.IsActive) .OrderBy(u => new { u.Age, u.Id }) .ToListAsync(); // 1.2) 降序排序(独立方法) var list3 = await conn.QuerySet<User>() .Where(u => u.IsActive) .OrderByDescending(u => u.Id) .ToListAsync(); // 2) 投影(推荐:DTO/匿名类型) var dtoList = await conn.QuerySet<User>() .Select(u => new { u.Id, u.UserName }) .Distinct() .Where(u => u.IsActive) .OrderBy(u => u.Id) .ToListAsync(); // 2.1) 投影到 DTO(对象初始化器) var dtoList2 = await conn.QuerySet<User>() .Select(u => new UserBriefDto { Id = u.Id, UserName = u.UserName }) .Where(u => u.IsActive) .ToListAsync(); // 3) 指定列(字符串:仍返回实体 T,但请注意未选列将为默认值) var columns = await conn.QuerySet<User>() .Select("Id", "UserName") .ToListAsync(); // 3.1) 字符串列名排序(升序/降序) var columns2 = await conn.QuerySet<User>() .Select("Id", "UserName") .OrderBy("Id", "UserName") .ToListAsync(); var columns3 = await conn.QuerySet<User>() .Select("Id", "UserName") .OrderByDescending("Id", "UserName") .ToListAsync(); // 3.2) GroupBy(分组) // 注意:GroupBy 必须配合 Select("Col1", ...) 或 Select(projection) 指定返回列 var grouped = await conn.QuerySet<User>() .Select("Age") .GroupBy(u => u.Age) .ToListAsync(); // 3.3) 聚合 + Having(推荐:投影 + SqlFunc) // 注意:聚合函数通过 SqlFunc.* 在表达式中声明,用于生成 SQL(不会在运行时执行)。 // 典型生成 SQL 形态: // SELECT [Age] AS [Age], COUNT(*) AS [Cnt] FROM [Users] GROUP BY [Age] HAVING (COUNT(*) > @value) var grouped2 = await conn.QuerySet<User>() .Select(u => new { u.Age, Cnt = SqlFunc.Count() }) .GroupBy(u => u.Age) .Having(u => SqlFunc.Count() > 1) .ToListAsync(); // 4) 兼容方式(不推荐):SelectColumns(...) 已标记 Obsolete,返回“部分字段”的实体,易误用 var legacyColumns = await conn.QuerySet<User>() .SelectColumns(u => new { u.Id, u.UserName }) .ToListAsync(); // 5) JOIN(Inner / Left / Right) // 注意:JOIN 需要你提供 ON 条件(表达式树或原生 SQL 片段) var joined = await conn.QuerySet<User>() .InnerJoin<Role>((u, r) => u.RoleId == r.Id) .Where<Role>((u, r) => u.IsActive && r.IsEnabled) // 双表 WHERE .Select<Role>((u, r) => new { u.Id, u.UserName, r.RoleName }) // JOIN 投影(同时引用两张表;匿名类型无需写 object) .Distinct() .ToListAsync(); // 5.1) JOIN + 原生 SQL WHERE(复杂场景兜底) var joined2 = await conn.QuerySet<User>() .LeftJoin<Role>((u, r) => u.RoleId == r.Id) .Where("[Role].[RoleName] LIKE @name", new { name = "%Admin%" }) .ToListAsync(); // 5.2) 两次 JOIN + 三表投影 + 三表 WHERE // 注意:第二次 JOIN 可以用“已 Join 的表”作为左表(例如 Role -> Dept) var joined3 = await conn.QuerySet<User>() .InnerJoin<Role>((u, r) => u.RoleId == r.Id) .InnerJoin<Role, Dept>((r, d) => r.DeptId == d.Id) // 以 Role 作为左表关联 Dept .Select<Role, Dept>((u, r, d) => new { u.Id, u.UserName, r.RoleName, d.DeptName }) .Where<Role, Dept>((u, r, d) => u.IsActive && r.IsEnabled && d.IsEnabled) .ToListAsync(); // 6) UNION / UNION ALL(合并结果集) // 注意:UNION 要求两侧 SELECT 的列数量/类型可兼容;库不会自动对齐列。 // 另外,为避免两侧参数名冲突(例如都生成 @Age / @w0_0),UNION 会自动对每个子查询做参数前缀隔离。 var unionList = await conn.QuerySet<User>() .Where(u => u.Age > 18) .UnionAll( conn.QuerySet<User>().Where(u => u.Age > 20)) .OrderBy("Id") // UNION 结果集列名/别名(只允许“简单标识符”) .Paging(0, 20) // SqlServer/Oracle:UNION 分页必须先 OrderBy(...) .ToListAsync();删除(Delete)
// 1) 条件删除 var affected = await conn.DeleteSet<User>() .Where(u => u.IsActive == false) .ExecuteAsync(); // 2) 按主键删除 var affected2 = await conn.DeleteSet<User>() .ByKey(1) .ExecuteAsync();软删除(Soft Delete)
简化策略(推荐):
- 物理删除:
Delete(...) / DeleteSet<T>()永远执行DELETE。 - 逻辑删除(软删):显式调用
SoftDelete(...) / SoftDeleteSet<T>(),并且实体必须标注[SoftDelete];否则会抛出异常(避免误以为已软删)。 - 查询过滤:全局开关
FilterSoftDeleted控制是否过滤已删除数据;默认不过滤(查询包含已删除数据)。过滤仅对标注了[SoftDelete]的实体生效。
// 0) 实体声明软删除:可以标在“实体类”或“软删字段属性”上 // - 推荐标在属性上:无需配置 ColumnName,默认使用属性名(或 [Column] 映射名) // - Flag 模式默认值:NotDeletedValue=0,DeletedValue=1 // // class User // { // [Key] // public int Id { get; set; } // // [SoftDelete] // 默认 Mode=Flag,NotDeletedValue=0,DeletedValue=1 // public int IsDeleted { get; set; } // } // 1) 启用软删除(建议在启动阶段配置一次) // - DI 风格(推荐):在 Program.cs 里配置,可与 SQL 日志一起集中配置 // builder.Services.AddSmartDapper(configureSoftDelete: o => // { // o.FilterSoftDeleted = true; // 开启后:查询会自动排除“已删除”(仅对标注了 [SoftDelete] 的实体生效) // }); // // - 非 DI 场景:直接配置 DapperExtensions.ConfigureSoftDelete(o => { o.FilterSoftDeleted = true; // 开启后:查询会自动排除“已删除”(仅对标注了 [SoftDelete] 的实体生效) }); // 2) 查询:默认包含已删除;当 FilterSoftDeleted=true 时自动排除已删除 var list = await conn.QuerySet<User>() .Where(u => u.IsActive) .ToListAsync(); // 3) 物理删除(DELETE) await conn.DeleteSet<User>().ByKey(1).ExecuteAsync(); // 4) 逻辑删除(SoftDelete):实体必须标注 [SoftDelete],否则会抛异常 await conn.SoftDeleteSet<User>().ByKey(1).ExecuteAsync(); // 5) 恢复(反软删) await conn.RestoreSet<User>().ByKey(1).ExecuteAsync();插入(Insert)
// 1) 自动创建实体并填充 var newId = await conn.InsertSet<User>() .Fill(u => { u.UserName = "Alice"; u.IsActive = true; }) .ExecuteAndGetIdAsync(); // 2) 基于已有实体实例 var user = new User { UserName = "Bob", IsActive = true }; await conn.InsertSet(user).ExecuteAsync();更新(Update)
// 1) 推荐:对象初始化器(表达式树提取值) var rows = await conn.UpdateSet<User>() .Set(u => new User { UserName = "Alice", IsActive = true }) .Where(u => u.Id == 1) .ExecuteAsync(); // 2) 也支持:匿名对象(字段名按“属性名/列名”处理) var rows2 = await conn.UpdateSet<User>() .Set(new { UserName = "Alice", IsActive = true }) .Where(u => u.Id == 1) .ExecuteAsync();Fluent 的已知限制/注意事项
- 分页与排序:
QuerySet<T>().Paging(skip, take)目前不支持与自定义OrderBy(...)同用(分页 SQL 已内置排序规则)。 - 分页与排序(降序):同样不支持与
OrderByDescending(...)同用。 - 分页与分组:
QuerySet<T>().Paging(skip, take)目前不支持与GroupBy(...)同用。 - DISTINCT 注意事项:
Distinct()会对整行去重,可能带来额外开销;并且在不同数据库上与ORDER BY/分页组合时存在语义差异,请按业务谨慎使用。 - 分组限制:
GroupBy(...)需要显式指定返回列(Select("Col1", ...)或Select(projection));不支持“全字段 + GroupBy”。 - Having 限制:
Having(...)需要显式指定返回列(Select("Col1", ...)或Select(projection));通常应与GroupBy(...)配合使用。- 表达式版
Having(predicate)支持SqlFunc.Count/Sum/Avg/Min/Max/CountDistinct等聚合函数声明; - 复杂场景可用
Having("COUNT(*) > @min", new { min = 1 })直接写 SQL 片段(注意自行保证安全)。
- 表达式版
- 投影限制:投影链(
Select(projection))目前不支持Paging(...),也不支持与SelectColumns(...)混用(SelectColumns已标记 Obsolete,仅为兼容保留)。 - JOIN 投影限制:
Select<TJoin>((t, j) => ...):支持1 个 JOIN;Select<TJoin1, TJoin2>((t, j1, j2) => ...):支持2 个 JOIN;- 当前版本
Where(predicate)的多表谓词仅支持2 表/3 表,且第一个参数必须是主表T。
- UNION 限制/注意:
Union/UnionAll要求两侧查询的列列表结构兼容(数量/类型),库不会自动补齐/对齐列;- UNION 的
OrderBy("Col")仅支持简单列名/别名(不支持Table.Col这类带点号形式); - SqlServer/Oracle 下 UNION 的
Paging(skip,take)需要先OrderBy(...)(否则无法生成 OFFSET/FETCH)。
- First/Single:
FirstOrDefault()/SingleOrDefault()不会强制加TOP/LIMIT,仅对结果集做“取第一条/单条”的行为(多条命中时SingleOrDefault会抛异常)。
多数据库支持
- SQL Server:
SqlGeneratorFactory.CreateSqlServer<T>() - MySQL:
SqlGeneratorFactory.CreateMySql<T>() - SQLite:
SqlGeneratorFactory.CreateSqlite<T>()