通过EF Core将Sql server数据表移植到MySql

在还是.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))

//);

...

收工。

相关推荐
冉冰学姐10 分钟前
SSM在线影评网站平台82ap4(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面
数据库·ssm框架·在线影评平台·影片分类
Exquisite.1 小时前
企业高性能web服务器(4)
运维·服务器·前端·网络·mysql
知识分享小能手1 小时前
SQL Server 2019入门学习教程,从入门到精通,SQL Server 2019数据库的操作(2)
数据库·学习·sqlserver
踩坑小念2 小时前
秒杀场景下如何处理redis扣除状态不一致问题
数据库·redis·分布式·缓存·秒杀
萧曵 丶3 小时前
MySQL 语句书写顺序与执行顺序对比速记表
数据库·mysql
Wiktok4 小时前
MySQL的常用数据类型
数据库·mysql
曹牧4 小时前
Oracle 表闪回(Flashback Table)
数据库·oracle
J_liaty4 小时前
Redis 超详细入门教程:从零基础到实战精通
数据库·redis·缓存
m0_706653234 小时前
用Python批量处理Excel和CSV文件
jvm·数据库·python
山岚的运维笔记4 小时前
SQL Server笔记 -- 第15章:INSERT INTO
java·数据库·笔记·sql·microsoft·sqlserver