EntityFramework Core 运行dotnet ef命令迁移背后本质是什么?(EF Core迁移原理)
前言
终于踏出第一步探索EF Core原理和本质,过程虽然比较漫长且枯燥乏味还得反复论证,其中滋味自知,EF Core的强大想必不用我再过多废话,有时候我们是否思考过背后到底做了些什么,到底怎么实现的呢?比如本节要讲的在命令行简单敲下dotnet ef migrations add initial初始化表完事,如此简洁。激起了我的好奇,下面我们来看看。本节内容可能比较多,请耐心。
EntityFramework Core命令基础拾遗
我们提前创建好.NET Core Web应用程序和实体模型以及上下文,园中例子太多且我们也只是探讨迁移原理,无关乎其他。
如此简单一个命令就初始化了表,是不是很神奇,我们接下来要做的就是化神奇为简单。我们接下来将上述迁移文件夹删除,再次运行如下命令,看看迁移详细过程。
- dotnet ef migrations add init -c EFCoreDbContext -p ..\EfCore.Data\ --verbose
通过如上两张图我们可看出EF迁移将会进行两步:第一步则是编译上下文所在项目,编译启动项目。第二步则是通过编译成功后的上下文所在程序集合启动项目程序集最终实现迁移。总结起来就是简单两小步,背后所需要做的很多,请继续往下看。
EntityFramework Core迁移本质
当我们敲写dotnet ef migrations add initial命令后,紧接着会在启动项目obj文件夹会生成如下文件。
这个东西是做什么的呢,我也不知道,我们打开该文件看看。
- <?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 != )
- {
- Console.Error.WriteLine("Invoking MSBuild target failed");
- }
- Console.ReadKey();
- }
默认情况下MSBuildProjectExtensionsPath路径在项目中obj文件夹下如上我们迁移的WebApplication1.csproj.EntityFrameworkCore.targets,我们对targets文件命名一般约定为$(MSBuildProjectExtensionsPath)$(MSBuildProjectFile).<SomethingUnique>.targets,如上代码为我们仿照实际迁移时在obj文件夹下生成的targets文件。当启动dotnet进程运行时会在控制台打印如下参数:
上述只是作为简单的显示信息而使用,利用CLI工具在我们项目内部创建了一个MSBuild目标。这个目标可以完成MSBuild所能做的任何事情,EF Core则是将加载目标读取临时文件的形式来获取项目信息。
- 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 != )
- {
- 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 <= ) continue;
- var name = line.Substring(, idx)?.Trim();
- var value = line.Substring(idx + )?.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().ToList();
- var projectFile = projectFiles[];
- 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可以配置为从多个位置动态加载程序集,这些位置包括:
应用程序基目录(与入口点应用程序位于同一文件夹中,不需要配置)
- 包缓存文件夹(NuGet恢复缓存或NuGet后备文件夹)
- 优化的包缓存或运行时包存储。
- 共享框架(通过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 = ; i < args.Count; i++)
- {
- if (i != )
- {
- builder.Append(" ");
- }
- if (args[i].IndexOf(' ') == -)
- {
- builder.Append(args[i]);
- continue;
- }
- builder.Append("\"");
- var pendingBackslashs = ;
- for (var j = ; j < args[i].Length; j++)
- {
- switch (args[i][j])
- {
- case '\"':
- if (pendingBackslashs != )
- {
- builder.Append('\\', pendingBackslashs * );
- pendingBackslashs = ;
- }
- builder.Append("\\\"");
- break;
- case '\\':
- pendingBackslashs++;
- break;
- default:
- if (pendingBackslashs != )
- {
- if (pendingBackslashs == )
- {
- builder.Append("\\");
- }
- else
- {
- builder.Append('\\', pendingBackslashs * );
- }
- pendingBackslashs = ;
- }
- builder.Append(args[i][j]);
- break;
- }
- }
- if (pendingBackslashs != )
- {
- builder.Append('\\', pendingBackslashs * );
- }
- 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().ToList();
- var projectFile = projectFiles[];
- 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 != )
- {
- 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 <= ) continue;
- var name = line.Substring(, idx)?.Trim();
- var value = line.Substring(idx + )?.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 != )
- {
- 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 != )
- {
- 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"));
- }, );
此时我们再来手动迁移那么将在上下文所在项目中生成迁移文件夹,如下:
了解了执行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命令背后的本质以此来加深印象,希望对阅读的您也能有所帮助,我们下节再会。
EntityFramework Core 运行dotnet ef命令迁移背后本质是什么?(EF Core迁移原理)的更多相关文章
- Cookies 初识 Dotnetspider EF 6.x、EF Core实现dynamic动态查询和EF Core注入多个上下文实例池你知道有什么问题? EntityFramework Core 运行dotnet ef命令迁移背后本质是什么?(EF Core迁移原理)
Cookies 1.创建HttpCookies Cookie=new HttpCookies("CookieName");2.添加内容Cookie.Values.Add(&qu ...
- .net core 运行不需命令行
1.问题情景: 需要保证已安装.net core SDK,并且命令提示符下运行“dotnet --version”,有反应. 如果之前运行良好,现在却不行了,查看安装程序中存在.net core SD ...
- DotNet Run 命令介绍
前言 本篇主要介绍 asp.net core 中,使用 dotnet tools 运行 dotnet run 之后的系统执行过程. 如果你觉得对你有帮助的话,不妨点个[推荐]. 目录 dotnet r ...
- dotnet tools 运行 dotnet run
dotnet tools 运行 dotnet run dotnet run 命令介绍 前言 本篇主要介绍 asp.net core 中,使用 dotnet tools 运行 dotnet run 之后 ...
- 《ASP.NET Core In Action》读书笔记系列四 创建ASP.NET Core 应用步骤及相应CLI命令
一般情况下,我们都是从一个模板(template)开始创建应用的(模板:提供构建应用程序所需的基本代码).本节使用 Visual Studio 2017 .ASP.NET Core2.0和 Visua ...
- 在Linux安装ASP.NET Core运行时环境
我使用的是Centos7 ,其它的Linux请参考微软文档 微软官方介绍文档: https://www.microsoft.com/n ...
- 更新.net core 3.0,dotnet ef命令无法使用的解决办法
之前项目采用.net core 2.2 实现,今天更新vs2019,系统.net core也被升级到3.0,在cmd中使用dotnet ef命令出现 “无法执行,因为找不到指定的命令或文件.可能的原因 ...
- .net core执行dotnet ef migrations createmodel等命令出错
.net core执行dotnet ef migrations createmodel等命令出错 执行dotnet ef migrations createmodel.dotnet ef migrat ...
- ASP.NET CORE dotnet run 命令使用debug方式运行
由于我的开发环境比较复杂,每次调试一套项目都要启动好几个VS,比较繁琐,今天决定换一种方式调试,对于不该改动的代码的附加项目直接使用dotnet run命令以debug的运行方式运行, 一开始无法运行 ...
随机推荐
- nodejs之socket.io模块——实现了websocket协议
Nodejs实现websocket的4种方式:socket.io.WebSocket-Node.faye-websocket-node.node-websocket-server,主要使用的是sock ...
- H3C无线路由器安装与设置
一.电脑与路由器的连接利用一根cat5e网线一头连接到电脑上笔记本或台式机都可以,另一头连接到无线路由器的LAN口任意LAN口都可以二.设置无线路由器完成路由器安装与电脑连接后,接下首次使用就需要设置 ...
- 【前端】Vue和Vux开发WebApp日志二、优化gulp任务
转载请注明出处:http://www.cnblogs.com/shamoyuu/p/vue_vux_2.html 项目github地址:https://github.com/shamoyuu/vue- ...
- Linux XZ压缩格式学习
XZ的介绍 今天升级Python的时候,下载的Python-2.7.8.tar.xz安装包为xz格式,好吧,我又孤陋寡闻了,居然第一次遇见xz格式的压缩文件.搜索了一下资料,下面是xz的一些介绍: ...
- python︱模块加载(pip安装)以及pycharm安装与报错解决方式
每每以为攀得众山小,可.每每又切实来到起点,大牛们,缓缓脚步来俺笔记葩分享一下吧,please~ --------------------------- 准备放下R开始学python,真是痛苦,因为找 ...
- TOE(TCP/IP Offload Engine)网卡与一般网卡的区别
TCP减压引擎,第一次听说这个名词,但是并不是一个新的概念了,若干年前听说过设备厂商在研究在FPGA之中实现TCP Stack,但是后来没有听到任何的产品出来,应该是路由设备to host的traff ...
- JavaScript split()函数
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- Codeforces Round #427 (Div. 2) D - Palindromic characteristics
本题是个简单的区间dp 最近都没时间做题了,被我妈强制喊回去,然后颓废了10天(回家也没发控制住自己= = 我的锅),计划都打乱了,本来还报名了百度之星,然后没时间参加 #include<cma ...
- freemarker之数组(十八)
1.设计思路 (1)声明一个数组 (2)打印数组中的元素 2.设计源码 <#--freemarker数组--> <#assign nums=[12,34,56,78,90,54,23 ...
- UltraEdit 脚本 实现查找替换
UltraEdit中,要实现,脚本查找替换功能,按照下文中的做法稍作修改, 现象很奇怪,有时可以进行查找替换有时不能. http://blog.csdn.net/neareast/article/de ...