简介这份资源是面向.NET开发者的EF Code First实战示例重点演示同一套领域模型如何分别对接SQLServer与MySQL两种数据库。Code First模式允许开发者只编写数据类由EF自动完成建库建表适合正在学习Entity Framework、需要理解跨数据库迁移与配置差异的初中级开发者参考。压缩包共88个文件约13.61MB以cs源码、dll程序集、config配置、xml文档为主另含exe、nupkg包、csproj工程与sln解决方案等完整保留了可编译运行的项目结构。内容围绕CodeFirstByMySQL与EFCodeFirst两个工程展开配合MySql.Data、MySql.Data.Entity及EntityFramework等依赖包直观呈现不同数据库提供程序下的上下文与连接配置写法。目前已有706人学习下载可作为搭建多数据库EF项目的起步模板帮助读者快速对照两种数据库的接入方式与工程组织减少自行摸索成本。1. 从一次数据库迁移翻车说起EF代码优先到底解决了什么同一个 .NET 项目开发阶段用 SQL Server 跑得好好的上线前客户要求换成 MySQL结果Update-Database直接报错字段类型对不上、自增主键行为不一致、迁移脚本里一堆 SQL Server 方言。这个场景我在实际项目里遇到过不止一次也是很多人搜索「EF代码优先实例包含SQLServer与MySQL」时真正想解决的问题能不能用一套 C# 实体类同时喂饱 SQL Server 和 MySQL 两种数据库EF Code First代码优先的核心思路是你先写 C# 实体类和DbContext由 EF 根据模型反向生成数据库表结构而不是先建库建表再生成代码。它把「数据库结构」变成了「代码的一部分」于是迁移、版本控制、多环境切换都变得可管理。这套方案适合中小型业务系统、需要频繁迭代表结构的团队以及要在多种数据库之间做适配的产品。但要注意代码优先不是银弹跨数据库时那些「数据库方言差异」会以各种玄学方式冒出来后面几章我会把踩过的坑一个个拆开讲。2. 环境搭建与第一个 Code First 模型从实体类到建库2.1 三个必装的包和版本选择逻辑EF Code First 在 .NET 生态里有两套主流实现EF6.NET Framework 时代和 EF Core.NET Core / .NET 5。现在新项目基本都选 EF Core因为它对多数据库 provider 的支持更规范。你需要装三类包包名作用选择建议Microsoft.EntityFrameworkCoreEF Core 核心与目标框架版本对齐Microsoft.EntityFrameworkCore.SqlServerSQL Server provider只在需要连 SQL Server 时装Pomelo.EntityFrameworkCore.MySqlMySQL provider社区维护兼容性比官方 MySql.Data 好Microsoft.EntityFrameworkCore.Design迁移工具支持必须装否则dotnet ef命令不可用这里有个血泪经验MySQL 的 provider 不要用 Oracle 官方那个MySql.EntityFrameworkCore它在复杂迁移场景下 bug 较多社区普遍推荐 Pomelo。装包命令# 核心包 设计时工具 dotnet add package Microsoft.EntityFrameworkCore dotnet add package Microsoft.EntityFrameworkCore.Design # 两个数据库 provider 都装上方便切换 dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Pomelo.EntityFrameworkCore.MySql参数说明dotnet add package默认拉最新稳定版生产项目建议显式指定版本号比如--version 8.0.0避免不同环境拉到不同版本导致迁移脚本不一致。2.2 写一个能同时适配两种数据库的实体模型先定义实体。关键点是尽量用两种数据库都支持的类型和约定避免一开始就引入方言特性。public class Product { public int Id { get; set; } // 主键EF 默认识别 Id public string Name { get; set; } string.Empty; public decimal Price { get; set; } // 金额用 decimal两种库都支持 public DateTime CreatedAt { get; set; } public bool IsActive { get; set; } } public class AppDbContext : DbContext { public DbSetProduct Products { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder options) { // 实际项目从配置文件读这里演示两种连接方式 // SQL Server: // options.UseSqlServer(Serverlocalhost;DatabaseShopDb;Trusted_ConnectionTrue;); // MySQL: options.UseMySql( Serverlocalhost;DatabaseShopDb;Userroot;Passwordyourpwd;, ServerVersion.AutoDetect(Serverlocalhost;DatabaseShopDb;Userroot;Passwordyourpwd;) ); } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.EntityProduct(e { e.Property(p p.Name).HasMaxLength(200).IsRequired(); // decimal 精度必须显式指定否则 MySQL 默认精度可能丢小数 e.Property(p p.Price).HasPrecision(18, 2); }); } }逻辑说明OnModelCreating里用 Fluent API 配置约束比 Data Annotation 更灵活也更容易做多数据库适配。HasPrecision(18, 2)这行是重点——如果不写SQL Server 默认decimal(18,2)但 MySQL 可能生成decimal(65,30)之类导致金额字段行为不一致。ServerVersion.AutoDetect会自动探测 MySQL 版本避免手写版本号写错。2.3 生成迁移并建库的完整命令链模型写好后用迁移命令生成建表脚本# 安装 EF 工具全局一次即可 dotnet tool install --global dotnet-ef # 为 SQL Server 生成迁移 dotnet ef migrations add InitialCreate --context AppDbContext # 应用到数据库会真正建库建表 dotnet ef database update --context AppDbContext如果你要同时支持两种数据库常见做法是为每个 provider 维护独立的迁移文件夹通过--output-dir区分dotnet ef migrations add Init_SqlServer --output-dir Migrations/SqlServer dotnet ef migrations add Init_MySql --output-dir Migrations/MySql参数说明--output-dir指定迁移文件输出目录--context指定 DbContext多上下文项目必须写。执行database update时EF 会读取迁移文件里的 C# 代码翻译成对应数据库的 SQL 执行。注意迁移文件一旦生成并提交到版本库就不要手动改否则团队协作时会出现迁移快照不一致。3. 双数据库适配的核心Provider 切换与迁移隔离3.1 用配置文件驱动 Provider 切换硬编码连接字符串是新手常见做法但多环境项目必须把 provider 选择做成可配置。典型做法是在appsettings.json里写{ Database: { Provider: MySql, ConnectionStrings: { SqlServer: Serverlocalhost;DatabaseShopDb;Trusted_ConnectionTrue;TrustServerCertificateTrue;, MySql: Serverlocalhost;DatabaseShopDb;Userroot;Passwordyourpwd; } } }然后在启动时根据Provider字段决定用哪个var dbSection builder.Configuration.GetSection(Database); var provider dbSection[Provider]; var connStr dbSection.GetSection(ConnectionStrings)[provider]!; builder.Services.AddDbContextAppDbContext(options { if (provider SqlServer) options.UseSqlServer(connStr); else options.UseMySql(connStr, ServerVersion.AutoDetect(connStr)); });逻辑说明这样切换数据库只需要改一行配置不用重新编译。ServerVersion.AutoDetect会建立一次连接探测版本生产环境如果启动性能敏感可以改成new MySqlServerVersion(new Version(8, 0, 36))写死版本省掉探测开销。3.2 迁移隔离为什么不能共用一套迁移文件这是跨数据库最容易翻车的地方。EF 的迁移文件里包含大量 provider 相关的代码比如 SQL Server 生成nvarchar(max)MySQL 生成longtext自增列 SQL Server 用IDENTITYMySQL 用AUTO_INCREMENT。如果你用 SQL Server 生成的迁移去 update MySQL 数据库轻则字段类型不对重则直接报语法错误。正确做法是每个 provider 一套独立迁移通过不同的MigrationsAssembly隔离options.UseSqlServer(connStr, b b.MigrationsAssembly(MyApp.SqlServerMigrations)); options.UseMySql(connStr, ServerVersion.AutoDetect(connStr), b b.MigrationsAssembly(MyApp.MySqlMigrations));参数说明MigrationsAssembly指定迁移程序集把不同 provider 的迁移放到不同类库项目里。这样dotnet ef database update时EF 只会加载对应程序集的迁移不会串味。代价是项目结构变复杂但对需要长期维护双数据库的产品来说这个代价值得。3.3 字段类型映射对照表提前避开类型坑跨数据库时C# 类型到数据库类型的映射差异是重灾区。下面这张表是我实际项目里总结的常用对照C# 类型SQL ServerMySQL注意事项string无长度nvarchar(max)longtextMySQL 索引长文本会失败stringHasMaxLength(200)nvarchar(200)varchar(200)推荐显式指定长度decimaldecimal(18,2)decimal(65,30) 默认必须 HasPrecisionDateTimedatetime2datetime(6)MySQL 精度到微秒boolbittinyint(1)查询时注意 0/1 转换Guiduniqueidentifierchar(36)MySQL 存字符串性能略差提示string 类型如果不指定长度MySQL 会生成 longtext这种类型无法直接建普通索引查询性能会断崖式下降。养成给所有 string 属性加HasMaxLength的习惯。4. 避坑与排查跨数据库迁移的 5 个真实翻车现场4.1 现象MySQL 下自增主键从 0 开始SQL Server 从 1 开始原因两种数据库对自增起始值的默认约定不同EF 迁移生成的脚本继承了各自数据库的默认行为。解决在OnModelCreating里显式配置ValueGeneratedOnAdd()并在迁移后手动执行ALTER TABLE ... AUTO_INCREMENT1MySQL统一起始值。如果业务对主键值敏感建议改用 Guid 或雪花 ID彻底绕开自增差异。4.2 现象迁移时报「There is already an object named Products」原因数据库里已经有表但 EF 的__EFMigrationsHistory表里没有对应记录EF 以为没建过。解决不要手动删表了事正确做法是手动往__EFMigrationsHistory插入一条记录或者用dotnet ef database update --migration InitialCreate指定迁移。血泪经验生产环境千万别用EnsureCreated()它和迁移机制互斥用了之后迁移就废了。4.3 现象MySQL 报「Specified key was too long; max key length is 3072 bytes」原因给一个varchar(500)的字段建了唯一索引utf8mb4 编码下 500*42000 字节加上其他索引超限。解决缩短索引字段长度或改用前缀索引。EF 里可以用HasIndex(p p.Code).HasDatabaseName(IX_Code)配合迁移脚本手动改前缀。更根本的办法是设计阶段就控制索引字段长度。4.4 现象DateTime 字段在 MySQL 里存进去少了几毫秒原因MySQL 的datetime默认精度是秒EF 迁移生成的是datetime(6)才对。如果手动建过表或迁移文件被改过精度就丢了。解决检查迁移文件里 DateTime 字段是否带(6)没有就重新生成迁移。涉及时间排序的业务精度丢失会导致顺序错乱。4.5 现象切换 provider 后查询报「Unknown column p.Price in field list」原因迁移没重新生成数据库表结构还是旧 provider 的。解决切换 provider 后必须重新执行migrations add和database update不能复用旧迁移。建议在 CI 流程里加一步校验用目标 provider 跑一次空库迁移确保脚本能跑通。5. 进阶技巧用迁移脚本做多数据库的自动化验证5.1 生成幂等 SQL 脚本做人工审查EF 支持把迁移导出成 SQL 脚本这在跨数据库场景下特别有用——你可以直接看到 EF 到底生成了什么 SQL# 生成 SQL Server 脚本 dotnet ef migrations script --idempotent --output migrate_sqlserver.sql # 生成 MySQL 脚本 dotnet ef migrations script --idempotent --output migrate_mysql.sql参数说明--idempotent生成可重复执行的脚本带 IF NOT EXISTS 判断适合生产环境。生成后对比两个脚本能快速发现类型映射差异。我一般会在代码评审时把这两个脚本一起提交让团队看到数据库层面的实际变化。5.2 用测试容器做双数据库回归真正靠谱的验证方式是在 CI 里同时起 SQL Server 和 MySQL 容器对同一套实体模型跑迁移和基础 CRUD 测试。[Theory] [InlineData(SqlServer)] [InlineData(MySql)] public async Task Can_Insert_And_Query_Product(string provider) { var options BuildOptions(provider); // 根据 provider 构造 DbContextOptions using var ctx new AppDbContext(options); await ctx.Database.MigrateAsync(); // 自动应用迁移 ctx.Products.Add(new Product { Name 测试商品, Price 9.99m, CreatedAt DateTime.UtcNow }); await ctx.SaveChangesAsync(); var saved await ctx.Products.FirstAsync(); Assert.Equal(9.99m, saved.Price); // 验证 decimal 精度没丢 }逻辑说明Database.MigrateAsync()会在测试库上自动执行迁移确保迁移脚本本身可用。[Theory]让同一套测试跑两遍分别针对两种 provider。这个测试能抓住 90% 的跨数据库类型问题尤其是 decimal 精度和 DateTime 精度这两类隐蔽 bug。5.3 一个我坚持了三年的习惯每次新增实体或修改字段后我一定会做三件事第一本地用两种 provider 各跑一次database update第二导出两份 SQL 脚本肉眼扫一遍类型第三跑一遍上面的双 provider 测试。这套流程看起来繁琐但它把「上线才发现数据库不兼容」的概率压到了接近零。跨数据库适配没有捷径靠的就是把验证做在提交之前。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?