EntityFrameworkCore+CodeFirst:根据实体自动生成数据库(二) 程序包管理控制台迁移

二、程序包管理控制台迁移

需要在DAL层引用包:Microsoft.EntityFrameworkCore.Tools

 

 

 

然后在控制台程序中也需要引用包:Microsoft.EntityFrameworkCore.Design

 

这样,就完成了需要的组件的引用了。

接下来打开程序包管理控制台,在vs中的“工具”中

 

 

 

 

 将默认项目切换为CodeFirst.DAL

 

接下来敲入命令:

Add-Migration MyCodeFirst
MyCodeFirst:为自定义,每次构建都不能重名。比如下次构建,可以是MyCodeFirst1

如下:

 

命令会显示构建成功,这时候在我们的DAL层中会多了一个Migrations目录,每次的迁移文件都会存在该目录中

 

 20220830065221_MyCodeFirst.cs  :是针对本次迁移生成的文件。每次迁移都会生成一个对应的迁移文件。里面有一个Up方法和一个Down方法,Up方法表示要执行的操作,Down方法相当于回滚,如果需要还原到上一个状态,就会执行Down方法。

MyDbContextModelSnapshot.cs :生成的配置文件。只会生成一次。

 

接下来就是执行这些文件来创建数据库,执行以下命令:

update-database MyCodeFirst  //指向上面自定义的名称,意思是执行那个文件

这样就生成了数据库结构了

 

 

 

比如下一次结构有修改,那就执行 

Add-Migration 新定义的名称
再执行 update-database 新定义的名称
即可。

在数据库中,我们会发现多了一个__EFMigrationsHistory,该表是自动生成的,记录迁移过程

 

该种方式与第一种方式好在于,对于数据结构的变化,可以每次都更新到数据库。

但在生产环境下,不太适用,我们无法在服务器上通过 VS的nuget控制台去执行update-datebase 命令。

 

=======================================

SQL脚本

除了用 Add-Migration 命令和update-database 结合之外,我们还可以将变更的结构生成sql进行核对,使用script-migration命令即可

script-migration:不指定版本,默认生成最新迁移版本的(全部)SQL脚本 
script-migration source target :source 是源版本,target 是目标版本,根据两个版本的结构差异而生成sql

 

首先,我们先使用 Add-Migration test1命令,生成迁移代码,然后再使用 script-migration 命令生成脚本

执行后,再vs中会弹出一个.sql的文件,如

 

 

我们可以人工核对脚本的正确性,再拿到远程服务器上去执行。

但有时候,我们是在本地多次改了结构,会多次进行本地数据结构迁移。这时候,更新到远程时,需要将本地新增加的sql文件按照生成的时间顺序,分别放到远程上去执行,不然结构就会不对。

幂等 SQL 脚本

上面生成的 SQL 脚本只能用于将架构从一个迁移更改为另一个迁移;你需要适当地应用脚本,并且仅应用于处于正确迁移状态的数据库。 EF Core 还支持生成幂等脚本,此类脚本将在内部检查已经应用哪些迁移(通过迁移历史记录表),并且只应用缺少的迁移。 如果不确知应用到数据库的最后一个迁移,或者需要部署到多个可能分别处于不同迁移的数据库,此类脚本非常有用。

使用的命令是:Script-Migration -Idempotent 

也可以使用dotnet命令

dotnet ef migrations script --idempotent

 

====================================================

 

假如我们已经了Add-Migration MyCodeFirst,但是在生产环境下,我们既然没办法在nuget上执行  update-datebase  命令,那么我们也可以用应用程序来执行
原理都是调用dbcontext上下文对象的.Database.Migrate() 方法来实现。

可以有两种使用方式,不同应用场景:

1:直接在startup.cs的Configure方法中直接调用,如

  var dbContext = app.ApplicationServices.GetRequiredService<MyDbContext>();
            dbContext.Database.Migrate();

这样也可以实现更新。

2:写扩展类来执行
代码放这里,看着感觉不太顺眼,不太合理对吧。

那就定义一个扩展类DatabaseManagementService

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CodeFirst;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace WebApplication1
{
    public static class DatabaseManagementService
    {
        // Getting the scope of our database context
        public static IApplicationBuilder UseMigrationInitialisation(
            this IApplicationBuilder app,
            IWebHostEnvironment env,
            IConfiguration configuration)
        {
            using (var serviceScope = app.ApplicationServices.CreateScope())
            {
                using (var context = serviceScope.ServiceProvider.GetService<MyDbContext>())
                {
                    var db = context.Database;
                    db.Migrate();
                    SeedData(context, env, configuration); //创建数据种子
                }
            }
            return app;
        }
        private static void SeedData(
            MyDbContext _context,
            IWebHostEnvironment env,
            IConfiguration configuration
            )
        {
            if (env.IsDevelopment())
            {
                //Do something for DEV 
            }
            else
            {
                //Do something for Production
            }

            if(!_context.Set<Student>().Any()) //若是空,则写入默认数据,避免重复写入
            {
                Student model = new Student();
                model.Name = "风扇";
                model.sex = 0;
                model.Age = 10;
                _context.Set<Student>().Add(model);
            }
           
            _context.SaveChanges();

        }
    }
}

 

然后在 startup.cs的路由终结点时,调用

  app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            })
                .UseMigrationInitialisation(env, Configuration);

 

通过这种方法,也可以达到目的,同时又能初始化一些数据。

但是,在程序端执行,有几个点需要特别注意:

  • 如果应用程序的多个实例正在运行,这两个应用程序可能会尝试同时应用迁移并失败(更糟糕的情况是导致数据损坏)。
  • 同样,如果一个应用程序正在访问数据库,而另一个应用程序正在迁移它,这可能会导致严重的问题。
  • 应用程序必须具有提升的访问权限才能修改数据库架构。 在生产环境中限制应用程序的数据库权限通常是一种很好的做法。
  • 出现问题时,能够回滚已应用的迁移很重要。 其他策略可以轻松提供此功能,并且开箱即用。
  • 程序会直接应用 SQL 命令,不给开发人员检查或修改的机会。 这在生产环境中可能会很危险。

也就是说,这个程序建议应该是独立的,专门给数据库迁移使用,而不是在业务程序中执行。

至此,程序包管理控制台迁移完成。

更多分享,请大家关注我的个人公众号:

posted @ 2022-09-29 09:01  黄明辉  阅读(407)  评论(0编辑  收藏  举报