在还是.net frameworks时,我们用ado.net 操作数据库,后来,微软为了迎合跨平台趋势,推出.net core,虽然也.net core也有ado.net,但微软更推荐ef core,无论是国内还是国内涉及.net core的C#技术书(vb.net也一样)都着重讲解了ef core,ef core不仅跨平台,还跨数据库,可以避免程序员适应不同数据库之间sql语言差异带来的开销,除了特别复杂的sql查询,ef core就体现出来了,并且它还可实现不同数据库之间迁移,下面就演示一下sql server 2019迁移到MySQL8,体现一下ef core的强大。
首先,在sql server 2019上新建一个数据库,此处取名为EFCoreDemo,在数据库中添加两张简单的表,ClassRoom和Student,ClassRoom为主表,Student为子表,ClassRoom与Student是一对多的关系(级联更新,级联删除),表字段如截图1和2。并向两表中任意填写一些数据。


创建表后,我们用visaul studio 2022新建一个.net core的类库(注意不是.net frameworks),删除自动生成的Class类,在工具栏的管理解决方案的nuge程序包,下载安装以下三个包,Microsoft.EntityFrameworkCore.Design、Microsoft.EntityFrameworkCore.SqlServer和Microsoft.EntityFrameworkCore.Tools,注意为了后期迁移到mysql,这里建议版本为8.0.0,(并非最新版本)。打开程序包管理器控制台(PM)窗口,执行
Scaffold-DbContext "Data Source=localhost;Initial Catalog=数据库名;User ID=用户名;Password=密码;TrustServerCertificate=True" Microsoft.EntityFrameworkCore.SqlServer - OutputDir Models - Context 数据库名DbContext - NoOnConfiguring - UseDatabaseNames - DataAnnotations - Verbose
成功后能看到一个Model文件夹,里在有与表对应的实体类和上下文类。接着可以手工新建Services的文件夹,用于实现数据库基本的CRUD操作。
csharp
using EFCoreMigration;
using EFCoreMigration.Models;
using Microsoft.EntityFrameworkCore;
using System;
namespace EFCoreMigration.Services
{
public class ClassRoomService
{
private readonly EFCoreDbContext _dbContext;
// 通过依赖注入获取数据库上下文
public ClassRoomService(EFCoreDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<ClassRoom> AddClassRoomAsync(ClassRoom classRoom)
{
try
{
_dbContext.ClassRooms.Add(classRoom);
await _dbContext.SaveChangesAsync();
return classRoom;
}
catch (Exception ex)
{
throw new Exception( ex.Message, ex);
}
}
public async Task<List<ClassRoom>> GetAllClassRoomsAsync()
{
try
{
return await _dbContext.ClassRooms.ToListAsync();
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public async Task<ClassRoom?> GetClassRoomByIdAsync(int id)
{
try
{
return await _dbContext.ClassRooms.FindAsync(id);
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public async Task<bool> UpdateClassRoomAsync(int id, ClassRoom updatedClassRoom)
{
try
{
var existingClassRoom = await _dbContext.ClassRooms.FindAsync(id);
if (existingClassRoom == null)
{
return false;
}
// 更新属性
existingClassRoom.ClassRoomName = updatedClassRoom.ClassRoomName;
_dbContext.ClassRooms.Update(existingClassRoom);
await _dbContext.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public async Task<bool> DeleteClassRoomAsync(int id)
{
try
{
var classRoom = await _dbContext.ClassRooms.FindAsync(id);
if (classRoom == null)
{
return false;
}
_dbContext.ClassRooms.Remove(classRoom);
await _dbContext.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
}
}
csharp
using EFCoreMigration.Models;
using Microsoft.EntityFrameworkCore;
using System;
namespace EFCoreMigration.Services
{
public class StudentService
{
private readonly EFCoreDbContext _dbContext;
public StudentService(EFCoreDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<Student> AddStudentAsync(Student student)
{
try
{
_dbContext.Students.Add(student);
await _dbContext.SaveChangesAsync();
return student;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public async Task<List<Student>> GetAllStudentsAsync()
{
try
{
// 可根据需要Include关联的ClassRoom信息
return await _dbContext.Students
.Include(s => s.ClassRoom) // 关联查询教室信息
.ToListAsync();
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public async Task<Student?> GetStudentByIdAsync(int id)
{
try
{
return await _dbContext.Students
.Include(s => s.ClassRoom)
.FirstOrDefaultAsync(s => s.Id == id);
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public async Task<bool> UpdateStudentAsync(int id, Student updatedStudent)
{
try
{
var existingStudent = await _dbContext.Students.FindAsync(id);
if (existingStudent == null)
{
return false;
}
existingStudent.StudentName = updatedStudent.StudentName;
existingStudent.ClassRoomId = updatedStudent.ClassRoomId;
_dbContext.Students.Update(existingStudent);
await _dbContext.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
public async Task<bool> DeleteStudentAsync(int id)
{
try
{
var student = await _dbContext.Students.FindAsync(id);
if (student == null)
{
return false;
}
_dbContext.Students.Remove(student);
await _dbContext.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
throw new Exception(ex.Message, ex);
}
}
}
}
接着在解决方案资源管理器新建一个控制台项目,此处命名为InkoveDemo。用于测试数据库代码能否正常调用,简单起见,这里只完成ClassRoom添加与列出所有数据,代码如下(注意,此处为测试,生产环境下数据库配置不建议直接硬编码)
csharp
using EFCoreMigration;
using EFCoreMigration.Services;
using EFCoreMigration.Models;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace InkoveDemo
{
class Program
{
static async Task Main(string[] args)
{
var optionsBuilder = new DbContextOptionsBuilder<EFCoreDbContext>();
string ConnSqlserverStr = "Data Source=localhost;Initial Catalog=EFCoreDemo;User ID=sa;Password=123;TrustServerCertificate=True";
optionsBuilder.UseSqlServer(ConnSqlserverStr);
// 创建数据库上下文实例
using var dbContext = new EFCoreDbContext(optionsBuilder.Options);
// 确保数据库和表已创建
await dbContext.Database.EnsureCreatedAsync();
// 实例化班级服务(注入数据库上下文)
var classRoomService = new ClassRoomService(dbContext);
try
{
// 1. 硬编码添加班级数据
ClassRoom classroom =
new ClassRoom { ClassRoomName = "文秘班" };
await classRoomService.AddClassRoomAsync(classroom);
// 2. 列出所有班级数据
var allClassRooms = await classRoomService.GetAllClassRoomsAsync();
Console.WriteLine("\n===== 所有班级列表 =====");
if (allClassRooms.Any())
{
foreach (var room in allClassRooms)
{
Console.WriteLine($"ID: {room.Id}");
Console.WriteLine($"班级名称: {room.ClassRoomName}");
Console.WriteLine("---------------------");
}
}
else
{
Console.WriteLine("暂无班级数据");
}
}
catch (Exception ex)
{
Console.WriteLine($"操作失败:{ex.Message}");
}
Console.WriteLine("\n按任意键退出...");
Console.ReadKey();
}
}
}
需要在依赖项引入自已刚建的类库EFCoreMigration,并将启动项目切换为InkoveDemo。运行后可看到如图3效果。

下面是迁移数据库了,再下载安装一个Pomelo.EntityFrameworkCore.MySql的nuge程序包,版本号8.0.0,接着新一个Factory类,代码如下,注意将mysql8数据库配置和mysql8版本与替换成你自己的。
csharp
using EFCoreMigration;
using EFCoreMigration.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Pomelo.EntityFrameworkCore.MySql;
using Microsoft.Extensions.Configuration;
using System.IO;
namespace EFCoreMigration;
// 仅用于迁移,迁移完成后直接删除此类!
public class EFCoreDemoDbContextDesignTimeFactory
: IDesignTimeDbContextFactory<EFCoreDbContext>
{
public EFCoreDbContext CreateDbContext(string[] args)
{
string connectionString = "Server=localhost;Port=3306;Database=efcoredemo;Uid=root;Pwd=123456;Charset=utf8mb4;";
// 配置 MySQL 选项(版本是你的MySql一致)
var optionsBuilder = new DbContextOptionsBuilder<EFCoreDbContext>();
optionsBuilder.UseMySql(
connectionString,
new MySqlServerVersion(new Version(8, 0, 43))
);
// 返回 DbContext 实例,供迁移工具使用
return new EFCoreDbContext(optionsBuilder.Options);
}
}
接着PM窗口执行
Pomelo.EntityFrameworkCore.MySql,成功后会有一个Migrations目录,不用管里面的代码,继续在PM窗口执行Update-Database,重新连接或刷新mysql数据库,见到两张与sql server相同的表,至此表结构已完成迁移。
接下来,便是迁移表中的数据了,建立一个DataMigrationHelper类代码
csharp
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using EFCoreMigration;
using EFCoreMigration.Models;
namespace EFCoreMigration
{
public class DataMigrationHelper
{
// SQL Server 连接字符串(迁移源)
private readonly string _sqlServerConnStr;
// MySQL 连接字符串(迁移目标)
private readonly string _mysqlConnStr;
/// <summary>
/// 初始化迁移工具
/// </summary>
public DataMigrationHelper(string sqlServerConnStr, string mysqlConnStr)
{
_sqlServerConnStr = sqlServerConnStr;
_mysqlConnStr = mysqlConnStr;
}
/// <summary>
/// 执行全量数据迁移(按表顺序迁移,处理外键依赖)
/// </summary>
public async Task MigrateAllDataAsync()
{
// 1. 连接 SQL Server(读数据)
var sqlServerOptions = new DbContextOptionsBuilder<EFCoreDbContext>()
.UseSqlServer(_sqlServerConnStr)
.Options;
// 2. 连接 MySQL(写数据)
var mysqlOptions = new DbContextOptionsBuilder<EFCoreDbContext>()
.UseMySql(_mysqlConnStr, new MySqlServerVersion(new Version(8, 0, 43)))
.Options;
try
{
Console.WriteLine("=== 开始数据迁移 ===");
// 🔴 关键:按「无外键依赖 → 有外键依赖」的顺序迁移(避免插入失败)
// 例:先迁移 AdminTab、News(无外键),再迁移 Pic(依赖 News)
await MigrateTableAsync<ClassRoom>(sqlServerOptions, mysqlOptions);
await MigrateTableAsync<Student>(sqlServerOptions, mysqlOptions); // 最后迁移有外键的表
Console.WriteLine("=== 数据迁移完成!===");
}
catch (Exception ex)
{
Console.WriteLine($"迁移失败:{ex.Message}");
if (ex.InnerException != null)
Console.WriteLine($" 内部错误:{ex.InnerException.Message}");
}
}
/// <summary>
/// 单个表的数据迁移(通用方法,复用所有实体)
/// </summary>
private async Task MigrateTableAsync<TEntity>(DbContextOptions<EFCoreDbContext> sqlServerOptions,
DbContextOptions<EFCoreDbContext> mysqlOptions)
where TEntity : class
{
Console.WriteLine($"正在迁移 {typeof(TEntity).Name} 表...");
// 从 SQL Server 读取所有数据
using (var sqlServerDb = new EFCoreDbContext(sqlServerOptions))
{
// 读取数据(AsNoTracking 提高性能,不跟踪实体状态)
List<TEntity> dataList = await sqlServerDb.Set<TEntity>()
.AsNoTracking()
.ToListAsync();
if (dataList.Count == 0)
{
Console.WriteLine($" {typeof(TEntity).Name} 表无数据,跳过...");
return;
}
// 批量插入 MySQL
using (var mysqlDb = new EFCoreDbContext(mysqlOptions))
{
// 先清空 MySQL 目标表(避免重复数据,可选)
await mysqlDb.Set<TEntity>().ExecuteDeleteAsync();
// 批量添加(EF Core 8 支持 AddRange 高效插入)
mysqlDb.Set<TEntity>().AddRange(dataList);
// 提交事务(保存数据)
await mysqlDb.SaveChangesAsync();
Console.WriteLine($" 成功迁移 {dataList.Count} 条数据到 {typeof(TEntity).Name} 表");
}
}
}
}
}
快去看看mysql数据库,数据也成功迁移,只要对InkoveDemo略作修改,便可对mysql数据库进行操作了。
...
string ConnSqlserverStr = "Data Source=localhost;Initial Catalog=EFCoreDemo;User ID=sa;Password=123;TrustServerCertificate=True";
optionsBuilder.UseSqlServer(ConnSqlserverStr);
//如果使用mysql使用以下语句,别忘记将配置替换成你自己的
// string Connmysqlstr = "Server=localhost;Port=3306;Database=efcoredemo;Uid=root;Pwd=123456;Charset=utf8mb4;";
// optionsBuilder.UseMySql(
// Connmysqlstr,
// new MySqlServerVersion(new Version(8, 0, 36))
//);
...
收工。