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命令背後的本質以此來加深印象,但願對閱讀的您也能有所幫助,咱們下節再會。

 

相關文章
相關標籤/搜索