EFCore与EF6:ORM技术深度解析
前言
ORM 是 Object Relational Mapping 的缩写,译为“对象关系映射”,是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。它解决了对象和关系型数据库之间的数据交互问题,ORM的作用是在关系型数据库和业务实体对象之间作一个映射,这样我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
为什么使用EF
在软件开发中,选择EF(Entity Framework) 通常是因为它作为微软推出的 ORM(对象关系映射)框架,能有效简化数据访问层的开发,提升开发效率并降低维护成本。以下从多个维度详细说明选择 EF 的原因:
1. 简化数据访问代码,提高开发效率
-
告别手写 SQL:EF 通过将数据库表映射为 C#/VB.NET中的实体类,让开发者可以用面向对象的方式(如
dbContext.Users.Where(u => u.Age > 18)
)操作数据,而非直接编写 SQL 语句,减少了重复的 CRUD(增删改查)代码。 -
自动生成 SQL:EF 会根据开发者编写的 LINQ 查询或 Lambda 表达式,自动转换为对应的 SQL 语句,避免了手动写 SQL 可能出现的语法错误或性能问题(尤其对 SQL 不熟悉的开发者)。
2. 强大的 ORM 功能,支持多种数据库
-
跨数据库兼容性:EF 支持 SQL Server、MySQL、PostgreSQL、Oracle 等多种主流数据库,只需更换配置(如连接字符串),即可实现数据库的切换,降低了对特定数据库的依赖。
-
丰富的映射关系:支持一对一、一对多、多对多等复杂的表关系映射,开发者无需手动处理外键关联,只需通过实体类的属性(如
public List<Order> Orders { get; set; }
)定义关系,EF 会自动维护关联逻辑。
3. 灵活的开发模式,适应不同场景
-
数据库优先(Database First):如果数据库已存在,EF 可通过逆向工程自动生成实体类和数据上下文,快速适配现有数据库。
-
模型优先(Model First):开发者可先设计实体模型(通过可视化设计器或代码),再由 EF 自动生成数据库表结构,适合从 0 开始的项目。
-
代码优先(Code First):直接通过代码定义实体类和关系,EF 根据代码自动创建或更新数据库(通过迁移功能),是目前最流行的模式,尤其适合敏捷开发和 DDD(领域驱动设计)。
4. 内置迁移功能,轻松管理数据库版本
-
EF 的迁移(Migrations) 功能可跟踪实体类的变更,并生成对应的 SQL 脚本用于更新数据库,避免了手动修改表结构可能导致的数据丢失或不一致。
-
支持版本回滚,可随时将数据库恢复到之前的状态,方便团队协作和环境部署(如开发、测试、生产环境的数据库同步)。
5. 与.NET 生态深度集成,降低学习成本
-
作为微软官方框架,EF 与.NET Core/.NET 5+、ASP.NET Core 等技术无缝集成,无需额外配置即可使用,且文档丰富、社区活跃。
-
开发者可直接使用 LINQ(.NET 中的查询语言)操作数据,无需学习新的查询语法,对熟悉.NET 的开发者非常友好。
6. 性能可优化,满足大多数业务需求
-
虽然 EF 的自动生成 SQL 可能在极端场景下不如手写 SQL 高效,但通过延迟加载(按需加载关联数据)、贪婪加载(
Include
方法预加载)、显式加载等功能,可有效优化查询性能。 -
支持原生 SQL 注入(
FromSqlRaw
),对于复杂查询,可直接编写 SQL 并与 EF 结合使用,兼顾灵活性和效率。
7. 成熟稳定,适合企业级应用
-
经过多年迭代,EF 已非常成熟(最新版本为 EF Core 8),广泛应用于企业级项目,能应对高并发、大数据量等场景。
-
支持事务管理、缓存机制(如二级缓存)、异步操作(
async/await
)等企业级特性,满足生产环境需求。
总结
选择 EF 的核心原因是平衡了开发效率、灵活性和稳定性。对于大多数.NET 项目,尤其是需要快速迭代、跨数据库支持或团队中 SQL 经验参差不齐的情况,EF 能显著减少开发工作量,让开发者更专注于业务逻辑而非数据访问细节。当然,在对性能有极致要求(如高频交易系统)或 SQL 逻辑极其复杂的场景,也可结合手写 SQL 使用,EF 的灵活性使其能兼容这种混合模式。
EF/EF Core
比较 EF Core 和 EF6
Entity Framework Core (EF Core) 是适用于 .NET 的新式对象数据库映射器。 它支持 LINQ 查询、更改跟踪、更新和架构迁移。
Entity Framework 6 (EF6) 是专为 .NET Framework 设计的对象关系映射器,但支持 .NET Core。 EF6 是一款受支持的稳定产品,但已经不再对其进行积极开发。
EF6
安装
打开NuGet管理器,搜索EntityFramework
并安装即可
创建模型
创建模型有两种方案
-
使用 Code First:开发者编写代码来指定模型。 EF 基于实体类和开发者提供的其他模型配置,在运行时生成模型和映射。
-
使用 EF 设计器:开发者使用 EF 设计器进行绘制以指定模型。 生成的模型以 XML 格式存储在具有 EDMX 扩展名的文件中。 应用程序域对象通常从概念模型中自动生成。使用EF设计器比较简单,只需要点点点就可以了,这里就贴一个微软官方的文档链接吧
这两种方案都可用于定位现有数据库或创建新数据库。其实就是自己编写实体类和自动生成实体类的区别罢了,重点说自己编写实体类。
-
根据数据表的关系创建实体类,这些数据表可以存在也可以不存在,如果不存在,在执行查询时会自动创建数据库、数据表、建立关联,如下创建了两个实体类
// 一个实体类对应一个数据表,类名应该和表名相同 public class Blog {public int BlogId { get; set; }public string Name { get; set; } // 一个ICollection<T>类型的属性,表示一个一对多的关联关系,这个表示Post表中有一个外键关联着当前这张表的主键public virtual List<Post> Posts { get; set; } } public class Post {public int PostId { get; set; }public string Title { get; set; }public string Content { get; set; } // 外键列,使用表名+Id的格式表示该列存储的是另一个表的主键public int BlogId { get; set; }// 存储外键对象的属性,关联的表中对应的数据将会存储到该属性中public virtual Blog Blog { get; set; } }
-
创建
DbContext
类的派生类,所有对数据库的操作都是使用这个类的实例来实现的public class BloggingContext : DbContext {// 调用基类的构造,并传递数据库连接字符串// 也可以指定一个连接字符串的名字,如:Abc,就需要在App.Config中配置对应的连接字符串,如下public DB() : base("连接字符串"){// 为操作日志设置方法,每个操作都会调用这个方法并输出日志Database.Log = Console.WriteLine;}// 每一张表对应着一个DbSet<T>类型的属性,用于操作数据库public DbSet<Blog> Blogs { get; set; }public DbSet<Post> Posts { get; set; } }
// App.Config <connectionStrings><add connectionString="Server=.,1433;Uid=sa;Pwd=laoguo66.;Database=abcdefg" name="Abc" providerName="System.Data.SqlClient"/> </connectionStrings> ```
-
接下来就可以进行数据库的读取和写入了
如果你现在还没有这个数据库,需要它帮你创建,那么至少需要进行一次数据库操作之后才会生效
using (DB dB = new DB()) {// 使用LINQ语句执行数据库的查询操作IQueryable<Blog> Blogs = from b in dB.Blogs select b;// 注意:只有当迭代IQueryable时或者调用ToList、ToArray语句时,才会真正的执行查询操作Blog[] a = Blogs.ToArray(); }
-
不出意外的话,如果你没有这个数据库,那么你现在就有了。如果你已经有了,应该是可以查询到数据库的。
数据库迁移
现在可以对模型进行一些更改,当我们进行这些更改时,还需要更新数据库架构。
为此,我们将使用一项称为 Code First 迁移(简称迁移)的功能。
借助迁移功能,我们可以拥有一组有序的步骤,这些步骤介绍如何升级(和降级)数据库架构。 每个步骤(称为迁移)都包含一些描述要应用的更改的代码。
-
打开包管理器控制台,步骤:“工具”->“库包管理器”->“包管理器控制台”
-
在包管理器控制台中运行
Enable-Migrations
命令-
该命令将会把 Migrations 文件夹添加到项目中,其中包含两项:
-
Configuration.cs - 此文件包含 Migrations 用于迁移 BloggingContext 的设置。 你可以在此处指定种子数据、为其他数据库注册提供程序、更改将在其中生成迁移的命名空间等。
-
<timestamp>_InitialCreate.cs - 这是你的第一次迁移,它代表已应用于数据库的更改,将其从空数据库变为包含 Blogs 和 Posts 表的数据库。
-
-
-
现在可以对模型执行一系列的更改,比如向实体类中添加一个属性、删除一个属性等一系列的操作
-
在包管理器控制台中运行
Add-Migration AddUrl
命令。 Add-Migration 命令检查自上次迁移以来的更改,并使用找到的任何更改为新的迁移搭建基架。 我们可以为迁移指定一个名称;在本例中,我们将迁移称为“AddUrl”。你应该在实际的开发中为实体的更改添加更加合适的名字。 -
在包管理器控制台中运行
Update-Database
命令。 此命令将对数据库应用迁移。
自动迁移
自动迁移让你可以使用 Code First 迁移,而无需在项目中针对你进行的每个更改创建代码文件。 并非所有更改都可以自动进行应用 - 例如,列重命名需要使用基于代码的迁移。
可以交错执行自动迁移和基于代码的迁移,但在团队开发场景中不建议这样做。 如果你是使用源代码控制的开发人员团队的一员,则应使用纯自动迁移或纯基于代码的迁移。 鉴于自动迁移的局限性,我们建议在团队环境中使用基于代码的迁移。
可以通过在包管理器控制台中运行 Enable-Migrations –EnableAutomaticMigrations
命令来开启自动迁移。
开启自动迁移之后,我们对实体修改之后只需要执行Update-Database
命令,就可以将实体的更改同步到数据库了,但如果你的修改可能会导致数据库数据丢失,如删除列,则会出现一个错误,需要运行 Add-Migration AddUrl
命令添加一个迁移操作后再执行Update-DataBase
数据注解
可以为实体类添加注解向 EF 提供有关这些类和它们映射到的数据库的更多信息
-
Key
EF默认查找类中名为“Id”的属性,或类名和“Id”的组合,例如“BlogId”。 此属性将映射到数据库中的主键列。使用Key
注解设置某个属性为主键 -
NotMapped
该特性指定某个属性不在数据库中进行映射 -
ConcurrencyCheck
可以标记一个或多个属性,以便在用户编辑或删除实体时,将该属性用于数据库中的并发检查。当进行更新或者删除时,不仅仅会根据主键来定位行进行操作,还会根据更新的列进行筛选,如果在操作中有其他客户端修改了数据库的该数据,这个更新将会失败 -
Table["表名"]
更改某个类要映射到数据库的表名 -
Column("列名", [TypeName="类型"])
更改某个属性映射到数据表的列名,也可以指定数据的类型 -
[Index]
设置某个列为索引列,将会在数据库上为其创建索引 -
[Index("IdIndex")]
默认情况下,索引将命名为 IX_<属性名称>。 也可以为索引指定一个名称。 -
[Index(IsUnique = true)]
默认情况下,索引是非唯一的,但可以使用IsUnique
命名参数来指定索引应该是唯一的。 -
[Index(Order=N)]
创建多列索引时,需要为索引中的列指定顺序 -
[ForeignKey("外键列名")]
例如现在有如下两张表,Post将会按照约定去关联Blog表中的BlogId主键,但是Blog中没有这个列,EF会为我们自动在数据库中创建一个Blog_ABC的列来进行外键关联,这明显不是我们想要的。可以使用
ForeignKey
特性解决这个问题,如下代码中的第16行-
public class Blog {[Key]public int ABC { get; set; }public string Name { get; set; }public string Url { get; set; }public virtual List<Post> Posts { get; set; } }public class Post {public int PostId { get; set; }public string Title { get; set; }public string Content { get; set; }public int BlobId { get; set; }[ForeignKey("BlobId")]public virtual Blog Blog { get; set; } }
-
基于现有数据库创建实体类
如果你已经创建好数据库了,只需要点点点就可以创建数据库对应的实体类了
-
“项目”->“添加新项...”
-
从左侧菜单中选择“数据”,然后选择“ADO.NET 实体数据模型”
-
输入名称并单击“确定”
-
此操作将启动实体数据模型向导
-
选择“数据库中的 Code First”并单击“下一步”
如果数据库发生了变换怎么办?
只能手动根据数据库的更改来更新实体类
使用设计器
-
“项目”->“添加新项...”
-
从左侧菜单中选择“数据”,然后选择“ADO.NET 实体数据模型”
-
输入名称并单击“确定”
-
此操作将启动实体数据模型向导
-
选择“来自数据库的EF设计器”或者“空EF设计器模型”并单击“下一步”
-
后面就是图形化操作,点点点就行了
查询数据
使用实体框架查询数据的各种方法,包括 LINQ 和 Find 方法
using (var context = new BloggingContext()) {// 查询Blogs中所有名字为B开头的数据var blogs = from b in context.Blogswhere b.Name.StartsWith("B")select b; // 查询Blogs中名字为ADO.NET Blog的第一条数据var blog = context.Blogs.Where(b => b.Name == "ADO.NET Blog").FirstOrDefault();、// 如果需要根据主键查询数据,使用Find即可var b = db.books.Find(22); }
修改实体
修改实体直接操作对应的对象即可,操作完成之后调用SaveChanges
方法将操作同步到数据库
-
DbSet.Add
-
DbSet.AddRange
-
DbSet.Remove
-
DbSet.RemoveRange
using (var context = new BloggingContext()) {Blogs blog = new Blogs(){ Name = "Test" }context.Blogs.Add(new Blogs(){ Name = "Test" });context.SaveChanges();Console.WriteLine("新增的id列:" + blog.Id); }
EFCore
EFCore 6.0入门看这篇就够了 - Mamba24⁸ - 博客园
Entity Framework (EF) Core 是轻量化、可扩展、开源和跨平台版的常用 Entity Framework 数据访问技术。
EF Core 可用作对象关系映射程序 (O/RM),这可以实现以下两点:
-
使 .NET 开发人员能够使用 .NET 对象处理数据库。
-
无需再像通常那样编写大部分数据访问代码。
安装
-
EFCore核心包的安装:在efcore中搜索并安装
Microsoft.EntityFrameworkCore.SqlServer
,因为我这里连接的是sqlServer数据库,如果是其他数据库则安装其他的包。数据库提供程序。 -
工具安装:执行项目中与 EF Core 相关的任务,例如创建和应用数据库迁移,或基于现有数据库创建 EF Core 模型,在nuGet中搜索并安装
Microsoft.EntityFrameworkCore.Tools
-
第二个步骤安装的工具需要在程序包管理器控制台才能使用,打开的步骤为:工具 => NuGet程序包管理器 => 程序包管理器控制台
-
运行
Get-Help about_EntityFrameworkCore
命令来验证安装是否成功,如果出现如下的提示,则说明安装完成_/\__---==/ \\___ ___ |. \|\| __|| __| | ) \\\| _| | _| \_/ | //|\\|___||_| / \\\/\\
-
工具包相关的命令详见EF Core 工具参考(包管理器控制台)- EF Core | Microsoft Learn
数据库和DB类的创建
数据库优先
如果你已经有数据库了,或者更喜欢在数据库管理工具中创建数据表和表关系时,可以使用这种方式
在程序包管理器控制台中执行Scaffold-DbContext
命令来生成所需要的DbContext和Model类,如下
Scaffold-DbContext -Connection "Server=.,1433;Uid=sa;Pwd=laoguo66.;Database=BookManager;TrustServerCertificate=true" -Provider "Microsoft.EntityFrameworkCore.SqlServer" -OutputDir "DB"
更多参数点击这里查看
不出意外的话,你应该可以得到和该数据库相关的DbContext类和实体类,接下来就可以进行数据库操作了
代码优先
代码优先也就是手动创建数据库相关的DbContext类和实体类,然后根据这些类创建数据库
-
创建相关类,并指定各个表的关系
using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; // 数据库上下文类 public class BloggingContext : DbContext {// 每一个数据表使用一个 DbSet<T> 类型的属性表示public DbSet<Blog> Blogs { get; set; }public DbSet<Post> Posts { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options)=> options.UseSqlServer("你的数据库链接字符串"); } // Blog表的实体类 public class Blog {public int BlogId { get; set; }public string Url { get; set; } // 当某个表被其他表关联时,一般是一对多,需要在主键实体类中创建 List<外键表> 类型的属性存储他们public List<Post> Posts { get; } = new(); } // Post表的实体类 public class Post {public int PostId { get; set; }public string Title { get; set; }public string Content { get; set; } // 指定当前列外键列,主键为Blog表的BlogId列public int BlogId { get; set; }// 用于存储外键实例public Blog Blog { get; set; } }
-
在“包管理器控制台(PMC)”中,运行以下命令,根据类生成数据库,相关命令的作用已经在自动迁移章节说过了,功能是相同的
Add-Migration
的作用 等价Enable-Migrations
Add-Migration InitialCreate Update-Database
DbContext 配置和初始化
通过重写DbContext类的OnConfiguring方法来对上下文进行配置,比如指定数据库和数据库连接字符串
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {optionsBuilder// 配置日志输出函数和输出级别.LogTo((v) => Debug.WriteLine(v), LogLevel.Information)// 指定连接字符串.UseSqlServer("Server=.,1433;Uid=sa;Pwd=*****;Database=BookManager;TrustServerCertificate=true"); }
数据库系统 | 配置示例 | NuGet 程序包 |
---|---|---|
SQL Server 或 Azure SQL | .UseSqlServer(connectionString) | Microsoft.EntityFrameworkCore.SqlServer |
Azure Cosmos DB | .UseCosmos(connectionString, databaseName) | Microsoft.EntityFrameworkCore.Cosmos |
SQLite | .UseSqlite(connectionString) | Microsoft.EntityFrameworkCore.Sqlite |
EF Core 内存中数据库 | .UseInMemoryDatabase(databaseName) | Microsoft.EntityFrameworkCore.InMemory |
PostgreSQL* | .UseNpgsql(connectionString) | Npgsql.EntityFrameworkCore.PostgreSQL |
MySQL/MariaDB* | .UseMySql(connectionString) | Pomelo.EntityFrameworkCore.MySql |
Oracle* | .UseOracle(connectionString) | Oracle.EntityFrameworkCore |
更多DbContext配置详见
数据注解
-
[NotMapped]
如果不希望在模型中包含某一类型,可以用于类和属性 -
[Table("blogs")]
如果实体类名和表名不同的情况,使用指定数据库名称 -
[Column("blog_id")]
实体类的属性名和列名不通时,使用它指定列名 -
[Column(TypeName = "varchar(200)")]
显式的指定数据库列的类型 -
[MaxLength(500)]
最大长度仅适用于数组数据类型,如string
和byte[]
-
[Required]
指定列为必须的属性 -
[Comment("The URL of the blog")]
为数据列设置备注(注释) -
[Column(Order = 0)]
指定列的顺序 -
[Key]
标志某个属性为主键,一般不用,因为他会自动将Id或者XxxId的列设置为主键
EF(Entity Framework) Core和EF(Entity Framework)6区别对比
Entity Framework (EF) Core 和 Entity Framework 6 (EF6) 是 Microsoft 提供的两种流行的数据访问技术,它们用于在 .NET 应用程序中对数据库进行操作。尽管它们的目标相同,但这两个版本在架构、性能、支持平台和功能上有明显的区别。以下是一些主要的区别对比:
参考文档:EF(Entity Framework) Core和EF(Entity Framework)6区别对比-CJavaPy
支持的平台:
EF6:原始设计为 .NET Framework 的一部分,仅支持 Windows 平台。 EF Core:作为 .NET Core 的一部分被重新设计和构建,支持跨平台,包括 Windows、Linux 和 macOS。它也支持在 .NET Framework 和 .NET 5/6 上运行。 性能:
EF Core:在许多方面进行了优化,比 EF6 提供更好的性能。例如,它在查询优化、内存使用和批量更新操作方面进行了改进。 EF6:尽管在其生命周期中进行了多次优化,但在性能方面通常不及 EF Core。 模型配置:
EF6:主要使用 DbContext 和 OnModelCreating 方法中的 Fluent API 或者属性(Attribute)来配置模型。 EF Core:扩展了 Fluent API,提供了更多的配置选项,并且对数据注解(Data Annotations)的支持也更加丰富。 迁移和数据库支持:
EF Core:支持自动和代码优先的迁移方式。它支持更多类型的数据库,包括非关系数据库。 EF6:支持自动迁移和代码优先迁移,但主要限于 SQL Server 和一些其他关系数据库。 查询能力:
EF Core:引入了更多的 LINQ 查询操作和函数,提高了查询的灵活性和表达能力。 EF6:虽然支持复杂的 LINQ 查询,但在某些复杂情况下可能面临性能问题。 更新和社区支持:
EF Core:作为 .NET Core 的一部分,持续获得更新和改进,社区活跃。 EF6:虽然仍然获得维护更新,但主要集中在修复重要问题上,新功能的添加较少。 API和功能:
EF Core:不是 EF6 的直接升级,缺少一些 EF6 中存在的特性(如 EDMX 文件支持、设计时工具支持),但引入了一些新功能,如全局查询过滤器、简化的事务 API 等。 EF6:具有成熟的功能和广泛的 API 支持,适用于依赖这些特性的现有应用程序。
数据库操作
其他操作和EF6基本相同,此处略
FreeSql
官方文档
LinqToSql
官方文档