EntityFramework Core 运行dotnet ef命令迁移背后本质是什么?(EF Core迁移原理)

前言

终于踏出第一步探索EF Core原理和本质,过程虽然比较漫长且枯燥乏味还得反复论证,其中滋味自知,EF Core的强大想必不用我再过多废话,有时候咱们是否思考过背后到底作了些什么,到底怎么实现的呢?好比本节要讲的在命令行简单敲下dotnet ef migrations add initial初始化表完事,如此简洁。激起了个人好奇,下面咱们来看看。本节内容可能比较多,请耐心。sql

EntityFramework Core命令基础拾遗

咱们提早建立好.NET Core Web应用程序和实体模型以及上下文,园中例子太多且咱们也只是探讨迁移原理,无关乎其余。数据库

 

如此简单一个命令就初始化了表,是否是很神奇,咱们接下来要作的就是化神奇为简单。咱们接下来将上述迁移文件夹删除,再次运行以下命令,看看迁移详细过程。json

dotnet ef migrations add init -c EFCoreDbContext -p ..\EfCore.Data\ --verbose

经过如上两张图咱们可看出EF迁移将会进行两步:第一步则是编译上下文所在项目,编译启动项目。第二步则是经过编译成功后的上下文所在程序集合启动项目程序集最终实现迁移。总结起来就是简单两小步,背后所须要作的不少,请继续往下看。 缓存

EntityFramework Core迁移本质 

当咱们敲写dotnet ef migrations add initial命令后,紧接着会在启动项目obj文件夹会生成以下文件。app

这个东西是作什么的呢,我也不知道,咱们打开该文件看看。框架

<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="GetEFProjectMetadata" Condition="">
    <MSBuild Condition=" '$(TargetFramework)' == '' "
             Projects="$(MSBuildProjectFile)"
             Targets="GetEFProjectMetadata"
             Properties="TargetFramework=$(TargetFrameworks.Split(';')[0]);EFProjectMetadataFile=$(EFProjectMetadataFile)" />
    <ItemGroup Condition=" '$(TargetFramework)' != '' ">
      <EFProjectMetadata Include="AssemblyName: $(AssemblyName)" />
      <EFProjectMetadata Include="OutputPath: $(OutputPath)" />
      <EFProjectMetadata Include="Platform: $(Platform)" />
      <EFProjectMetadata Include="PlatformTarget: $(PlatformTarget)" />
      <EFProjectMetadata Include="ProjectAssetsFile: $(ProjectAssetsFile)" />
      <EFProjectMetadata Include="ProjectDir: $(ProjectDir)" />
      <EFProjectMetadata Include="RootNamespace: $(RootNamespace)" />
      <EFProjectMetadata Include="RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)" />
      <EFProjectMetadata Include="TargetFileName: $(TargetFileName)" />
      <EFProjectMetadata Include="TargetFrameworkMoniker: $(TargetFrameworkMoniker)" />
    </ItemGroup>
    <WriteLinesToFile Condition=" '$(TargetFramework)' != '' "
                      File="$(EFProjectMetadataFile)"
                      Lines="@(EFProjectMetadata)" />
  </Target>
</Project>

一堆的如上东西,什么鬼玩意,刚看到这东西时我懵逼了,因而开始了探索之路。在.NET Core CLI 1.0.0有了称为“项目工具扩展”的功能,咱们称之为“CLI工具”。 这些是项目特定的命令行工具,也就是说扩展了dotnet命令。好比咱们安装Microsoft.DotNet.Watcher.Tools包则可使用dotnet watch命令,就是这么个意思。在.NET Core还没有完善时,项目文件采用JSON格式,紧接着改成了以扩展名为.xproj结尾的项目文件,格式也就转换为了XML格式,最后项目文件定型为以.proj结尾,固然数据格式依然是XML,我猜想估计和MSBuild有关,由于微软对XML数据格式的操做已经有很是成熟的库,相比较而言JSON咱们使用起来固然更加方便,可能微软须要多作额外的工做,纯属猜想。了解和知道MSBuild的童鞋看到上述数据格式想必格外亲切,再熟悉不过了,咱们若仔细看到上述数据参数,就可以明白上述参数是存放的项目参数。在.NET Core中都是利用MSBuild和CLI工具来读取项目信息以用于其余目的。那么问题就来了,如何读取项目信息呢?工具

利用MSBuild和CLI工具读取项目信息

首先咱们须要找到项目中以扩展名为.proj结尾的文件,其次咱们须要注入MSBuild Target,最后则启动进程是调用Target,代码以下:优化

        public static void Main(string[] args)
        {

            var projectFile = @"D:\Visual Studio 2015\Projects\EFCore2Example\EFCore2Example\EFCore2Example.csproj";

            var targetFileName = Path.GetFileName(projectFile) + ".EntityFrameworkCore.targets";

            var projectExePath = Path.Combine(@"D:\Visual Studio 2015\Projects\EFCore2Example\EFCore2Example", "obj");

            Directory.CreateDirectory(projectExePath);

            var targetFile = Path.Combine(projectExePath, targetFileName);

            File.WriteAllText(targetFile,
 @"
<Project>
      <Target Name=""GetEFProjectMetadata"">
        <PropertyGroup>
            <EFProjectMetadata> 
AssemblyName: $(AssemblyName) 
OutputPath: $(OutputPath) 
Platform: $(Platform)
            </EFProjectMetadata>
        </PropertyGroup>
       <Message Importance=""High"" Text=""$(EFProjectMetadata)"" />
      </Target>
  </Project>");

            var psi = new ProcessStartInfo
            {
                FileName = "dotnet",
                Arguments = $"msbuild \"{projectFile}\" /t:GetEFProjectMetadata /nologo"
            };
            var process = Process.Start(psi);
            process.WaitForExit();
            if (process.ExitCode != 0)
            {
                Console.Error.WriteLine("Invoking MSBuild target failed");
            }
            Console.ReadKey();
        }

默认状况下MSBuildProjectExtensionsPath路径在项目中obj文件夹下如上咱们迁移的WebApplication1.csproj.EntityFrameworkCore.targets,咱们对targets文件命名通常约定为$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).<SomethingUnique>.targets,如上代码为咱们仿照实际迁移时在obj文件夹下生成的targets文件。当启动dotnet进程运行时会在控制台打印以下参数:ui

上述只是做为简单的显示信息而使用,利用CLI工具在咱们项目内部建立了一个MSBuild目标。这个目标能够完成MSBuild所能作的任何事情,EF Core则是将加载目标读取临时文件的形式来获取项目信息。spa

            var projectFile = @"D:\Visual Studio 2015\Projects\EFCore2Example\EFCore2Example\EFCore2Example.csproj";

            var targetFileName = Path.GetFileName(projectFile) + ".EntityFrameworkCore.targets";

            var projectExePath = Path.Combine(@"D:\Visual Studio 2015\Projects\EFCore2Example\EFCore2Example", "obj");

            Directory.CreateDirectory(projectExePath);

            var targetFile = Path.Combine(projectExePath, targetFileName);

            File.WriteAllText(targetFile,
 @"
<Project>
      <Target Name=""GetEFProjectMetadata"">
        <ItemGroup>
          <EFProjectMetadata Include = ""AssemblyName: $(AssemblyName)"" />
          <EFProjectMetadata Include = ""OutputPath: $(OutputPath)"" />
          <EFProjectMetadata Include = ""Platform: $(Platform)"" />
          <EFProjectMetadata Include = ""PlatformTarget: $(PlatformTarget)"" />
          <EFProjectMetadata Include = ""ProjectAssetsFile: $(ProjectAssetsFile)"" />
          <EFProjectMetadata Include = ""ProjectDir: $(ProjectDir)"" />
          <EFProjectMetadata Include = ""RootNamespace: $(RootNamespace)"" />
          <EFProjectMetadata Include = ""RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)"" />
          <EFProjectMetadata Include = ""TargetFileName: $(TargetFileName)"" />
          <EFProjectMetadata Include = ""TargetFrameworkMoniker: $(TargetFrameworkMoniker)"" />
        </ItemGroup>
        <WriteLinesToFile
                              File =""$(EFProjectMetadataFile)""
                              Lines = ""@(EFProjectMetadata)"" />
      </Target>
  </Project>");


            var tmpFile = Path.GetTempFileName();
            var psi = new ProcessStartInfo
            {
                FileName = "dotnet",
                Arguments = $"msbuild \"{projectFile}\" /t:GetEFProjectMetadata /nologo \"/p:EFProjectMetadataFile={tmpFile}\""
            };
            var process = Process.Start(psi);
            process.WaitForExit();
            if (process.ExitCode != 0)
            {
                Console.Error.WriteLine("Invoking MSBuild target failed");
            }
            var lines = File.ReadAllLines(tmpFile);
            File.Delete(tmpFile);

            var properties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            foreach (var line in lines)
            {
                var idx = line.IndexOf(':');
                if (idx <= 0) continue;
                var name = line.Substring(0, idx)?.Trim();
                var value = line.Substring(idx + 1)?.Trim();
                properties.Add(name, value);
            }

            Console.WriteLine("........................................");

            Console.WriteLine($"EFCore2Example project has {properties.Count()}  properties");
            Console.WriteLine($"AssemblyName = { properties["AssemblyName"] }");
            Console.WriteLine($"OutputPath = { properties["OutputPath"] }");
            Console.WriteLine($"Platform = { properties["Platform"] }");
            Console.WriteLine($"PlatformTarget = { properties["PlatformTarget"] }");
            Console.WriteLine($"ProjectAssetsFile = { properties["ProjectAssetsFile"] }");
            Console.WriteLine($"ProjectDir = { properties["ProjectDir"] }");
            Console.WriteLine($"RootNamespace = { properties["RootNamespace"] }");
            Console.WriteLine($"RuntimeFrameworkVersion = { properties["RuntimeFrameworkVersion"] }");
            Console.WriteLine($"TargetFileName = { properties["TargetFileName"] }");
            Console.WriteLine($"TargetFrameworkMoniker = { properties["TargetFrameworkMoniker"] }");

            Console.WriteLine("........................................");        

上述是控制台中示例,若咱们在.NET Core  Web应用程序中,此时咱们彻底能够获取到项目文件而无需如控制台写死项目文件路径,以下:

             var projectFiles = Directory.EnumerateFiles(Directory.GetCurrentDirectory(), "*.*proj", SearchOption.TopDirectoryOnly)
                   .Where(f => !string.Equals(Path.GetExtension(f), ".xproj", StringComparison.OrdinalIgnoreCase))
                   .Take(2).ToList();

            var projectFile = projectFiles[0];

            var targetFileName = Path.GetFileName(projectFile) + ".EntityFrameworkCore.targets";
            .......

此时获取到启动项目信息,以下:

到了这里咱们探索完了EF Core如何进行迁移的第一步,同时咱们也明白为什么要将执行命令路径切换到启动项目项目文件所在目录,由于须要获取到项目信息,而后进行Build也就是生成,若是执行生成错误则返回,不然返回项目详细信息。到这里咱们了解了利用MSBuild和CLI工具来获取上下文所在项目详细信息和启动项目详细信息。咱们继续往下探讨。

执行.NET Core必需文件和调用ef.exe或者ef.x86.exe应用程序或者ef.dll程序集执行迁移

经过上述MSBuild和CLI工具咱们获取到上下文和启动项目详细信息,接下来则是进行迁移,如开头第四张图片所示,所执行命令大体以下:

dotnet exec --depsfile [.deps.json] --addtionalprobingpath [nugetpackage] --runtimeconfig [.runtimeconfig.json] ef.dll migrations add 
init -c [DbContext] --assembly [DbContextAssmbly] --startup-assembly [StartupProjectAssembly]

一波刚平息 一波又起,首先咱们得明白上述命令,好比经过读取扩展名为.deps.json文件来执行--depsfile命令,以及读取扩展名为.runtimeconfig.json文件执行--runtimeconfig命令,那么这两个文件是作什么的呢,咱们又得花费一点功夫来说解。接下来咱们利用dotnet命令来建立控制台程序来初识上述两个命令的做用。首先咱们运行以下命令建立控制台程序,在此须要特别说明的是在.NET Core  2.0后当经过dotnet  build后直接包含了执行dotnet restore命令

dotnet new Console

此时同时也会在obj文件夹下生成project.assets.json文件,这个文件是作什么的呢?别着急,咱们先讲完.deps.json和.runtimeconfig.json继续话题会讲到这个文件的做用,咱们继续。

此时咱们继续运行生成命令,以下则会生成bin文件夹,同时在以下.netcoreapp2.1文件夹会生成咱们须要讲到的两个json文件。

dotnet build

 

{
  "runtimeOptions": {
    "tfm": "netcoreapp2.1",
    "framework": {
      "name": "Microsoft.NETCore.App",
      "version": "2.1.0-preview1-26216-03"
    }
  }
}

运行.NET Core应用程序必需要runtimeconfig.json文件,意为“运行时”,咱们也能够翻译为共享框架,且运行时和共享框架概念可任意转换。此json文件为运行时配置选项,若是没有runtimeconfig.json文件,将抛出异常,咱们删除该文件看看。

经过运行时json文件当运行时指示dotnet运行Microsoft.NETCore.App 2.0.0共享框架 此框架是最经常使用的框架,但也存在其余框架,例如Microsoft.AspNetCore.App。 与.NET Framework不一样,可能会在计算机上安装多个.NET Core共享框架。dotnet读取json文件,并在C:\Program Files\dotnet\shared中查找运行该应用程序所需的文件,以下存在多个运行时框架。固然若是咱们安装了更高版本的.net core如2.1.0-preview1-final,此时dotnet将自动选择最高的版本。

好了,咱们算是明白.runtimeconfig.json文件主要是用来指示dotnet在运行时使用哪一个框架。咱们再来看看.deps.json文件,以下:

{
  "runtimeTarget": {
    "name": ".NETCoreApp,Version=v2.1",
    "signature": "da39a3ee5e6b4b0d3255bfef95601890afd80709"
  },
  "compilationOptions": {},
  "targets": {
    ".NETCoreApp,Version=v2.1": {
      "认识.NET Core/1.0.0": {
        "runtime": {
          "认识.NET Core.dll": {}
        }
      }
    }
  },
  "libraries": {
    "认识.NET Core/1.0.0": {
      "type": "project",
      "serviceable": false,
      "sha512": ""
    }
  }
}

deps.json文件是一个依赖关系清单。它能够用来配置来自包的组件的动态连接。NET Core能够配置为从多个位置动态加载程序集,这些位置包括:

应用程序基目录(与入口点应用程序位于同一文件夹中,不须要配置)

  1. 包缓存文件夹(NuGet恢复缓存或NuGet后备文件夹)
  2. 优化的包缓存或运行时包存储。
  3. 共享框架(经过runtimeconfig.json配置)。

好了,对于.deps.json和runtimeconfig.json文件暂时先讲到这里,后续有可能再详细讲解,咱们弄明白了这两个文件的大体做用便可。回到咱们的话题,那么这两个文件是如何找到的呢?那就得结合咱们第一步获取到的项目信息了,在第一部分获取项目信息最后给出的图片里面根据ProjectDir和OutputPath就能够获取到.deps.json和.runtimeconfig.json文件。最后则须要获取ef.dll程序集从而执行相关迁移命令,那么ef.dll程序集是怎么获取到的呢?这个时候就须要获取项目中的信息ProjectAssetsFile即读取project.assets.json文件,获取packageFolders节点下数据,以下:

 "packageFolders": {
    "C:\\Users\\JeffckyWang\\.nuget\\packages\\": {},
    "C:\\Program Files (x86)\\Microsoft SDKs\\NuGetPackagesFallback\\": {},
    "C:\\Program Files\\dotnet\\sdk\\NuGetFallbackFolder": {}
  },

咱们从开头第四张图片可看出对于--addtionalprobingpath有三个路径也就是如上三个路径,咱们看看如上三个路径是否存在ef.dll程序集。

 

如上只有nuget和sdk中有ef.dll程序集,咱们依然看看开头第四张图片最终执行的倒是sdk中的ef.dll程序集,难道是若是nuget和skd目录在project.assets.json中都存在,那么优先从sdk中查找么,也就是sdk中程序集优先级比nuget程序集高吗,若是sdk中存在对应程序集则直接执行吗。当移除该文件中nuget路径,从新生成会覆盖。因此猜想可能优先查找sdk中是否存在ef.dll程序集。这里还需额外说明一点的是咱们在第一节获取到了项目详细信息,其中有一项是TargetFrameworkMoniker,若咱们建立的项目是.NET Framework,此时根据TargetFrameworkMoniker来判断,若为.NETCoreApp则执行上述ef.dll程序集不然执行以下路径应用程序来迁移。

手动执行命令迁移 

上述咱们完整讲述了在命令行中执行dotnet ef命令背后的本质是什么,那么咱们接下来利用代码手动来迁移。以下第一个类为解析进程所需的参数类【从dotnet ef源码拷贝而来】

    public static class Common
    {
        public static string ToArguments(IReadOnlyList<string> args)
        {
            var builder = new StringBuilder();
            for (var i = 0; i < args.Count; i++)
            {
                if (i != 0)
                {
                    builder.Append(" ");
                }

                if (args[i].IndexOf(' ') == -1)
                {
                    builder.Append(args[i]);

                    continue;
                }

                builder.Append("\"");

                var pendingBackslashs = 0;
                for (var j = 0; j < args[i].Length; j++)
                {
                    switch (args[i][j])
                    {
                        case '\"':
                            if (pendingBackslashs != 0)
                            {
                                builder.Append('\\', pendingBackslashs * 2);
                                pendingBackslashs = 0;
                            }
                            builder.Append("\\\"");
                            break;

                        case '\\':
                            pendingBackslashs++;
                            break;

                        default:
                            if (pendingBackslashs != 0)
                            {
                                if (pendingBackslashs == 1)
                                {
                                    builder.Append("\\");
                                }
                                else
                                {
                                    builder.Append('\\', pendingBackslashs * 2);
                                }

                                pendingBackslashs = 0;
                            }

                            builder.Append(args[i][j]);
                            break;
                    }
                }

                if (pendingBackslashs != 0)
                {
                    builder.Append('\\', pendingBackslashs * 2);
                }

                builder.Append("\"");
            }

            return builder.ToString();
        }
    }

项目所需的详细信息,咱们封装成一个类且其中包含执行build命令的方法,以下:

    public class Project
    {
        public string AssemblyName { get; set; }
        public string Language { get; set; }
        public string OutputPath { get; set; }
        public string PlatformTarget { get; set; }
        public string ProjectAssetsFile { get; set; }
        public string ProjectDir { get; set; }
        public string RootNamespace { get; set; }
        public string RuntimeFrameworkVersion { get; set; }
        public string TargetFileName { get; set; }
        public string TargetFrameworkMoniker { get; set; }

        public void Build()
        {
            var args = new List<string>
            {
                "build"
            };

            args.Add("/p:GenerateRuntimeConfigurationFiles=True");
            args.Add("/verbosity:quiet");
            args.Add("/nologo");

            var arg = Common.ToArguments(args);

            var psi = new ProcessStartInfo
            {
                FileName = "dotnet",
                Arguments = arg
            };
            var process = Process.Start(psi);
            process.WaitForExit();
        }
    }

接下来则是获取项目详细信息、生成、迁移,以下三个方法以及对应方法实现。

            //获取项目详细信息
            var projectMedata = GetProjectMedata();

            //生成
            projectMedata.Build();

            //执行EF迁移命令
            ExecuteEFCommand(projectMedata);
        public Project GetProjectMedata()
        {
            var projectFiles = Directory.EnumerateFiles(Directory.GetCurrentDirectory(), "*.*proj", SearchOption.TopDirectoryOnly)
                  .Where(f => !string.Equals(Path.GetExtension(f), ".xproj", StringComparison.OrdinalIgnoreCase))
                  .Take(2).ToList();

            var projectFile = projectFiles[0];

            var targetFileName = Path.GetFileName(projectFile) + ".EntityFrameworkCore.targets";

            var projectExePath = Path.Combine(Path.GetDirectoryName(projectFile), "obj");

            Directory.CreateDirectory(projectExePath);

            var targetFile = Path.Combine(projectExePath, targetFileName);

            System.IO.File.WriteAllText(targetFile,
 @"
<Project>
      <Target Name=""GetEFProjectMetadata"">
        <ItemGroup>
          <EFProjectMetadata Include = ""AssemblyName: $(AssemblyName)"" />
          <EFProjectMetadata Include = ""OutputPath: $(OutputPath)"" />
          <EFProjectMetadata Include = ""Platform: $(Platform)"" />
          <EFProjectMetadata Include = ""PlatformTarget: $(PlatformTarget)"" />
          <EFProjectMetadata Include = ""ProjectAssetsFile: $(ProjectAssetsFile)"" />
          <EFProjectMetadata Include = ""ProjectDir: $(ProjectDir)"" />
          <EFProjectMetadata Include = ""RootNamespace: $(RootNamespace)"" />
          <EFProjectMetadata Include = ""RuntimeFrameworkVersion: $(RuntimeFrameworkVersion)"" />
          <EFProjectMetadata Include = ""TargetFileName: $(TargetFileName)"" />
          <EFProjectMetadata Include = ""TargetFrameworkMoniker: $(TargetFrameworkMoniker)"" />
        </ItemGroup>
        <WriteLinesToFile
                              File =""$(EFProjectMetadataFile)""
                              Lines = ""@(EFProjectMetadata)"" Overwrite=""true"" />
      </Target>
  </Project>");


            var tmpFile = Path.GetTempFileName();
            var psi = new ProcessStartInfo
            {
                FileName = "dotnet",
                Arguments = $"msbuild \"{projectFile}\" /t:GetEFProjectMetadata /nologo \"/p:EFProjectMetadataFile={tmpFile}\""
            };
            var process = Process.Start(psi);
            process.WaitForExit();
            if (process.ExitCode != 0)
            {
                Console.Error.WriteLine("Invoking MSBuild target failed");
            }
            var lines = System.IO.File.ReadAllLines(tmpFile);
            System.IO.File.Delete(tmpFile);

            var properties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
            foreach (var line in lines)
            {
                var idx = line.IndexOf(':');
                if (idx <= 0) continue;
                var name = line.Substring(0, idx)?.Trim();
                var value = line.Substring(idx + 1)?.Trim();
                properties.Add(name, value);
            }

            var project = new Project()
            {
                AssemblyName = properties["AssemblyName"],
                OutputPath = properties["OutputPath"],
                ProjectDir = properties["ProjectDir"],
                ProjectAssetsFile = properties["ProjectAssetsFile"],
                TargetFileName = properties["TargetFileName"],
                TargetFrameworkMoniker = properties["TargetFrameworkMoniker"],
                RuntimeFrameworkVersion = properties["RuntimeFrameworkVersion"],
                PlatformTarget = properties["PlatformTarget"],
                RootNamespace = properties["RootNamespace"]
            };
            return project;
        }
        public void ExecuteEFCommand(Project project)
        {var depsFile = Path.Combine(
                project.ProjectDir,
                project.OutputPath,
                project.AssemblyName + ".deps.json");
            var runtimeConfig = Path.Combine(
                project.ProjectDir,
                 project.OutputPath,
                project.AssemblyName + ".runtimeconfig.json");
            var projectAssetsFile = project.ProjectAssetsFile;

            var args = new List<string>
            {
                "exec",
                "--depsfile"
            };
            args.Add(depsFile);

            var packageSDKFolder = string.Empty;
            if (!string.IsNullOrEmpty(projectAssetsFile))
            {
                using (var reader = new JsonTextReader(System.IO.File.OpenText(projectAssetsFile)))
                {
                    var projectAssets = JToken.ReadFrom(reader);
                    var packageFolders = projectAssets["packageFolders"].Children<JProperty>().Select(p => p.Name);
                    foreach (var packageFolder in packageFolders)
                    {
                        packageSDKFolder = packageFolder;
                        args.Add("--additionalprobingpath");
                        args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar));
                    }
                }
            }
            if (System.IO.File.Exists(runtimeConfig))
            {
                args.Add("--runtimeconfig");
                args.Add(runtimeConfig);
            }
            else if (project.RuntimeFrameworkVersion.Length != 0)
            {
                args.Add("--fx-version");
                args.Add(project.RuntimeFrameworkVersion);
            }

            args.Add(Path.Combine(@"C:\Program Files\dotnet\sdk\NuGetFallbackFolder\microsoft.entityframeworkcore.tools.dotnet\2.0.2\tools\netcoreapp2.0", "ef.dll"));

            args.AddRange(new List<string>() { "migrations", "add", "initial", "-c", "EFCoreDbContext" });
            args.Add("--assembly");
            args.Add(Path.Combine(project.ProjectDir, project.OutputPath, project.TargetFileName));
            args.Add("--startup-assembly");
            args.Add(Path.Combine(project.ProjectDir, project.OutputPath, project.TargetFileName));if (!string.IsNullOrEmpty(project.Language))
            {
                args.Add("--language");
                args.Add(project.Language);
            }

            var arg = Common.ToArguments(args);
            var psi = new ProcessStartInfo
            {
                FileName = "dotnet",
                Arguments = arg,
                UseShellExecute = false
            };
            var process = Process.Start(psi);
            process.WaitForExit();
            if (process.ExitCode != 0)
            {
                Console.WriteLine("Migration failed");
            }
        }

请注意在上述ExecuteEFCommand方法中已明确标注此时目标迁移目录就是上述当前项目,须要迁移到上下文所在类库中,咱们在命令行就能够获得上下文所在项目,此时只须要将上述ExecuteEFCommand方法中标注改成从命令行获取到的项目参数便可,以下咱们直接写死:

 args.Add("--assembly");
 args.Add(Path.Combine(project.ProjectDir, project.OutputPath, "EFCore.Data.dll"));

同时还需添加上下文项目目录参数,以下:

args.Add("--project-dir");            
args.Add(@"C:\Users\JeffckyWang\Source\Repos\WebApplication1\EFCore.Data\"); if (!string.IsNullOrEmpty(project.Language)) { args.Add("--language"); args.Add(project.Language); } .......

最后将启动项目中生成的迁移目录修改成上下文所在项目,以下:

            var sqlStr = @"data source=WANGPENG;User Id=sa;Pwd=sa123;initial catalog=EFCore2xDb;
          integrated security=True;MultipleActiveResultSets=True;
"; services.AddDbContextPool<EFCoreDbContext>(options => { options.UseSqlServer(sqlStr, d => d.MigrationsAssembly("EFCore.Data")); }, 256);

此时咱们再来手动迁移那么将在上下文所在项目中生成迁移文件夹,以下:

了解了执行dotnet ef背后实现的原理,Jeff要说了【那么问题来了】,对于咱们而言有何帮助没有呢,固然有并且立刻能实现,咱们能够写一个批处理文件在发布时直接执行生成数据库表,说完就开干。咱们在上述WebApplication1启动项目中建立名为deploy-efcore.bat批处理文件,代码以下:

set EFCoreMigrationsNamespace=%WebApplication1
set EFCoreMigrationsDllName=%WebApplication1.dll
set EFCoreMigrationsDllDepsJson=%bin\debug\netcoreapp2.0\WebApplication1.deps.json
set PathToNuGetPackages=%USERPROFILE%\.nuget\packages set PathToEfDll=%PathToNuGetPackages%\microsoft.entityframeworkcore.tools.dotnet\2.0.0\tools\netcoreapp2.0\ef.dll dotnet exec --depsfile .\%EFCoreMigrationsDllDepsJson% --additionalprobingpath %PathToNuGetPackages% %PathToEfDll% database update --assembly .\%EFCoreMigrationsDllName% --startup-assembly .\%EFCoreMigrationsDllName% --project-dir . --verbose --root-namespace %EFCoreMigrationsNamespace% pause

总结 

本节咱们详细讲解了执行dotnet ef命令背后究竟发生了什么,同时也大概讨论了下.NET Core几个配置文件的做用,足够了解这些,当出现问题才不至于手足无措,耗时一天多才写完,不过收获颇多,下面咱们给出背后实现大体原理【后面可能会更详细探讨,到时继续更新】图来解释执行dotnet ef命令背后的本质以此来加深印象,但愿对阅读的您也能有所帮助,咱们下节再会。

 

相关文章
相关标签/搜索