EF Core 的核心就一个动作:把 SQL 写在 C 里,让数据库干剩下的活。 那会儿写 SQL,你得像指挥一个工兵营。你得拍板把哪块石头搬那会儿,哪块石头放哪,然后去问数据库:“这堆活干完了吗?”你需求写 WHERE 子句、JOIN、GROUP BY,就连还要处理分页和排序。痛点在于,数据库有自己的“黑盒”逻辑,你写的 SQL 跟实际运行的结局时常不在一个频道上。 EF Core 的出现,是这种“人找活干”的彻底终结。它把 SQL 写在了内存里,整个编译过程就在主机上搞定。你只需求定义数据库表的样子,它就能自动把架构师需求的所有 SQL 写出来。说的更直白一点,就是让你“告诉它如何存”,它自己就会“拍板如何查”。 这套系统建立在三层架构之上,但感觉上只有两层:Model 层和 Repository 层。 Model 层就是数据库的镜像。你在 C 里定义 Entity,实际上就是在说数据库表。大量新手当作 Entity 就是实体对象,那是错的。Entity 是数据库表的结构化描述,里面包含了主键、外键、属性类型和计算属性。它是模型的基石,拍板了数据库表长啥样。 Entity 和 Table 的关系特别微妙,但又至关关键。Entity 对应 Table。
要是实体定义错了,数据库就长歪了。
比如你想存一个“订单”表,Entity 里应当有 OrderId, CustomerId, Amount 这些字段。
要是漏了主键要么外键,数据库建表时就会报错,要么生成的表结构彻底不符合你的需求。数据模型不是代码,它是代码形成的物理对象,一旦写进数据库,它就是静止的规范。 接下来是 Repository 层。
这是 EF Core 的灵魂所在。它的任务是把 Entity 转换成 SQL,再把 SQL 的结局转换成 C 的对象。 传统的数据库操作,比如手动写 SQL 查询,要么写成字符串拼接(脏兮兮差),要么写成存过程(耦合度高,难维护)。EF Core 的核心机制就是"Code First",也就是代码优先。你写一个 AddOrder 方式,Entity 里新增了一个 OrderId 字段。EF Core 在编译期就会发现这个新字段,便它自动生成了一张新表,把 OrderId 映射为主键。 然后 EF Core 会生成一套默认的 CRUD 代码(Create, Read, Update, Delete)。当你调用 AddOrder 时,SQL 生成器会构建出相应的查询语句,比如 `INSERT INTO Orders...`。
这些语句会经过语法检查,确保没有拼写毛病。 但后续处理更多时候,我们需求的是“按需生成”,而不是“全量生成”。
比如只想查订单数量,不想查所有订单的详情。
这时候就需求上下文(Context)和 LINQ。 LINQ 是桥接层,它是 C 和 SQL 之间的翻译器。当你用 `.FirstOrDefault()` 要么 `.Where()` 来查询,LinQ 会把这些 API 映射成 SQL 的 `SELECT` 语句。
比如 `.Where((o) => o.Status "Completed")`,EF Core 会把它解析成 `WHERE Status = 'Completed'`。 这里有个细节挺好办被忽略:EF Core 生成代码时,并不是把 LINQ 的结局直接传回去,而是先用 `ToArray()` 把 LINQ 序列化为数组,然后再遍历生成对象。
这一步别看隐蔽,但对性能影响挺大。
特别是在大数据量下,LINQ 生成的 .NET 数组比 SQL 回的结局集(RowSet)要小大量,出于后者包含大量的元数据信息(索引、排序、分组等)。 ```csharp // 一个好办的场景,对比两种获取订单的方式 // 方式一:使用 LINQ var orders = context.Orders .Where(o => o.Status "Completed") .Select(o => new OrderDetail { ProductName = o.ProductName, Quantity = o.Quantity }); // 方式二:使用 SQL (通过 Repository 的 SQL 扩展方式) var ordersWithCount = context.Orders .Where(o => o.Status "Completed") .Count(); ``` 在第二种方式里,EF Core 直接执行了 SQL 查询,就连可能配合 `DbCommand` 直接回数据库的 Rowset,绕过了 LINQ 的序列化过程。
这就是为啥在某些场景下,直接 SQL 查询比 LINQ 查询快两倍的缘由。 另外,EF Core 还有一个隐藏功能:乐观锁。当你给订单表增添 `Increment` 属性时,EF Core 会在生成 SQL 时自动加上 `WHERE @@ROWCOUNT = 0` 这个条件。
这在防止并发冲突时贼有效,并且无需手动维护版本号逻辑。 至于查询优化,EF Core 默认开启的缓存策略是“延迟加载”。你查询一个 User 对象的与此同时,也会把 User 关联的 Orders 一起加载进来,直到你再次触发 `DbContext` 的 `ClearCache()` 要么指定 `AsNoTracking()` 才去销毁它们。
这避免了在内存中重复加载数据,但也带来了内存占用高的难题。 要是不想让实体对象保持连接,而是想获取一个冷数据(比如只查库,查完立马离开内存),EF Core 供给了 `AsNoTracking()` 方式。
这个方式会让实体对象不再追踪数据变更,直接回数据库的原始结局集。
这在大数据量统计场景下特别有用。 监听器(Listener)是 EF Core 当年的杀手锏,目前看更像是历史遗迹。
那会儿它负责自动把 C 对象映射为 SQL,目前这个功能已经内嵌在 EF Core 的上下文中了。
要是你手动在代码里写 `context.Sql("SELECT FROM ...")`,EF Core 根本不会去处理它。 从数据库驱动接口(Dapper)到 EF Core,再到目前的主流 .NET 库,EF Core 的演进路线实际上一直挺明显的,就是越来越倾向于把 SQL 生成逻辑留在主机端,削减客户端代码的维护成本。 最终说点实用的,要是是做大型项目,有时候确实不建议全用 C 生成所有 SQL。
要是你的表结构贼复杂,要么查询逻辑贼特殊(比如涉及复杂的存过程逻辑,要么需求把 SQL 嵌入到 C 字符串里直接执行),手动写 SQL 反而更可控。EF Core 是工具,不是魔法。它处理的是常规业务,对于极端的边缘情况,得靠 SQL 自己的逻辑去兜底。 总结一下,EF Core 的价值不在于写了多少代码,而在于省了多少 SQL 写和改的工夫。它把“写 SQL”变成了“定义表结构”,把“调接口”变成了“写代码”。对于大多数业务场景,这种架构的转变带来的可维护性提升,远超代码本身带来的性能损耗。