【四】EFCore全自动迁移,代码运行时迁移@Asp.netcore Code First DDD学习笔记

EfCore实现全自动化迁移数据库结构,非执行命令。达到运行时自动迁移,且数据库有数据结构的记录。

主题:Asp.netcore Code First +DDD学习笔记

目录:

   【一】 CodeFirst+DDD项目结构的构建

   【二】Asp.Netcore使用Panda.DynamicWebApi来进行Controller解耦

   【三】Asp.NetCore使用Efcore+Mysql实现CodeFirst

   【四】EfCore实现全自动化迁移

     案例代码下载点击

在Efcore官网上说的运行时迁移是你执行完成Add-Migration后在代码运行时执行myDbContext.Database.Migrate();数据库的数据结构会被更改,但是这个并不是我们所需要。我不想执行Add-Migration我想实现全自动化的方式。我们要实现自动化数据结构迁移,并在迁移时,非数据错误的字段修改和调整不会删除数据。就和执行命令达到的效果是一样的。

寻找Efcore内的方法发现:

 在Efcore官方里面有如下三个方法:

var modelDiffer = dbContext.GetInfrastructure().GetService();

modelDiffer.HasDifferences(lastModel, dbContext.Model);//这个方法返回值是true或者false,这个可以比较老版本的model和当前版本的model是否出现更改。

var upOperations = modelDiffer.GetDifferences(lastModel, dbContext.Model);//这个方法返回的迁移的操作对象。

dbContext.GetInfrastructure() .GetRequiredService().Generate(upOperations, dbContext.Model).ToList();//这个方法是根据迁移对象和当前的model生成迁移sql脚本。

根据以上三个方法我们可以把迁移做成自动化,首先我们要知道是否出现版本修改,我们要先知道老版本的model是什么样子的,这时候我们需要在每次迁移完成后,把model存到数据库即可。

每次我们从数据库拿出来最后一次的model使用HasDifferences方法和现在的model做比较,出现需要修改的时候我们使用GetDifferences方法获取迁移对象,然后根据迁移对象使用Generate方法来生成sql脚本,最后用efcore执行sql脚本即可。

如何把model存到数据库,并在从数据库取出后能还原成model用来最比较呢。我第一个想到的是序列化,于是我进行了尝试,发现该dbContext.Model不允许进行序列化。后来看到一大佬说:每次迁移生成的快照实际上是动态生成的类,所以要用动态编辑机制CSharpCompilation和CSharpCompilationOptions。大佬的文章链接:https://blog.csdn.net/weixin_38687913/article/details/81122169,于是得出如下解决方案

一. 给domain安装nuget包

         Microsoft.CodeAnalysis.Common  版本选择2.8

二.创建保存数据库版本的实体类

     在Domain的Domains创建MigrationLogs文件夹,并在该文件夹里面创建MigrationLog类,具体代码如下:

【四】EFCore全自动迁移,代码运行时迁移@Asp.netcore Code First DDD学习笔记_第1张图片

using EfCoreRepository.EfModelAttributes;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;

namespace Domain.MigrationLogs
{
    [EfModel]
    public class MigrationLog
    {
        [Key]
        public int Id { get; set; }
        public string SnapshotDefine { get; set; }

        public DateTime MigrationTime { get; set; }
    }
}

三.修改efcontent如下

using Domain.MigrationLogs;
using EfCoreRepository;
using EfCoreRepository.EfModelAttributes;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Design;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.Extensions.DependencyInjection;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading.Tasks;


namespace Domain.EfCoreContent
{
    public class EfContent : DbContext
    {
        public DbContextOptions dbContextOptions { get; set; }
        public EfContent(DbContextOptions options) : base(options)
        {
            dbContextOptions = options;
        }

        /// 
        /// 创建实体
        /// 
        /// 
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.AddEntityConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
            base.OnModelCreating(modelBuilder);
        }

        /// 
        /// 扩展查询
        /// 
        /// 
        /// 
        /// 
        public IQueryable Where(Expression> predicate) where T : class
        {
            return base.Set().Where(predicate);
        }
    }

    /// 
    /// efcore扩展sql查询
    /// 
    public static class EntityFrameworkCoreExtensions
    {
        /// 
        /// 执行sql返回datatable
        /// 
        /// 
        /// 
        /// 
        /// 
        public static DataTable SqlQuery(this EfContent efcontent, string sql, params object[] commandParameters)
        {
            var dt = new DataTable();
            using (var connection = efcontent.Database.GetDbConnection())
            {
                using (var cmd = connection.CreateCommand())
                {
                    efcontent.Database.OpenConnection();
                    cmd.CommandText = sql;

                    if (commandParameters != null && commandParameters.Length > 0)
                        cmd.Parameters.AddRange(commandParameters);
                    using (var reader = cmd.ExecuteReader())
                    {
                        dt.Load(reader);
                    }
                }
            }
            return dt;
        }

        /// 
        /// 执行多条sql
        /// 
        /// 
        /// 
        public static int ExecuteListSqlCommand(this EfContent efcontent,List sqlList)
        {
            int retunInt = 0;
            try
            {
                using (var trans = efcontent.Database.BeginTransaction())
                {
                    
                    sqlList.ForEach(cmd => retunInt+= efcontent.Database.ExecuteSqlCommand(cmd));
                    efcontent.Database.CommitTransaction();
                }
            }
            catch (DbException ex)
            {
                try
                {
                    efcontent.Database.RollbackTransaction();
                }
                catch (DbException)
                {
                    
                }
            }
            return retunInt;
        }

        /// 
        /// 执行sql返回list
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public static List SqlQuery(this EfContent efcontent, string sql, params object[] parameters) where T : class, new()
        {
            var dt = SqlQuery(efcontent, sql, parameters);
            return dt.ToList();
        }

        /// 
        /// datatable转list
        /// 
        /// 
        /// 
        /// 
        public static List ToList(this DataTable dt) where T : class, new()
        {
            var propertyInfos = typeof(T).GetProperties();
            var list = new List();
            foreach (DataRow row in dt.Rows)
            {
                var t = new T();
                foreach (PropertyInfo p in propertyInfos)
                {
                    if (dt.Columns.IndexOf(p.Name) != -1 && row[p.Name] != DBNull.Value)
                        p.SetValue(t, row[p.Name], null);
                }
                list.Add(t);
            }
            return list;
        }
    }

    /// 
    /// 给efcontent扩展自动处理数据库版本的方法
    /// 
    public static class EfCoreMigration
    {
        /// 
        /// 给EfContent添加的更新数据库结构的方法
        /// 
        public static void RunUpdateDataBaseEntity(this EfContent _dbContext)
        {
            // 创建一个DbContext,具体方法怎样都行
            IModel lastModel = null;
            try
            {
                // 读取迁移记录,把快照还原会model
                var lastMigration = _dbContext.Set()
                    .OrderByDescending(e => e.Id) 
                    .FirstOrDefault();
                lastModel = lastMigration == null ? null : (CreateModelSnapshot(lastMigration.SnapshotDefine)?.Model);
            }
            catch (DbException) { }
            var modelDiffer = _dbContext.Database.GetService();
            //判断是否有更改
            if (modelDiffer.HasDifferences(lastModel, _dbContext.Model))
            {
                // 用 IMigrationsModelDiffer 的 GetDifferences 方法获取迁移的操作对象
                var upOperations = modelDiffer.GetDifferences(lastModel, _dbContext.Model);

                //执行迁移
                Migrationing(upOperations, _dbContext);

                // 生成新的快照,存起来
                var snapshotCode = new DesignTimeServicesBuilder(typeof(EfContent).Assembly, Assembly.GetEntryAssembly(), new OperationReporter(new OperationReportHandler()), new string[0])
                    .Build((DbContext)_dbContext)
                    .GetService()
                    .GenerateSnapshot("ApiHost.Migrations", typeof(EfContent), "EfContentModelSnapshot", _dbContext.Model);//modelSnapshotNamespace:给动态生成类添加nameplace(必须和当前代码所在的命名控件下或者一样)modelSnapshotName:动态生成类的名称
                _dbContext.Set().Add(new MigrationLog()
                {
                    SnapshotDefine = snapshotCode,
                    MigrationTime = DateTime.Now
                });
                _dbContext.SaveChanges();
            }
        }


        /// 
        /// 迁移数据库结构
        /// 
        /// 
        /// 
        private static void Migrationing(IReadOnlyList upOperations,EfContent _dbContext)
        {
            List sqlChangeColumNameList = new List();
            List list = new List();
            //执行迁移列名修改
            foreach (var upOperation in upOperations)
            {
                if (upOperation is RenameColumnOperation)
                {
                    
                    sqlChangeColumNameList.Add(RenameColumnOperationToSql(upOperation as RenameColumnOperation, _dbContext));
                }
                else
                {
                    list.Add(upOperation);
                }
            }
            int columChangeCount = _dbContext.ExecuteListSqlCommand(sqlChangeColumNameList); 
            //处理剩余迁移
            if (list.Count > 0)
            {
                //通过 IMigrationsSqlGenerator 将操作迁移操作对象生成迁移的sql脚本,并执行
                var sqlList = _dbContext.Database.GetService()
                    .Generate(list, _dbContext.Model)
                    .Select(p=>p.CommandText).ToList();
               int changeCount= _dbContext.ExecuteListSqlCommand(sqlList);
            }
        }

        /// 
        /// 把RenameColumnOperation该字段转换成sql
        /// 
        /// 
        /// 
        private static string RenameColumnOperationToSql(RenameColumnOperation renameColumnOperation, EfContent _dbContext)
        {
            string column_type = string.Empty;
            string sql = "select column_type from information_schema.columns where table_name='" + (renameColumnOperation as RenameColumnOperation).Table + "'  and column_name='" + (renameColumnOperation as RenameColumnOperation).Name + "'";
            var dataTable = _dbContext.SqlQuery(sql);
            if (dataTable != null && dataTable.Rows.Count > 0)
            {
                column_type = dataTable.Rows[0].ItemArray[0].ToString();
            }
            return "alter table " + (renameColumnOperation as RenameColumnOperation).Table + " change  column " + (renameColumnOperation as RenameColumnOperation).Name + " " + (renameColumnOperation as RenameColumnOperation).NewName + " " + column_type + " ;";
        }


        /// 
        /// 把实体的string转成该对象
        /// 
        /// 
        /// 
        private static ModelSnapshot CreateModelSnapshot(string codedefine)
        {
            // 生成快照,需要存到数据库中供更新版本用
            var references = typeof(EfContent).Assembly
                .GetReferencedAssemblies()
                .Select(e => MetadataReference.CreateFromFile(Assembly.Load(e).Location))
                .Union(new MetadataReference[]
                {
                    MetadataReference.CreateFromFile(Assembly.Load("netstandard").Location),
                    MetadataReference.CreateFromFile(Assembly.Load("System.Runtime").Location),
                    MetadataReference.CreateFromFile(typeof(Object).Assembly.Location),
                    MetadataReference.CreateFromFile(typeof(EfContent).Assembly.Location)
                });
            var compilation = CSharpCompilation.Create("ApiHost.Migrations")//assemblyName:给动态生成类添加nameplace(必须和当前代码所在的命名控件下或者一样)和生成快照时要保持一直
                .WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                .AddReferences(references)
                .AddSyntaxTrees(SyntaxFactory.ParseSyntaxTree(codedefine));
            using (var stream = new MemoryStream())
            {
                var compileResult = compilation.Emit(stream);
                return compileResult.Success
                    ? Assembly.Load(stream.GetBuffer()).CreateInstance("ApiHost.Migrations.EfContentModelSnapshot") as ModelSnapshot //typeName即生成的快照时设置的modelSnapshotNamespace+modelSnapshotName(nameplace+动态生成类的名称)
                    : null;
            }
        }
    }
}

四.在Api项目内调用该静态扩展方法,修改Startup的Configure如下:

  public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
        {
            //使用跨越配置
            app.UseCors();
            //使用SwaggerApi自动生成器
            app.UseSwagger();
            //使用SwaggerApi自动生成器的Ui界面
            app.UseSwaggerUI(option =>
            {
                option.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
            });


            using (var scope = serviceProvider.CreateScope())
            {
                var efContent = scope.ServiceProvider
                    .GetRequiredService();
                efContent.RunUpdateDataBaseEntity();
            }


            //if (env.IsDevelopment())
            //{
            //    app.UseDeveloperExceptionPage();
            //}
            //else
            //{
            //    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            //    app.UseHsts();
            //}

            //app.UseHttpsRedirection();
            app.UseMvc();

        }

到这里,每次启动后数据库的结构会自动同步,根据测试和手动用命令执行的过程基本一致。执行后会发现数据库里面版本表里面会保存每次修改的model代码如下:

【四】EFCore全自动迁移,代码运行时迁移@Asp.netcore Code First DDD学习笔记_第2张图片

具体内容就是真实的代码,和手动用命令创建的这个类的代码一样

如下:【四】EFCore全自动迁移,代码运行时迁移@Asp.netcore Code First DDD学习笔记_第3张图片

 

到这里。efcore全自动迁移全部完成。

github地址:https://github.com/houliren/Asp.netcore-Code-First-DDD

 

你可能感兴趣的:(Asp.netcore,DDD,EfCore)