公司动态
FlexLabs.Upsert 架构揭秘:RelationalUpsertCommandRunner 的 SQL 生成与参数化机制
FlexLabs.Upsert 架构揭秘RelationalUpsertCommandRunner 的 SQL 生成与参数化机制【免费下载链接】FlexLabs.UpsertFlexLabs.Upsert is a library that brings UPSERT functionality to common database providers for Entity Framework in their respective native SQL syntax项目地址: https://gitcode.com/gh_mirrors/fl/FlexLabs.UpsertFlexLabs.Upsert 是一个为 EF Core 带来Upsert存在即更新不存在则插入能力的开源库它针对每种数据库提供原生 SQL 语法的插入或更新语句——本文带你快速看懂其核心类RelationalUpsertCommandRunner是如何一步步生成 SQL并安全参数化的。它解决什么问题在 EF Core 中做「插入或更新」通常要自己写原生 SQL 或做两次查询代码繁琐且容易写错。FlexLabs.Upsert 用一个链式 API 就解决了DataContext.DailyVisits .Upsert(new DailyVisit { UserID userID, Date today, Visits 1 }) .On(v new { v.UserID, v.Date }) .WhenMatched(v new DailyVisit { Visits v.Visits 1 }) .RunAsync();入口扩展方法定义在 UpsertExtensions.cs它并不直接拼 SQL而是创建一个UpsertCommandBuilder最终把活儿交给「命令运行器」。命令运行器一库一实现的插件化设计整个架构的核心是一套运行器Runner模式层级类型职责接口层IUpsertCommandRunner声明Run/RunAsync/RunAndReturn四个执行入口基础层UpsertCommandRunnerBase抽象骨架统一Supports(providerName)路由逻辑关系库层RelationalUpsertCommandRunner通用 SQL 生成 参数化流水线本文主角方言层SqlServerUpsertCommandRunner等 6 个各数据库的MERGE/ON CONFLICT方言实现内置运行器在 DefaultRunners.cs 中一次性注册覆盖六种场景数据库生成的原生语法SQL Server / OracleMERGE INTO …PostgreSQL / SQLiteINSERT … ON CONFLICT DO UPDATEMySQLINSERT … ON DUPLICATE KEY UPDATE内存测试直接操作集合请求到来时框架根据当前 Provider 名称匹配Supports(providerName)选中对应运行器你甚至可以用 UpsertServiceExtensions.cs 中的ReplaceUpsertCommandRunnerTRunner()注入自己的运行器。SQL 生成的三步流水线 RelationalUpsertCommandRunner把「实体集合 → 可执行 SQL」拆成三个清晰的阶段核心在 RelationalUpsertCommandRunner.cs 的 PrepareCommand① 建表模型带缓存按「运行器类型 实体类型」缓存RelationalTable见 TableCache 定义 与 RelationalTable.cs一次性解析出该实体的所有列——普通列、JSON 映射的 JSON 列、以及 Owned 实体的内联列避免每次执行都重复反射元数据。② 表达式解析Lambda → 已知值WhenMatched/UpdateCondition传入的是 C# Lambda 表达式由 ExpressionParser.cs 遍历成统一的中间表示ConstantValue—— 字面量不内联进 SQL而是登记为参数PropertyValue—— 列引用生成时带上源表/目标表前缀KnownExpression—— 比较、三目、COALESCE等表达式树③ 方言生成GenerateCommand每个数据库运行器实现抽象方法 GenerateCommand。以 SqlServer 实现 为例最终拼出类似MERGE INTO [表名] WITH (HOLDLOCK) AS [T] USING ( VALUES (p0, p1, p2) ) AS [S] (…) ON [T].[UserID] [S].[UserID] WHEN NOT MATCHED BY TARGET THEN INSERT (…) VALUES (…) WHEN MATCHED THEN UPDATE SET [Visits] [S].[Visits] p3;而 PostgreSQL 实现 则生成INSERT … ON CONFLICT (…) DO UPDATE SET …。同一份业务代码不同方言各得其所。参数化机制为什么你的 SQL 注入不了这是本库最值得学习的部分全部值都走参数通道SQL 里只出现占位符统一参数命名基类提供 Parameter(index) 方法默认p0、p1…SQL 文本中只引用编号常量值永远不进字符串拼接。索引统一分配PrepareCommand在分批前给每个ConstantValue写死ArgumentIndexL146-L147SQL 与参数列表严格对齐。类型映射创建 ADO.NET 参数执行时 PrepareDbCommandArgument 借助 EF 的IRelationalTypeMappingSource为每个参数创建带正确数据库类型的DbParameterJSON 列、表达式常量也各有专门的映射分支。智能分批不同数据库对单条语句的参数数量有硬上限如 SQL Server 限 2090 个、PostgreSQL 32767 个PrepareCommand会按MaxQueryParams自动把大批量实体切成多条命令循环执行L129-L154既绕过限制又不改变业务语义。名称转义表名、列名统一经EscapeName处理SQL Server 用方括号、PostgreSQL 用双引号杜绝保留字冲突。执行落点也很干净同步走ExecuteSqlRaw异步走ExecuteSqlRawAsyncRun 与 RunAsync 对比RunAndReturn则用FromSqlRaw把OUTPUT inserted.*/RETURNING *的结果直接映射回实体并同步回变更跟踪器。如何扩展接入新的数据库 Provider 得益于模板方法模式扩展一个新数据库只需四步继承RelationalUpsertCommandRunner实现GenerateCommand写方言 SQL、EscapeName名称转义覆写SourcePrefix/TargetPrefix源表/目标表列前缀如 SQL Server 的[S]./[T].用ReplaceUpsertCommandRunner注册进DbContextOptionsBuilder空值比较、COALESCE、CASE WHEN等通用片段则由基类的 ExpandExpression 免费复用新方言运行器往往只需百来行代码。写在最后FlexLabs.Upsert 的架构可以概括为一句话通用流水线列解析 → 表达式树 → 参数化 可插拔方言层GenerateCommand。想继续深入可以沿着这几个文件读源码RelationalUpsertCommandRunner.cs、ExpressionParser.cs 以及测试目录下的 SqlServerUpsertCommandRunnerTests.cs——后者几乎是对 SQL 生成逻辑逐条的「活文档」。【免费下载链接】FlexLabs.UpsertFlexLabs.Upsert is a library that brings UPSERT functionality to common database providers for Entity Framework in their respective native SQL syntax项目地址: https://gitcode.com/gh_mirrors/fl/FlexLabs.Upsert创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考