FlexLabs.Upsert 源码深潜:表达式解析器如何把 C Lambda 翻译成原生 SQL
FlexLabs.Upsert 源码深潜表达式解析器如何把 C# Lambda 翻译成原生 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 是一个为 Entity Framework CoreEF Core提供 UPSERT插入或更新功能的开源库它能按各数据库的原生 SQL 语法生成一条存在即更新、不存在则插入的语句。本文将深潜其核心源码——表达式解析器带你快速看懂 C# Lambda 表达式是如何一步步被翻译成原生 SQL 的是理解 EF Core Upsert 实现原理的完整指南。一、为什么需要表达式解析器EF Core 本身没有 Upsert 能力SQL Server 有MERGEPostgreSQL 有ON CONFLICTMySQL 有ON DUPLICATE KEY每家方言都不同。FlexLabs.Upsert 的解法是让用户用最自然的 C# Lambda 描述要更新哪些字段、更新成什么值、满足什么条件才更新由表达式解析器把 Lambda 编译成中间节点再由各数据库专属的 Runner 把这些节点渲染成原生 SQL。整个流水线只有一条公式Lambda → 已知值KnownValue树 → 原生 SQL。二、整体架构三层流水线层次关键文件职责解析层src/FlexLabs.EntityFrameworkCore.Upsert/Internal/Expressions/UpdateExpressionVisitor.cs遍历表达式树识别参数、成员、常量、运算符组织层src/FlexLabs.EntityFrameworkCore.Upsert/Internal/Expressions/ExpressionParser.cs把结果整理成列 → 值的映射PropertyMapping渲染层src/FlexLabs.EntityFrameworkCore.Upsert/Runners/RelationalUpsertCommandRunner.cs把中间节点翻译成 SQL 片段与参数各数据库方言只需实现最底层的 SQL 拼装例如SqlServerUpsertCommandRunner.cs负责MERGE语法、PostgreSqlUpsertCommandRunner.cs负责ON CONFLICT语法解析与组织逻辑则完全复用。三、第一层UpdateExpressionVisitor 遍历表达式树Upsert 的更新表达式签名是ExpressionFuncTEntity, TEntity, TEntity——两个TEntity参数分别代表数据库中的已有行existing和本次传入的新值incoming返回值即更新结果。UpdateExpressionVisitor继承自 EF 的ExpressionVisitor核心逻辑集中在几个方法见UpdateExpressionVisitor.cs的 L24-L92、L157-L192VisitParameterL143-L155区分左右两个参数生成ParameterValue标记这是后续判断引用哪一边列的基础VisitMemberL157-L192当访问的是参数上的属性时通过table.FindColumn()查出真实列元数据生成PropertyValue——从此 C# 属性名变成了数据库列名VisitBinaryL24-L92处理四则运算、比较、逻辑运算。亮点是常量折叠如果两边都是常量比如1 2直接用反射在 C# 侧算好只留下一个ConstantValueSQL 里就不会出现无谓的运算VisitMethodCall / VisitUnaryL15-L22、L112-L141普通方法调用如Math.Max(a, b)两侧为常量时和类型转换同样折叠为常量。五种已知值中间节点 解析的产物全部实现统一接口IKnownValue.csL6-L19提供GetConstantValues()与GetPropertyValues()两个收集方法节点类含义对应 SQLConstantValue字面量/折叠后的计算结果参数占位符p0PropertyValue对某列的引用含哪一边标记带前缀的列名KnownExpression运算节点Add、Coalesce、Conditional…运算符、COALESCE、CASE WHENBindingValue/MemberBinding拥有型实体Owned Entity的属性初始化展开为多列映射ParameterValue两个 Lambda 参数本身列引用依据四、第二层ExpressionParser 组织列映射ExpressionParser.cs对外只暴露三个入口ParseUpdateExpressionL23-L33解析对象初始化器形式的更新表达式逐条生成PropertyMapping列 值GetUpdateMappingsL9-L21未提供更新表达式时的默认行为——更新除匹配列、排除列外的全部列ParseUpdateConditionExpressionL36-L51把FuncTEntity, TEntity, bool更新条件解析成一棵KnownExpression树将来会渲染成ON CONFLICT DO UPDATE ... WHERE或MERGE WHEN子句。两个值得学习的设计细节常量绑定目标列ApplyColumnToConstantsL136-L151会把常量挂载到它即将写入的列上。这样生成参数时能借用该列的关系类型映射——典型场景是 Npgsql 要求UInt64参数显式声明类型列映射可把它转成decimal表达式编译器兜底遇到解析器不认识的复杂表达式若配置了UseExpressionCompiler见UpsertCommandArgs.cs的 L19会走GetValueCompiledUpdateExpressionVisitor.csL257-L270直接把子树编译成 C# 委托执行取回结果作为常量参数。慢一点但覆盖面更广——这是一个默认快路径、可配慢路径的优雅权衡。五、第三层把中间节点渲染成原生 SQLRelationalUpsertCommandRunner.cs是渲染层的中枢。PrepareCommandL102-L155串起全流程建表元数据缓存 → 调解析器 → 按数据库参数上限MaxQueryParamsOracle 只有 32/100 的限制把实体集分批每批输出一条完整 SQL 和参数列表。真正的翻译发生在ExpandValue/ExpandExpressionL163-L265中间节点渲染结果示例PropertyValue已有行目标前缀 转义列名如tgt.PricePropertyValue新值行源前缀 列名如src.PriceConstantValuep0占位符L176-L177比较/算术运算left right、left rightL199-L215CoalesceC# 的??COALESCE(a, b)L233-L238ConditionalC# 的? :CASE WHEN ... THEN ... ELSE ... ENDL240-L246与 null 的比较x IS NULL/IS NOT NULLL217-L231、L273-L278AndAlso/OrElseAND/ORL254-L261参数化方面PrepareDbCommandArgumentL392-L422优先使用ConstantValue上挂载的列元数据查找类型映射创建DbParameter保证数值精度与可空类型在各 Provider 下行为一致。最终各方言 Runner 拿到的已经是列名 参数索引 已知表达式树只需填进各自的模板SQL Server 拼MERGE、PostgreSQL 拼INSERT ... ON CONFLICT DO UPDATE、MySQL 拼REPLACE/ON DUPLICATEOracle、SQLite 同理——同一棵表达式树五种原生方言输出这正是各库原生 SQL 语法承诺的落点。六、从使用者视角看它的价值 ✅语法零学习成本写更新逻辑就像写 LINQ 查询existing new Product { Price incoming.Price * 1.2 }这类表达式直接可用单条 SQL 完成 Upsert比先查后写的两次往返更快且避免并发下的幻读与唯一键冲突类型安全常量折叠与列类型绑定发生在编译期SQL 注入无门可入方言自由同一套业务代码在 SQL Server、PostgreSQL、MySQL、Oracle、SQLite 间迁移无需改动更新逻辑。七、总结一张图看懂数据流C# Lambda 表达式 │ ▼ UpdateExpressionVisitor表达式树遍历 常量折叠 IKnownValue 中间树ConstantValue / PropertyValue / KnownExpression │ ▼ ExpressionParser列映射组织、常量绑定列类型 PropertyMapping 列表 更新条件表达式树 │ ▼ 各库 Runner.ExpandValue / ExpandExpression方言渲染 原生 SQLMERGE / ON CONFLICT / ON DUPLICATE ... 参数化命令想继续深挖建议按这条路径阅读源码UpsertExtensions.cs公开 API 入口→UpsertCommandArgs.cs参数模型→Internal/Expressions/目录解析核心→Runners/RelationalUpsertCommandRunner.csSQL 渲染再对照test/FlexLabs.EntityFrameworkCore.Upsert.Tests/Internal/ExpressionTests.cs里的测试用例就能看到每一类 Lambda 的翻译结果断言。【免费下载链接】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),仅供参考