5.1 从ADO.NET到ORM的取舍


文档摘要

5.1 从 ADO.NET 到 ORM 的取舍 ADO.NET 是 .NET 数据访问的最底层手工件:自己开连接、写命令、读结果、关资源,换来对 SQL 的完全掌控;ORM(EF Core)用映射与跟踪把这四步自动化,代价是引入一层需要理解的抽象。选型不是信仰问题,是成本问题——本节把两边的成本都摆上桌。 进入数据支线的第一站,先不碰 EF Core,而是亲手写一遍最原始的数据访问。理由很直接:不手工做过一遍,就体会不到 ORM 在替你做什么、也判断不出它什么时候碍事。观察哨本节的视角是"数据访问的四个动作":连接、命令、读取、释放——任何数据层都绕不开这四步。 手工数据访问长什么样 手工代码的控制力货真价实:SQL 一字不改地上库、没有翻译层、连接时机完全自己定。

5.1 从 ADO.NET 到 ORM 的取舍

ADO.NET 是 .NET 数据访问的最底层手工件:自己开连接、写命令、读结果、关资源,换来对 SQL 的完全掌控;ORM(EF Core)用映射与跟踪把这四步自动化,代价是引入一层需要理解的抽象。选型不是信仰问题,是成本问题——本节把两边的成本都摆上桌。

进入数据支线的第一站,先不碰 EF Core,而是亲手写一遍最原始的数据访问。理由很直接:不手工做过一遍,就体会不到 ORM 在替你做什么、也判断不出它什么时候碍事。观察哨本节的视角是"数据访问的四个动作":连接、命令、读取、释放——任何数据层都绕不开这四步。

手工数据访问长什么样

// ADO.NET 风格:四个动作全部手工,以查询商品为例 public Product? FindById(int id) { // 动作一:建连接(using 确保归还) using var conn = new SqliteConnection("Data Source=shop.db"); conn.Open(); // 动作二:写命令——注意参数化占位,绝不拼字符串 using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT Id, Name, Price FROM Products WHERE Id = @id"; cmd.Parameters.AddWithValue("@id", id); // 参数化:防注入的关键 // 动作三:读结果,逐行逐列手工搬运到对象 using var reader = cmd.ExecuteReader(); if (!reader.Read()) return null; return new Product { Id = reader.GetInt32(0), Name = reader.GetString(1), Price = reader.GetDecimal(2) }; } // 动作四:三个 using 结束,命令、读取器、连接按序释放

手工代码的控制力货真价实:SQL 一字不改地上库、没有翻译层、连接时机完全自己定。它的账单同样真实:每张表每列都要手写搬运代码;改一列要同步改 SQL 与搬运两处;连接忘记释放就是事故;插入并取回自增主键这种小事要写一串收尾 SQL。

两层代码之间隔着什么

ORM 的本质是"对象与关系之间的翻译官":你操作对象图(订单带着明细集合),它负责翻成表与连接,再把结果翻回来。翻译之外还捎带三件事:变更跟踪(记住你改过哪些属性,保存时只发差异 SQL)、身份映射(同一主键在一个上下文里只有一个对象实例)、迁移生成(模型改动变成建表改表脚本)。

图 5.1-1 手工层与 ORM 层的职责对照

图 5.1-1 手工层与 ORM 层的职责对照

案例:同一功能,两种实现的对照实验

背景:商品搜索要按关键字与价格区间过滤,团队里"手写派"与"ORM 派"各写一版,比一比可读性与风险点。

操作:手写版(节选):

// 手写版:SQL 拼接 + 手工搬运,控制力强但两处同步 cmd.CommandText = @"SELECT Id, Name, Price FROM Products WHERE Name LIKE @kw AND Price BETWEEN @lo AND @hi"; cmd.Parameters.AddWithValue("@kw", $"%{keyword}%"); cmd.Parameters.AddWithValue("@lo", low); cmd.Parameters.AddWithValue("@hi", high); // ...逐列搬运进 List<Product>

ORM 版:

// ORM 版:同一查询的 LINQ 表达,翻译交给框架 var products = await db.Products .Where(p => p.Name.Contains(keyword) && p.Price >= low && p.Price <= high) .ToListAsync(); // SQL 观察窗(5.3 节)可看到翻译结果与手写版几乎一致

结果:两版发出的 SQL 大体相同,性能差异可忽略。差别全在维护面:手写版改排序字段要动 SQL 与搬运两处;ORM 版改 Lambda 一处即可。但手写版对索引命中的确定性更高——DBA 评审 SQL 时看到的就是最终文本。

解读:ORM 的比较优势在"变更频率高、表多、团队大"时放大;手写的优势在"查询形态特殊、性能极度敏感、SQL 需要评审"时保留。成熟项目的常态是主干用 EF Core、报表与热点查询走逃生门。

变式:EF Core 混用三种形态——LINQ 为主,FromSql 原生 SQL 接实体,SqlQueryRaw 直接取标量或非实体类型。从整段原生到纯 LINQ 是连续光谱,按查询逐条选择,不必全站二选一。

选型论证框架

拿真实维度替信仰站队。我的论证框架四条:项目规模——表过三十、迭代快,搬运代码的成本压垮手工收益;团队构成——DBA 主导评审文化的团队保留 SQL 可见性更重要;查询形态——大量报表、复杂分析 SQL,逃生门比例会很高;人员流动——ORM 的代码风格统一性让新人接手更快。四条打完分,选型结论自然浮出,而且能向团队解释——能解释的选型才是好选型。

问题:老项目能逐步迁移到 EF Core 吗?

能,而且这正是两条路线并存的用武之地:新模块用上下文与 LINQ,存量手工层原样运行,两者通过同一个数据库会话策略(连接串、事务边界)协调。迁移按模块推进,每迁一块补一块集成测试——观察哨的建议是先迁"简单增删改查",把复杂查询留在逃生门里最后处理,风险最小、收益最快兑现。

⚠️ 常见坑:手写路线里最危险的不是麻烦,是"偶尔拼一次字符串"。只要存在一条拼接路径,注入面就存在。参数化是纪律,不是建议——代码评审对拼接 SQL 应当零容忍。

💡 关键直觉:ORM 不是"替你写 SQL 的工具",是"把表的世界翻译成对象世界的常驻翻译官"。翻译官有自己的脾气(翻译规则、跟踪行为),5.2 与 5.3 节都在讲怎么跟它相处。

本节要点回顾

  • 四动作模型:连接、命令、读取、释放是所有数据层的骨架,手工件逐个负责;
  • 参数化是纪律:手工路线的注入风险全部来自拼接,评审零容忍;
  • ORM 三件增值:翻译、变更跟踪、迁移生成,理解它们才能算清收益账;
  • 混用是常态:LINQ 干主干、原生 SQL 走报表与热点,按查询逐条选;
  • 选型框架四条:规模、团队、查询形态、人员流动,可解释的选型才站得住。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U