diff --git a/EFCore.sln b/EFCore.sln
index 2f9c6c8e04..af923627fa 100644
--- a/EFCore.sln
+++ b/EFCore.sln
@@ -1,7 +1,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26214.1
+VisualStudioVersion = 15.0.26228.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore", "src\EFCore\EFCore.csproj", "{715C38E9-B2F5-4DB2-8025-0C6492DEBDD4}"
EndProject
@@ -69,6 +69,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Benchmarks.EFCore",
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Benchmarks.EF6", "test\EFCore.Benchmarks.EF6\EFCore.Benchmarks.EF6.csproj", "{477EBF1E-A4B8-4D60-8681-5D6D5BB42CE1}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-ef", "src\dotnet-ef\dotnet-ef.csproj", "{2D66A1DA-D102-4DD9-960B-7D863BBB53DE}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ef", "src\ef\ef.csproj", "{4F7C93F3-A30F-4061-804C-32293DC256A1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Tools", "src\EFCore.Tools\EFCore.Tools.csproj", "{87ADBDB5-CA57-4EAB-9A8A-5E89480C9C6D}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EFCore.Tools.DotNet", "src\EFCore.Tools.DotNet\EFCore.Tools.DotNet.csproj", "{31ED3EA7-8270-478D-935D-0067BD7935B7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-ef.Tests", "test\dotnet-ef.Tests\dotnet-ef.Tests.csproj", "{27018CE2-C235-439C-80F2-C573C8904892}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ef.Tests", "test\ef.Tests\ef.Tests.csproj", "{935B51B9-A9B9-4DA2-93A2-663D3BCEAA83}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -199,6 +211,30 @@ Global
{477EBF1E-A4B8-4D60-8681-5D6D5BB42CE1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{477EBF1E-A4B8-4D60-8681-5D6D5BB42CE1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{477EBF1E-A4B8-4D60-8681-5D6D5BB42CE1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2D66A1DA-D102-4DD9-960B-7D863BBB53DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2D66A1DA-D102-4DD9-960B-7D863BBB53DE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2D66A1DA-D102-4DD9-960B-7D863BBB53DE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2D66A1DA-D102-4DD9-960B-7D863BBB53DE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4F7C93F3-A30F-4061-804C-32293DC256A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4F7C93F3-A30F-4061-804C-32293DC256A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4F7C93F3-A30F-4061-804C-32293DC256A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4F7C93F3-A30F-4061-804C-32293DC256A1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {87ADBDB5-CA57-4EAB-9A8A-5E89480C9C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {87ADBDB5-CA57-4EAB-9A8A-5E89480C9C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {87ADBDB5-CA57-4EAB-9A8A-5E89480C9C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {87ADBDB5-CA57-4EAB-9A8A-5E89480C9C6D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {31ED3EA7-8270-478D-935D-0067BD7935B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {31ED3EA7-8270-478D-935D-0067BD7935B7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {31ED3EA7-8270-478D-935D-0067BD7935B7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {31ED3EA7-8270-478D-935D-0067BD7935B7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {27018CE2-C235-439C-80F2-C573C8904892}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {27018CE2-C235-439C-80F2-C573C8904892}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {27018CE2-C235-439C-80F2-C573C8904892}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {27018CE2-C235-439C-80F2-C573C8904892}.Release|Any CPU.Build.0 = Release|Any CPU
+ {935B51B9-A9B9-4DA2-93A2-663D3BCEAA83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {935B51B9-A9B9-4DA2-93A2-663D3BCEAA83}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {935B51B9-A9B9-4DA2-93A2-663D3BCEAA83}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {935B51B9-A9B9-4DA2-93A2-663D3BCEAA83}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -235,5 +271,11 @@ Global
{E3146D04-6E87-41C1-A2E3-F2D7CDFB9B99} = {258D5057-81B9-40EC-A872-D21E27452749}
{049A9748-B3CE-4412-B455-F7108B3BA239} = {258D5057-81B9-40EC-A872-D21E27452749}
{477EBF1E-A4B8-4D60-8681-5D6D5BB42CE1} = {258D5057-81B9-40EC-A872-D21E27452749}
+ {2D66A1DA-D102-4DD9-960B-7D863BBB53DE} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
+ {4F7C93F3-A30F-4061-804C-32293DC256A1} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
+ {87ADBDB5-CA57-4EAB-9A8A-5E89480C9C6D} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
+ {31ED3EA7-8270-478D-935D-0067BD7935B7} = {CE6B50B2-34AE-44C9-940A-4E48C3E1B3BC}
+ {27018CE2-C235-439C-80F2-C573C8904892} = {258D5057-81B9-40EC-A872-D21E27452749}
+ {935B51B9-A9B9-4DA2-93A2-663D3BCEAA83} = {258D5057-81B9-40EC-A872-D21E27452749}
EndGlobalSection
EndGlobal
diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json
index b153ab1515..504d23066a 100644
--- a/NuGetPackageVerifier.json
+++ b/NuGetPackageVerifier.json
@@ -1,4 +1,16 @@
{
+ "adx": {
+ "rules": [
+ "AdxVerificationCompositeRule"
+ ],
+ "packages": {
+ "Microsoft.EntityFrameworkCore.Tools.DotNet": {
+ "packageTypes": [
+ "DotnetCliTool"
+ ]
+ }
+ }
+ },
"Default": {
"rules": [
"DefaultCompositeRule"
diff --git a/src/EFCore.Tools.DotNet/EFCore.Tools.DotNet.csproj b/src/EFCore.Tools.DotNet/EFCore.Tools.DotNet.csproj
new file mode 100644
index 0000000000..5b650070c6
--- /dev/null
+++ b/src/EFCore.Tools.DotNet/EFCore.Tools.DotNet.csproj
@@ -0,0 +1,28 @@
+
+
+
+
+
+ netcoreapp1.0
+ $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
+ true
+ false
+
+
+
+
+
+
+
+
+
+ version=$(PackageVersion);configuration=$(Configuration)
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/EFCore.Tools.DotNet/EFCore.Tools.DotNet.nuspec b/src/EFCore.Tools.DotNet/EFCore.Tools.DotNet.nuspec
new file mode 100644
index 0000000000..056048de47
--- /dev/null
+++ b/src/EFCore.Tools.DotNet/EFCore.Tools.DotNet.nuspec
@@ -0,0 +1,31 @@
+
+
+
+ Microsoft.EntityFrameworkCore.Tools.DotNet
+ $version$
+ Microsoft
+ false
+ Entity Framework Core .NET Command Line Tools. Includes dotnet-ef.
+ Entity Framework Core,entity-framework-core,EF,Data,O/RM
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/EFCore.Tools.DotNet/prefercliruntime b/src/EFCore.Tools.DotNet/prefercliruntime
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/EFCore.Tools/EFCore.Tools.csproj b/src/EFCore.Tools/EFCore.Tools.csproj
new file mode 100644
index 0000000000..109749ebca
--- /dev/null
+++ b/src/EFCore.Tools/EFCore.Tools.csproj
@@ -0,0 +1,28 @@
+
+
+
+
+
+ net451;netcoreapp1.0
+ $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec
+ true
+ true
+ false
+
+
+
+
+
+
+
+
+ version=$(PackageVersion);configuration=$(Configuration)
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/EFCore.Tools/EFCore.Tools.nuspec b/src/EFCore.Tools/EFCore.Tools.nuspec
new file mode 100644
index 0000000000..2b7ada242b
--- /dev/null
+++ b/src/EFCore.Tools/EFCore.Tools.nuspec
@@ -0,0 +1,30 @@
+
+
+
+ Microsoft.EntityFrameworkCore.Tools
+ $version$
+ Microsoft
+ false
+ true
+ Entity Framework Core Package Manager Console Tools. Includes Scaffold-DbContext, Add-Migration, and Update-Database.
+ Entity Framework Core,entity-framework-core,EF,Data,O/RM
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/EFCore.Tools/lib/net451/_._ b/src/EFCore.Tools/lib/net451/_._
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/EFCore.Tools/lib/netstandard1.3/_._ b/src/EFCore.Tools/lib/netstandard1.3/_._
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/EFCore.Tools/tools/EntityFrameworkCore.PowerShell2.psd1 b/src/EFCore.Tools/tools/EntityFrameworkCore.PowerShell2.psd1
new file mode 100644
index 0000000000..72c6ce645c
--- /dev/null
+++ b/src/EFCore.Tools/tools/EntityFrameworkCore.PowerShell2.psd1
@@ -0,0 +1,87 @@
+@{
+ # Script module or binary module file associated with this manifest
+ ModuleToProcess = 'EntityFrameworkCore.PowerShell2.psm1'
+
+ # Version number of this module.
+ ModuleVersion = '1.1.0'
+
+ # ID used to uniquely identify this module
+ GUID = '2de7c7fd-c848-41d7-8634-37fed4d3bb36'
+
+ # Author of this module
+ Author = 'Entity Framework Team'
+
+ # Company or vendor of this module
+ CompanyName = 'Microsoft Corporation'
+
+ # Copyright statement for this module
+ Copyright = '(c) .NET Foundation. All rights reserved.'
+
+ # Description of the functionality provided by this module
+ Description = 'Entity Framework Core Package Manager Console Tools'
+
+ # Minimum version of the Windows PowerShell engine required by this module
+ PowerShellVersion = '1.0'
+
+ # Name of the Windows PowerShell host required by this module
+ PowerShellHostName = 'Package Manager Host'
+
+ # Minimum version of the Windows PowerShell host required by this module
+ PowerShellHostVersion = '1.2'
+
+ # Minimum version of the .NET Framework required by this module
+ DotNetFrameworkVersion = '4.0'
+
+ # Minimum version of the common language runtime (CLR) required by this module
+ CLRVersion = ''
+
+ # Processor architecture (None, X86, Amd64, IA64) required by this module
+ ProcessorArchitecture = ''
+
+ # Modules that must be imported into the global environment prior to importing this module
+ RequiredModules = 'NuGet'
+
+ # Assemblies that must be loaded prior to importing this module
+ RequiredAssemblies = @()
+
+ # Script files (.ps1) that are run in the caller's environment prior to importing this module
+ ScriptsToProcess = @()
+
+ # Type files (.ps1xml) to be loaded when importing this module
+ TypesToProcess = @()
+
+ # Format files (.ps1xml) to be loaded when importing this module
+ FormatsToProcess = @()
+
+ # Modules to import as nested modules of the module specified in ModuleToProcess
+ NestedModules = @()
+
+ # Functions to export from this module
+ FunctionsToExport = (
+ 'Add-Migration',
+ 'Drop-Database',
+ 'Enable-Migrations',
+ 'Remove-Migration',
+ 'Scaffold-DbContext',
+ 'Script-Migration',
+ 'Update-Database'
+ )
+
+ # Cmdlets to export from this module
+ CmdletsToExport = @()
+
+ # Variables to export from this module
+ VariablesToExport = @()
+
+ # Aliases to export from this module
+ AliasesToExport = @()
+
+ # List of all modules packaged with this module
+ ModuleList = @()
+
+ # List of all files packaged with this module
+ FileList = @()
+
+ # Private data to pass to the module specified in ModuleToProcess
+ PrivateData = ''
+}
diff --git a/src/EFCore.Tools/tools/EntityFrameworkCore.PowerShell2.psm1 b/src/EFCore.Tools/tools/EntityFrameworkCore.PowerShell2.psm1
new file mode 100644
index 0000000000..3c25dd1beb
--- /dev/null
+++ b/src/EFCore.Tools/tools/EntityFrameworkCore.PowerShell2.psm1
@@ -0,0 +1,53 @@
+$ErrorActionPreference = 'Stop'
+
+$versionErrorMessage = 'The Entity Framework Core Package Manager Console Tools don''t support PowerShell version ' +
+ "$($PSVersionTable.PSVersion). Upgrade to PowerShell version 3.0 or higher, restart Visual Studio, and try again."
+
+function Add-Migration
+{
+ WarnIfEF6 'Add-Migration'
+
+ throw $versionErrorMessage
+}
+
+function Drop-Database
+{
+ throw $versionErrorMessage
+}
+
+function Enable-Migrations
+{
+ WarnIfEF6 'Enable-Migrations'
+
+ throw $versionErrorMessage
+}
+
+function Remove-Migration
+{
+ throw $versionErrorMessage
+}
+
+function Scaffold-DbContext
+{
+ throw $versionErrorMessage
+}
+
+function Script-Migration
+{
+ throw $versionErrorMessage
+}
+
+function Update-Database
+{
+ WarnIfEF6 'Update-Database'
+
+ throw $versionErrorMessage
+}
+
+function WarnIfEF6($cmdlet)
+{
+ if (Get-Module 'EntityFramework')
+ {
+ Write-Warning "Both Entity Framework Core and Entity Framework 6 are installed. The Entity Framework Core tools are running. Use 'EntityFramework\$cmdlet' for Entity Framework 6."
+ }
+}
\ No newline at end of file
diff --git a/src/EFCore.Tools/tools/EntityFrameworkCore.psd1 b/src/EFCore.Tools/tools/EntityFrameworkCore.psd1
new file mode 100644
index 0000000000..ab8189dad4
--- /dev/null
+++ b/src/EFCore.Tools/tools/EntityFrameworkCore.psd1
@@ -0,0 +1,87 @@
+@{
+ # Script module or binary module file associated with this manifest
+ ModuleToProcess = 'EntityFrameworkCore.psm1'
+
+ # Version number of this module.
+ ModuleVersion = '1.1.0'
+
+ # ID used to uniquely identify this module
+ GUID = 'c126fb40-c0f1-43ae-8dd0-06bb50512eb2'
+
+ # Author of this module
+ Author = 'Entity Framework Team'
+
+ # Company or vendor of this module
+ CompanyName = 'Microsoft Corporation'
+
+ # Copyright statement for this module
+ Copyright = '(c) .NET Foundation. All rights reserved.'
+
+ # Description of the functionality provided by this module
+ Description = 'Entity Framework Core Package Manager Console Tools'
+
+ # Minimum version of the Windows PowerShell engine required by this module
+ PowerShellVersion = '3.0'
+
+ # Name of the Windows PowerShell host required by this module
+ PowerShellHostName = 'Package Manager Host'
+
+ # Minimum version of the Windows PowerShell host required by this module
+ PowerShellHostVersion = '1.2'
+
+ # Minimum version of the .NET Framework required by this module
+ DotNetFrameworkVersion = '4.0'
+
+ # Minimum version of the common language runtime (CLR) required by this module
+ CLRVersion = ''
+
+ # Processor architecture (None, X86, Amd64, IA64) required by this module
+ ProcessorArchitecture = ''
+
+ # Modules that must be imported into the global environment prior to importing this module
+ RequiredModules = 'NuGet'
+
+ # Assemblies that must be loaded prior to importing this module
+ RequiredAssemblies = @()
+
+ # Script files (.ps1) that are run in the caller's environment prior to importing this module
+ ScriptsToProcess = @()
+
+ # Type files (.ps1xml) to be loaded when importing this module
+ TypesToProcess = @()
+
+ # Format files (.ps1xml) to be loaded when importing this module
+ FormatsToProcess = @()
+
+ # Modules to import as nested modules of the module specified in ModuleToProcess
+ NestedModules = @()
+
+ # Functions to export from this module
+ FunctionsToExport = (
+ 'Add-Migration',
+ 'Drop-Database',
+ 'Enable-Migrations',
+ 'Remove-Migration',
+ 'Scaffold-DbContext',
+ 'Script-Migration',
+ 'Update-Database'
+ )
+
+ # Cmdlets to export from this module
+ CmdletsToExport = @()
+
+ # Variables to export from this module
+ VariablesToExport = @()
+
+ # Aliases to export from this module
+ AliasesToExport = @()
+
+ # List of all modules packaged with this module
+ ModuleList = @()
+
+ # List of all files packaged with this module
+ FileList = @()
+
+ # Private data to pass to the module specified in ModuleToProcess
+ PrivateData = ''
+}
diff --git a/src/EFCore.Tools/tools/EntityFrameworkCore.psm1 b/src/EFCore.Tools/tools/EntityFrameworkCore.psm1
new file mode 100644
index 0000000000..247046ee7a
--- /dev/null
+++ b/src/EFCore.Tools/tools/EntityFrameworkCore.psm1
@@ -0,0 +1,1102 @@
+$ErrorActionPreference = 'Stop'
+
+#
+# Add-Migration
+#
+
+Register-TabExpansion Add-Migration @{
+ OutputDir = { <# Disabled. Otherwise, paths would be relative to the solution directory. #> }
+ Context = { param($x) GetContextTypes $x.Project $x.StartupProject $x.Environment }
+ Project = { GetProjects }
+ StartupProject = { GetProjects }
+}
+
+<#
+.SYNOPSIS
+ Adds a new migration.
+
+.DESCRIPTION
+ Adds a new migration.
+
+.PARAMETER Name
+ The name of the migration.
+
+.PARAMETER OutputDir
+ The directory (and sub-namespace) to use. Paths are relative to the project directory. Defaults to "Migrations".
+
+.PARAMETER Context
+ The DbContext type to use.
+
+.PARAMETER Environment
+ The environment to use. Defaults to "Development".
+
+.PARAMETER Project
+ The project to use.
+
+.PARAMETER StartupProject
+ The startup project to use. Defaults to the solution's startup project.
+
+.LINK
+ Remove-Migration
+ Update-Database
+ about_EntityFrameworkCore
+#>
+function Add-Migration
+{
+ [CmdletBinding(PositionalBinding = $false)]
+ param(
+ [Parameter(Position = 0, Mandatory = $true)]
+ [string] $Name,
+ [string] $OutputDir,
+ [string] $Context,
+ [string] $Environment,
+ [string] $Project,
+ [string] $StartupProject)
+
+ WarnIfEF6 'Add-Migration'
+
+ $dteProject = GetProject $Project
+ $dteStartupProject = GetStartupProject $StartupProject $dteProject
+
+ $params = 'migrations', 'add', $Name, '--json'
+
+ if ($OutputDir)
+ {
+ $params += '--output-dir', $OutputDir
+ }
+
+ $params += GetParams $Context $Environment
+
+ # NB: -join is here to support ConvertFrom-Json on PowerShell 3.0
+ $result = (EF $dteProject $dteStartupProject $params) -join "`n" | ConvertFrom-Json
+ Write-Output 'To undo this action, use Remove-Migration.'
+
+ $dteProject.ProjectItems.AddFromFile($result.migrationFile) | Out-Null
+ $DTE.ItemOperations.OpenFile($result.migrationFile) | Out-Null
+ ShowConsole
+
+ $dteProject.ProjectItems.AddFromFile($result.metadataFile) | Out-Null
+
+ $dteProject.ProjectItems.AddFromFile($result.snapshotFile) | Out-Null
+}
+
+#
+# Drop-Database
+#
+
+Register-TabExpansion Drop-Database @{
+ Context = { param($x) GetContextTypes $x.Environment $x.Project $x.StartupProject }
+ Project = { GetProjects }
+ StartupProject = { GetProjects }
+}
+
+<#
+.SYNOPSIS
+ Drops the database.
+
+.DESCRIPTION
+ Drops the database.
+
+.PARAMETER Context
+ The DbContext to use.
+
+.PARAMETER Environment
+ The environment to use. Defaults to "Development".
+
+.PARAMETER Project
+ The project to use.
+
+.PARAMETER StartupProject
+ The startup project to use. Defaults to the solution's startup project.
+
+.LINK
+ Update-Database
+ about_EntityFrameworkCore
+#>
+function Drop-Database
+{
+ [CmdletBinding(PositionalBinding = $false, SupportsShouldProcess = $true, ConfirmImpact = 'High')]
+ param([string] $Context, [string] $Environment, [string] $Project, [string] $StartupProject)
+
+ $dteProject = GetProject $Project
+ $dteStartupProject = GetStartupProject $StartupProject $dteProject
+
+ if (IsUWP $dteProject)
+ {
+ throw 'Drop-Database shouldn''t be used with Universal Windows Platform apps. Instead, call ' +
+ 'DbContext.Database.EnsureDeleted() at runtime.'
+ }
+
+ $params = 'dbcontext', 'info', '--json'
+ $params += GetParams $Context $Environment
+
+ # NB: -join is here to support ConvertFrom-Json on PowerShell 3.0
+ $info = (EF $dteProject $dteStartupProject $params) -join "`n" | ConvertFrom-Json
+
+ if ($PSCmdlet.ShouldProcess("database '$($info.databaseName)' on server '$($info.dataSource)'"))
+ {
+ $params = 'database', 'drop', '--force'
+ $params += GetParams $Context $Environment
+
+ EF $dteProject $dteStartupProject $params -skipBuild
+ }
+}
+
+#
+# Enable-Migrations (Obsolete)
+#
+
+function Enable-Migrations
+{
+ WarnIfEF6 'Update-Database'
+ Write-Warning 'Enable-Migrations is obsolete. Use Add-Migration to start using Migrations.'
+}
+
+#
+# Remove-Migration
+#
+
+Register-TabExpansion Remove-Migration @{
+ Context = { param($x) GetContextTypes $x.Environment $x.Project $x.StartupProject }
+ Project = { GetProjects }
+ StartupProject = { GetProjects }
+}
+
+<#
+.SYNOPSIS
+ Removes the last migration.
+
+.DESCRIPTION
+ Removes the last migration.
+
+.PARAMETER Force
+ Don't check to see if the migration has been applied to the database. Always implied on UWP apps.
+
+.PARAMETER Context
+ The DbContext to use.
+
+.PARAMETER Environment
+ The environment to use. Defaults to "Development".
+
+.PARAMETER Project
+ The project to use.
+
+.PARAMETER StartupProject
+ The startup project to use. Defaults to the solution's startup project.
+
+.LINK
+ Add-Migration
+ about_EntityFrameworkCore
+#>
+function Remove-Migration
+{
+ [CmdletBinding(PositionalBinding = $false)]
+ param([switch] $Force, [string] $Context, [string] $Environment, [string] $Project, [string] $StartupProject)
+
+ $dteProject = GetProject $Project
+ $dteStartupProject = GetStartupProject $StartupProject $dteProject
+
+ if (IsUWP $dteStartupProject)
+ {
+ $Force = [switch]::Present
+ }
+
+ $params = 'migrations', 'remove', '--json'
+
+ if ($Force)
+ {
+ $params += '--force'
+ }
+
+ $params += GetParams $Context $Environment
+
+ # NB: -join is here to support ConvertFrom-Json on PowerShell 3.0
+ $result = (EF $dteProject $dteStartupProject $params) -join "`n" | ConvertFrom-Json
+
+ $result | %{
+ $projectItem = GetProjectItem $dteProject $_
+ if ($projectItem)
+ {
+ $projectItem.Remove()
+ }
+ }
+}
+
+#
+# Scaffold-DbContext
+#
+
+Register-TabExpansion Scaffold-DbContext @{
+ Provider = { param($x) GetProviders $x.Project }
+ Project = { GetProjects }
+ StartupProject = { GetProjects }
+ OutputDir = { <# Disabled. Otherwise, paths would be relative to the solution directory. #> }
+}
+
+<#
+.SYNOPSIS
+ Scaffolds a DbContext and entity types for a database.
+
+.DESCRIPTION
+ Scaffolds a DbContext and entity types for a database.
+
+.PARAMETER Connection
+ The connection string to the database.
+
+.PARAMETER Provider
+ The provider to use. (E.g. Microsoft.EntityFrameworkCore.SqlServer)
+
+.PARAMETER OutputDir
+ The directory to put files in. Paths are relaive to the project directory.
+
+.PARAMETER Context
+ The name of the DbContext to generate.
+
+.PARAMETER Schemas
+ The schemas of tables to generate entity types for.
+
+.PARAMETER Tables
+ The tables to generate entity types for.
+
+.PARAMETER DataAnnotations
+ Use attributes to configure the model (where possible). If omitted, only the fluent API is used.
+
+.PARAMETER Force
+ Overwrite existing files.
+
+.PARAMETER Environment
+ The environment to use. Defaults to "Development".
+
+.PARAMETER Project
+ The project to use.
+
+.PARAMETER StartupProject
+ The startup project to use. Defaults to the solution's startup project.
+
+.LINK
+ about_EntityFrameworkCore
+#>
+function Scaffold-DbContext
+{
+ [CmdletBinding(PositionalBinding = $false)]
+ param(
+ [Parameter(Position = 0, Mandatory = $true)]
+ [string] $Connection,
+ [Parameter(Position = 1, Mandatory = $true)]
+ [string] $Provider,
+ [string] $OutputDir,
+ [string] $Context,
+ [string[]] $Schemas = @(),
+ [string[]] $Tables = @(),
+ [switch] $DataAnnotations,
+ [switch] $Force,
+ [string] $Environment,
+ [string] $Project,
+ [string] $StartupProject)
+
+ $dteProject = GetProject $Project
+ $dteStartupProject = GetStartupProject $StartupProject $dteProject
+
+ $params = 'dbcontext', 'scaffold', $Connection, $Provider, '--json'
+
+ if ($OutputDir)
+ {
+ $params += '--output-dir', $OutputDir
+ }
+
+ if ($Context)
+ {
+ $params += '--context', $Context
+ }
+
+ $params += $Schemas | %{ '--schema', $_ }
+ $params += $Tables | %{ '--table', $_ }
+
+ if ($DataAnnotations)
+ {
+ $params += '--data-annotations'
+ }
+
+ if ($Force)
+ {
+ $params += '--force'
+ }
+
+ $params += GetParams -Environment $Environment
+
+ # NB: -join is here to support ConvertFrom-Json on PowerShell 3.0
+ $result = (EF $dteProject $dteStartupProject $params) -join "`n" | ConvertFrom-Json
+
+ $result | %{ $dteProject.ProjectItems.AddFromFile($_) | Out-Null }
+ $DTE.ItemOperations.OpenFile($result[0]) | Out-Null
+ ShowConsole
+}
+
+#
+# Script-Migration
+#
+
+Register-TabExpansion Script-Migration @{
+ From = { param($x) GetMigrations $x.Context $x.Environment $x.Project $x.StartupProject }
+ To = { param($x) GetMigrations $x.Context $x.Environment $x.Project $x.StartupProject }
+ Context = { param($x) GetContextTypes $x.Environment $x.Project $x.StartupProject }
+ Project = { GetProjects }
+ StartupProject = { GetProjects }
+}
+
+<#
+.SYNOPSIS
+ Generates a SQL script from migrations.
+
+.DESCRIPTION
+ Generates a SQL script from migrations.
+
+.PARAMETER From
+ The starting migration. Defaults to '0' (the initial database).
+
+.PARAMETER To
+ The ending migration. Defaults to the last migration.
+
+.PARAMETER Idempotent
+ Generate a script that can be used on a database at any migration.
+
+.PARAMETER Output
+ The file to write the result to.
+
+.PARAMETER Context
+ The DbContext to use.
+
+.PARAMETER Environment
+ The environment to use. Defaults to "Development".
+
+.PARAMETER Project
+ The project to use.
+
+.PARAMETER StartupProject
+ The startup project to use. Defaults to the solution's startup project.
+
+.LINK
+ Update-Database
+ about_EntityFrameworkCore
+#>
+function Script-Migration
+{
+ [CmdletBinding(PositionalBinding = $false)]
+ param(
+ [Parameter(ParameterSetName = 'WithoutTo', Position = 0)]
+ [Parameter(ParameterSetName = 'WithTo', Position = 0, Mandatory = $true)]
+ [string] $From,
+ [Parameter(ParameterSetName = 'WithTo', Position = 1, Mandatory = $true)]
+ [string] $To,
+ [switch] $Idempotent,
+ [string] $Output,
+ [string] $Context,
+ [string] $Environment,
+ [string] $Project,
+ [string] $StartupProject)
+
+ $dteProject = GetProject $Project
+ $dteStartupProject = GetStartupProject $StartupProject $dteProject
+
+ if (!$Output)
+ {
+ $intermediatePath = GetIntermediatePath $dteProject
+ if (!(Split-Path $intermediatePath -IsAbsolute))
+ {
+ $projectDir = GetProperty $dteProject.Properties 'FullPath'
+ $intermediatePath = Join-Path $projectDir $intermediatePath -Resolve
+ }
+
+ $scriptFileName = [IO.Path]::ChangeExtension([IO.Path]::GetRandomFileName(), '.sql')
+ $Output = Join-Path $intermediatePath $scriptFileName
+ }
+ elseif (!(Split-Path $Output -IsAbsolute))
+ {
+ $Output = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Output)
+ }
+
+ $params = 'migrations', 'script', '--output', $Output
+
+ if ($From)
+ {
+ $params += $From
+ }
+
+ if ($To)
+ {
+ $params += $To
+ }
+
+ if ($Idempotent)
+ {
+ $params += '--idempotent'
+ }
+
+ $params += GetParams $Context $Environment
+
+ EF $dteProject $dteStartupProject $params
+
+ $DTE.ItemOperations.OpenFile($Output) | Out-Null
+ ShowConsole
+}
+
+#
+# Update-Database
+#
+
+Register-TabExpansion Update-Database @{
+ Migration = { param($x) GetMigrations $x.Context $x.Environment $x.Project $x.StartupProject }
+ Context = { param($x) GetContextTypes $x.Environment $x.Project $x.StartupProject }
+ Project = { GetProjects }
+ StartupProject = { GetProjects }
+}
+
+<#
+.SYNOPSIS
+ Updates the database to a specified migration.
+
+.DESCRIPTION
+ Updates the database to a specified migration.
+
+.PARAMETER Migration
+ The target migration. If '0', all migrations will be reverted. Defaults to the last migration.
+
+.PARAMETER Context
+ The DbContext to use.
+
+.PARAMETER Environment
+ The environment to use. Defaults to "Development".
+
+.PARAMETER Project
+ The project to use.
+
+.PARAMETER StartupProject
+ The startup project to use. Defaults to the solution's startup project.
+
+.LINK
+ Script-Migration
+ about_EntityFrameworkCore
+#>
+function Update-Database
+{
+ [CmdletBinding(PositionalBinding = $false)]
+ param(
+ [Parameter(Position = 0)]
+ [string] $Migration,
+ [string] $Context,
+ [string] $Environment,
+ [string] $Project,
+ [string] $StartupProject)
+
+ WarnIfEF6 'Update-Database'
+
+ $dteProject = GetProject $Project
+ $dteStartupProject = GetStartupProject $StartupProject $dteProject
+
+ if (IsUWP $dteStartupProject)
+ {
+ throw 'Update-Database shouldn''t be used with Universal Windows Platform apps. Instead, call ' +
+ 'DbContext.Database.Migrate() at runtime.'
+ }
+
+ $params = 'database', 'update'
+
+ if ($Migration)
+ {
+ $params += $Migration
+ }
+
+ $params += GetParams $Context $Environment
+
+ EF $dteProject $dteStartupProject $params
+}
+
+#
+# (Private Helpers)
+#
+
+function GetProjects
+{
+ return Get-Project -All | %{ $_.ProjectName }
+}
+
+function GetProviders($projectName)
+{
+ if (!$projectName)
+ {
+ $projectName = (Get-Project).ProjectName
+ }
+
+ return Get-Package -ProjectName $projectName | %{ $_.Id }
+}
+
+function GetContextTypes($environment, $projectName, $startupProjectName)
+{
+ $project = GetProject $projectName
+ $startupProject = GetStartupProject $startupProjectName $project
+
+ $params = 'dbcontext', 'list', '--json'
+ $params += GetParams -Environment $environment
+
+ # NB: -join is here to support ConvertFrom-Json on PowerShell 3.0
+ $result = (EF $project $startupProject $params -skipBuild) -join "`n" | ConvertFrom-Json
+
+ return $result | %{ $_.safeName }
+}
+
+function GetMigrations($context, $environment, $projectName, $startupProjectName)
+{
+ $project = GetProject $projectName
+ $startupProject = GetStartupProject $startupProjectName $project
+
+ $params = 'migrations', 'list', '--json'
+ $params += GetParams $context $environment
+
+ # NB: -join is here to support ConvertFrom-Json on PowerShell 3.0
+ $result = (EF $project $startupProject $params -skipBuild) -join "`n" | ConvertFrom-Json
+
+ return $result | %{ $_.safeName }
+}
+
+function WarnIfEF6 ($cmdlet)
+{
+ if (Get-Module 'EntityFramework')
+ {
+ Write-Warning "Both Entity Framework Core and Entity Framework 6 are installed. The Entity Framework Core tools are running. Use 'EntityFramework\$cmdlet' for Entity Framework 6."
+ }
+}
+
+function GetProject($projectName)
+{
+ if (!$projectName)
+ {
+ return Get-Project
+ }
+
+ return Get-Project $projectName
+}
+
+function GetStartupProject($name, $fallbackProject)
+{
+ if ($name)
+ {
+ return Get-Project $name
+ }
+
+ $startupProjectPaths = $DTE.Solution.SolutionBuild.StartupProjects
+ if ($startupProjectPaths)
+ {
+ if ($startupProjectPaths.Length -eq 1)
+ {
+ $startupProjectPath = $startupProjectPaths[0]
+ if (!(Split-Path -IsAbsolute $startupProjectPath))
+ {
+ $solutionPath = Split-Path (GetProperty $DTE.Solution.Properties 'Path')
+ $startupProjectPath = Join-Path $solutionPath $startupProjectPath -Resolve
+ }
+
+ $startupProject = GetSolutionProjects | ?{
+ try
+ {
+ $fullName = $_.FullName
+ }
+ catch [NotImplementedException]
+ {
+ return $false
+ }
+
+ if ($fullName -and $fullName.EndsWith('\'))
+ {
+ $fullName = $fullName.Substring(0, $fullName.Length - 1)
+ }
+
+ return $fullName -eq $startupProjectPath
+ }
+ if ($startupProject)
+ {
+ return $startupProject
+ }
+
+ Write-Warning "Unable to resolve startup project '$startupProjectPath'."
+ }
+ else
+ {
+ Write-Verbose 'More than one startup project found.'
+ }
+ }
+ else
+ {
+ Write-Verbose 'No startup project found.'
+ }
+
+ return $fallbackProject
+}
+
+function GetSolutionProjects()
+{
+ $projects = New-Object 'System.Collections.Stack'
+
+ $DTE.Solution.Projects | %{
+ $projects.Push($_)
+ }
+
+ while ($projects.Count)
+ {
+ $project = $projects.Pop();
+
+ <# yield return #> $project
+
+ if ($project.ProjectItems)
+ {
+ $project.ProjectItems | ?{ $_.SubProject } | %{
+ $projects.Push($_.SubProject)
+ }
+ }
+ }
+}
+
+function GetParams($context, $environment)
+{
+ $params = @()
+
+ if ($context)
+ {
+ $params += '--context', $context
+ }
+
+ if ($environment)
+ {
+ $params += '--environment', $environment
+ }
+
+ return $params
+}
+
+function ShowConsole
+{
+ $componentModel = Get-VSComponentModel
+ $powerConsoleWindow = $componentModel.GetService([NuGetConsole.IPowerConsoleWindow])
+ $powerConsoleWindow.Show()
+}
+
+function EF($project, $startupProject, $params, [switch] $skipBuild)
+{
+ if (IsUWP $project)
+ {
+ $outputType = GetProperty $project.Properties 'OutputType'
+ $outputTypeEx = GetProperty $project.Properties 'OutputTypeEx'
+ if ($outputType -eq 2 -and $outputTypeEx -eq 3)
+ {
+ throw "Project '$($project.ProjectName)' is a Windows Runtime component. The Entity Framework Core " +
+ 'Package Manager Console Tools don''t support this type of project.'
+ }
+ }
+
+ if (IsXproj $startupProject)
+ {
+ throw "Startup project '$($startupProject.ProjectName)' is an ASP.NET Core or .NET Core project for Visual " +
+ 'Studio 2015. This version of the Entity Framework Core Package Manager Console Tools doesn''t support ' +
+ 'these types of projects.'
+ }
+ if (IsUWP $startupProject)
+ {
+ $useDotNetNative = GetProperty $startupProject.ConfigurationManager.ActiveConfiguration.Properties 'ProjectN.UseDotNetNativeToolchain'
+ if ($useDotNetNative -eq 'True')
+ {
+ throw "Startup project '$($startupProject.ProjectName)' compiles with the .NET Native tool chan. Uncheck " +
+ 'this option in the project settings or use a different configuration and try again.'
+ }
+
+ $outputType = GetProperty $startupProject.Properties 'OutputType'
+ if ($outputType -eq 2)
+ {
+ $outputTypeEx = GetProperty $startupProject.Properties 'OutputTypeEx'
+ if ($outputTypeEx -eq 2)
+ {
+ throw "Startup project '$($startupProject.ProjectName)' is a class library. Select a Universal " +
+ 'Windows Platform app as your startup project and try again.'
+ }
+ if ($outputTypeEx -eq 3)
+ {
+ throw "Startup project '$($startupProject.ProjectName)' is a Windows Runtime component. The Entity " +
+ 'Framework Core Package Manager Console Tools don''t support this type of project.'
+ }
+ }
+ }
+
+ Write-Verbose "Using project '$($project.ProjectName)'."
+ Write-Verbose "Using startup project '$($startupProject.ProjectName)'."
+
+ if (!$skipBuild)
+ {
+ Write-Verbose 'Build started...'
+
+ # TODO: Only build startup project. Don't use BuildProject, you can't specify platform
+ $solutionBuild = $DTE.Solution.SolutionBuild
+ $solutionBuild.Build(<# WaitForBuildToFinish: #> $true)
+ if ($solutionBuild.LastBuildInfo)
+ {
+ throw 'Build failed.'
+ }
+
+ Write-Verbose 'Build succeeded.'
+ }
+
+ $startupProjectDir = GetProperty $startupProject.Properties 'FullPath'
+ $outputPath = GetProperty $startupProject.ConfigurationManager.ActiveConfiguration.Properties 'OutputPath'
+ $targetDir = Join-Path $startupProjectDir $outputPath -Resolve
+ $startupTargetFileName = GetOutputFileName $startupProject
+ $startupTargetPath = Join-Path $targetDir $startupTargetFileName
+ $targetFrameworkMoniker = GetProperty $startupProject.Properties 'TargetFrameworkMoniker'
+ $frameworkName = New-Object 'System.Runtime.Versioning.FrameworkName' $targetFrameworkMoniker
+ $targetFramework = $frameworkName.Identifier
+
+ if ($targetFramework -in '.NETFramework', '.NETCore')
+ {
+ $platformTarget = GetPlatformTarget $startupProject
+ if ($platformTarget -eq 'x86')
+ {
+ $exePath = Join-Path $PSScriptRoot 'net451\ef.x86.exe'
+ }
+ elseif ($platformTarget -in 'AnyCPU', 'x64')
+ {
+ $exePath = Join-Path $PSScriptRoot 'net451\ef.exe'
+ }
+ else
+ {
+ throw "Startup project '$($startupProject.ProjectName)' has an active platform of '$platformTarget'. Select " +
+ 'a different platform and try again.'
+ }
+ }
+ elseif ($targetFramework -in '.NETCoreApp', '.NETStandard')
+ {
+ if ($targetFramework -eq '.NETStandard')
+ {
+ Write-Warning ("Startup project '$($startupProject.ProjectName)' targets framework '.NETStandard'. This " +
+ "framework is not intended for execution and may fail to resolve runtime dependencies. If so, select " +
+ "a different startup project and try again.")
+ }
+
+ $exePath = (Get-Command 'dotnet').Path
+
+ $startupTargetName = GetProperty $startupProject.Properties 'AssemblyName'
+ $depsFile = Join-Path $targetDir ($startupTargetName + '.deps.json')
+ $projectAssetsFile = GetCsproj2Property $startupProject 'ProjectAssetsFile'
+ $runtimeConfig = Join-Path $targetDir ($startupTargetName + '.runtimeconfig.json')
+ $efPath = Join-Path $PSScriptRoot 'netcoreapp1.0\ef.dll'
+
+ $dotnetParams = 'exec', '--depsfile', $depsFile
+
+ if ($projectAssetsFile)
+ {
+ # NB: -Raw is here to support ConvertFrom-Json on PowerShell 3.0
+ $projectAssets = Get-Content $projectAssetsFile -Raw | ConvertFrom-Json
+ $projectAssets.packageFolders.psobject.Properties.Name | %{
+ $dotnetParams += '--additionalprobingpath', $_.TrimEnd('\')
+ }
+ }
+
+ if (Test-Path $runtimeConfig)
+ {
+ $dotnetParams += '--runtimeconfig', $runtimeConfig
+ }
+
+ $dotnetParams += $efPath
+
+ $params = $dotnetParams + $params
+ }
+ else
+ {
+ throw "Startup project '$($startupProject.ProjectName)' targets framework '$targetFramework'. " +
+ 'The Entity Framework Core Package Manager Console Tools don''t support this framework.'
+ }
+
+ $projectDir = GetProperty $project.Properties 'FullPath'
+ $targetFileName = GetOutputFileName $project
+ $targetPath = Join-Path $targetDir $targetFileName
+ $rootNamespace = GetProperty $project.Properties 'RootNamespace'
+
+ if (IsWeb $startupProject)
+ {
+ $dataDir = Join-Path $startupProjectDir 'App_Data'
+ }
+ else
+ {
+ $dataDir = $targetDir
+ }
+
+ $params += '--verbose',
+ '--no-color',
+ '--prefix-output',
+ '--assembly', $targetPath,
+ '--startup-assembly', $startupTargetPath,
+ '--project-dir', $projectDir,
+ '--content-root', $startupProjectDir,
+ '--data-dir', $dataDir
+
+ if (IsUWP $startupProject)
+ {
+ $params += '--no-appdomain'
+ }
+
+ if ($rootNamespace)
+ {
+ $params += '--root-namespace', $rootNamespace
+ }
+
+ $arguments = ToArguments $params
+ $startInfo = New-Object 'System.Diagnostics.ProcessStartInfo' -Property @{
+ FileName = $exePath;
+ Arguments = $arguments;
+ UseShellExecute = $false;
+ CreateNoWindow = $true;
+ RedirectStandardOutput = $true;
+ StandardOutputEncoding = [Text.Encoding]::UTF8;
+ }
+
+ Write-Verbose "$exePath $arguments"
+
+ $process = [Diagnostics.Process]::Start($startInfo)
+
+ while ($line = $process.StandardOutput.ReadLine())
+ {
+ $level = $null
+ $text = $null
+
+ $parts = $line.Split(':', 2)
+ if ($parts.Length -eq 2)
+ {
+ $level = $parts[0]
+ $text = $parts[1].Substring(8 - $level.Length)
+ }
+
+ switch ($level)
+ {
+ 'error' { throw $text }
+ 'warn' { Write-Warning $text }
+ 'info' { Write-Host $text }
+ 'data' { Write-Output $text }
+ 'verbose' { Write-Verbose $text }
+ default { Write-Host $line }
+ }
+ }
+
+ $process.WaitForExit()
+}
+
+function IsXproj($project)
+{
+ return $project.Kind -eq '{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}'
+}
+
+function IsCsproj2($project)
+{
+ return $project.Kind -eq '{9A19103F-16F7-4668-BE54-9A1E7A4F7556}'
+}
+
+function IsWeb($project)
+{
+ $types = GetProjectTypes $project
+
+ return $types -contains '{349C5851-65DF-11DA-9384-00065B846F21}'
+}
+
+function IsUWP($project)
+{
+ $types = GetProjectTypes $project
+
+ return $types -contains '{A5A43C5B-DE2A-4C0C-9213-0A381AF9435A}'
+}
+
+function GetIntermediatePath($project)
+{
+ # TODO: Remove when dotnet/roslyn-project-system#665 is fixed
+ if (IsCsproj2 $project)
+ {
+ return GetCsproj2Property $project 'IntermediateOutputPath'
+ }
+
+ return GetProperty $project.ConfigurationManager.ActiveConfiguration.Properties 'IntermediatePath'
+}
+
+function GetPlatformTarget($project)
+{
+ # TODO: Remove when dotnet/roslyn-project-system#669 is fixed
+ if (IsCsproj2 $project)
+ {
+ $platformTarget = GetCsproj2Property $project 'PlatformTarget'
+ if ($platformTarget)
+ {
+ return $platformTarget
+ }
+
+ return GetCsproj2Property $project 'Platform'
+ }
+
+ return GetProperty $project.ConfigurationManager.ActiveConfiguration.Properties 'PlatformTarget'
+}
+
+function GetOutputFileName($project)
+{
+ # TODO: Remove when dotnet/roslyn-project-system#667 is fixed
+ if (IsCsproj2 $project)
+ {
+ return GetCsproj2Property $project 'TargetFileName'
+ }
+
+ return GetProperty $project.Properties 'OutputFileName'
+}
+
+function GetProjectTypes($project)
+{
+ $solution = Get-VSService 'Microsoft.VisualStudio.Shell.Interop.SVsSolution' 'Microsoft.VisualStudio.Shell.Interop.IVsSolution'
+ $hierarchy = $null
+ $hr = $solution.GetProjectOfUniqueName($project.UniqueName, [ref] $hierarchy)
+ [Runtime.InteropServices.Marshal]::ThrowExceptionForHR($hr)
+
+ $aggregatableProject = Get-Interface $hierarchy 'Microsoft.VisualStudio.Shell.Interop.IVsAggregatableProject'
+ if (!$aggregatableProject)
+ {
+ return $project.Kind
+ }
+
+ $projectTypeGuidsString = $null
+ $hr = $aggregatableProject.GetAggregateProjectTypeGuids([ref] $projectTypeGuidsString)
+ [Runtime.InteropServices.Marshal]::ThrowExceptionForHR($hr)
+
+ return $projectTypeGuidsString.Split(';')
+}
+
+function GetProperty($properties, $propertyName)
+{
+ try
+ {
+ return $properties.Item($propertyName).Value
+ }
+ catch
+ {
+ return $null
+ }
+}
+
+function GetCsproj2Property($project, $propertyName)
+{
+ $browseObjectContext = Get-Interface $project 'Microsoft.VisualStudio.ProjectSystem.Properties.IVsBrowseObjectContext'
+ $unconfiguredProject = $browseObjectContext.UnconfiguredProject
+ $configuredProject = $unconfiguredProject.GetSuggestedConfiguredProjectAsync().Result
+ $properties = $configuredProject.Services.ProjectPropertiesProvider.GetCommonProperties()
+
+ return $properties.GetEvaluatedPropertyValueAsync($propertyName).Result
+}
+
+function GetProjectItem($project, $path)
+{
+ $fullPath = GetProperty $project.Properties 'FullPath'
+
+ if (Split-Path $path -IsAbsolute)
+ {
+ $path = $path.Substring($fullPath.Length)
+ }
+
+ $itemDirectory = (Split-Path $path -Parent)
+
+ $projectItems = $project.ProjectItems
+ if ($itemDirectory)
+ {
+ $directories = $itemDirectory.Split('\')
+ $directories | %{
+ if ($projectItems)
+ {
+ $projectItems = $projectItems.Item($_).ProjectItems
+ }
+ }
+ }
+
+ if (!$projectItems)
+ {
+ return $null
+ }
+
+ $itemName = Split-Path $path -Leaf
+
+ try
+ {
+ return $projectItems.Item($itemName)
+ }
+ catch [Exception]
+ {
+ }
+
+ return $null
+}
+
+function ToArguments($params)
+{
+ $arguments = ''
+ for ($i = 0; $i -lt $params.Length; $i++)
+ {
+ if ($i)
+ {
+ $arguments += " "
+ }
+
+ if (!$params[$i].Contains(' '))
+ {
+ $arguments += $params[$i]
+
+ continue
+ }
+
+ $arguments += '"'
+
+ $pendingBackslashs = 0
+ for ($j = 0; $j -lt $params[$i].Length; $j++)
+ {
+ switch ($params[$i][$j])
+ {
+ '"'
+ {
+ if ($pendingBackslashs)
+ {
+ $arguments += '\' * $pendingBackslashs * 2
+ $pendingBackslashs = 0
+ }
+ $arguments += '\"'
+ }
+
+ '\'
+ {
+ $pendingBackslashs++
+ }
+
+ default
+ {
+ if ($pendingBackslashs)
+ {
+ if ($pendingBackslashs -eq 1)
+ {
+ $arguments += '\'
+ }
+ else
+ {
+ $arguments += '\' * $pendingBackslashs * 2
+ }
+
+ $pendingBackslashs = 0
+ }
+
+ $arguments += $params[$i][$j]
+ }
+ }
+ }
+
+ if ($pendingBackslashs)
+ {
+ $arguments += '\' * $pendingBackslashs * 2
+ }
+
+ $arguments += '"'
+ }
+
+ return $arguments
+}
diff --git a/src/EFCore.Tools/tools/about_EntityFrameworkCore.help.txt b/src/EFCore.Tools/tools/about_EntityFrameworkCore.help.txt
new file mode 100644
index 0000000000..c167ed5abf
--- /dev/null
+++ b/src/EFCore.Tools/tools/about_EntityFrameworkCore.help.txt
@@ -0,0 +1,41 @@
+
+ _/\__
+ ---==/ \\
+ ___ ___ |. \|\
+ | __|| __| | ) \\\
+ | _| | _| \_/ | //|\\
+ |___||_| / \\\/\\
+
+TOPIC
+ about_EntityFrameworkCore
+
+SHORT DESCRIPTION
+ Provides information about the Entity Framework Core Package Manager Console Tools.
+
+LONG DESCRIPTION
+ This topic describes the Entity Framework Core Package Manager Console Tools. See https://docs.efproject.net for
+ information on Entity Framework Core.
+
+ The following Entity Framework Core commands are available.
+
+ Cmdlet Description
+ -------------------------- ---------------------------------------------------
+ Add-Migration Adds a new migration.
+
+ Drop-Database Drops the database.
+
+ Remove-Migration Removes the last migration.
+
+ Scaffold-DbContext Scaffolds a DbContext and entity types for a database.
+
+ Script-Migration Generates a SQL script from migrations.
+
+ Update-Database Updates the database to a specified migration.
+
+SEE ALSO
+ Add-Migration
+ Drop-Database
+ Remove-Migration
+ Scaffold-DbContext
+ Script-Migration
+ Update-Database
diff --git a/src/EFCore.Tools/tools/init.ps1 b/src/EFCore.Tools/tools/init.ps1
new file mode 100644
index 0000000000..34b5cb4dc8
--- /dev/null
+++ b/src/EFCore.Tools/tools/init.ps1
@@ -0,0 +1,24 @@
+param($installPath, $toolsPath, $package, $project)
+
+if ($PSVersionTable.PSVersion.Major -lt 3)
+{
+ # This section needs to support PS2 syntax
+ # Use $toolsPath because PS2 does not support $PSScriptRoot
+ $env:PSModulePath = $env:PSModulePath + ';$toolsPath'
+
+ # Import a "dummy" module that contains matching functions that throw on PS2
+ Import-Module (Join-Path $toolsPath 'EntityFrameworkCore.PowerShell2.psd1') -DisableNameChecking
+
+ throw "PowerShell version $($PSVersionTable.PSVersion) is not supported. Please upgrade PowerShell to 3.0 or " +
+ 'greater and restart Visual Studio.'
+}
+else
+{
+ if (Get-Module 'EntityFrameworkCore')
+ {
+ Remove-Module 'EntityFrameworkCore'
+ }
+
+ Import-Module (Join-Path $PSScriptRoot 'EntityFrameworkCore.psd1') -DisableNameChecking
+}
+
diff --git a/src/EFCore.Tools/tools/install.ps1 b/src/EFCore.Tools/tools/install.ps1
new file mode 100644
index 0000000000..d260b151f7
--- /dev/null
+++ b/src/EFCore.Tools/tools/install.ps1
@@ -0,0 +1,5 @@
+param($installPath, $toolsPath, $package, $project)
+
+Write-Host
+Write-Host 'Type ''get-help EntityFrameworkCore'' to see all available Entity Framework Core commands.'
+Write-Host
diff --git a/src/EFCore.Tools/tools/net451/ef.exe.config b/src/EFCore.Tools/tools/net451/ef.exe.config
new file mode 100644
index 0000000000..bbc7a1a4e6
--- /dev/null
+++ b/src/EFCore.Tools/tools/net451/ef.exe.config
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/EFCore.Tools/tools/net451/ef.x86.exe.config b/src/EFCore.Tools/tools/net451/ef.x86.exe.config
new file mode 100644
index 0000000000..bbc7a1a4e6
--- /dev/null
+++ b/src/EFCore.Tools/tools/net451/ef.x86.exe.config
@@ -0,0 +1,219 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/dotnet-ef/Commands/ProjectCommandBase.cs b/src/dotnet-ef/Commands/ProjectCommandBase.cs
new file mode 100644
index 0000000000..ab679d23ba
--- /dev/null
+++ b/src/dotnet-ef/Commands/ProjectCommandBase.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal class ProjectCommandBase : EnvironmentCommandBase
+ {
+ public override void Configure(CommandLineApplication command)
+ {
+ new ProjectOptions().Configure(command);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/dotnet-ef/Exe.cs b/src/dotnet-ef/Exe.cs
new file mode 100644
index 0000000000..a94d58bfa3
--- /dev/null
+++ b/src/dotnet-ef/Exe.cs
@@ -0,0 +1,98 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Text;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal static class Exe
+ {
+ public static int Run(string executable, IReadOnlyList args)
+ {
+ var arguments = ToArguments(args);
+
+ Reporter.WriteVerbose(executable + " " + arguments);
+
+ var build = Process.Start(
+ new ProcessStartInfo
+ {
+ FileName = executable,
+ Arguments = arguments,
+ UseShellExecute = false
+ });
+ build.WaitForExit();
+
+ return build.ExitCode;
+ }
+
+ private static string ToArguments(IReadOnlyList 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();
+ }
+ }
+}
diff --git a/src/dotnet-ef/Program.cs b/src/dotnet-ef/Program.cs
new file mode 100644
index 0000000000..d293e23132
--- /dev/null
+++ b/src/dotnet-ef/Program.cs
@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal static class Program
+ {
+ private static int Main(string[] args)
+ {
+ var app = new CommandLineApplication(throwOnUnexpectedArg: false)
+ {
+ Name = "dotnet ef"
+ };
+
+ new RootCommand().Configure(app);
+
+ try
+ {
+ return app.Execute(args);
+ }
+ catch (Exception ex)
+ {
+ if (ex is CommandException || ex is CommandParsingException)
+ {
+ Reporter.WriteVerbose(ex.ToString());
+ }
+ else
+ {
+ Reporter.WriteInformation(ex.ToString());
+ }
+
+ Reporter.WriteError(ex.Message);
+
+ return 1;
+ }
+ }
+ }
+}
diff --git a/src/dotnet-ef/Project.cs b/src/dotnet-ef/Project.cs
new file mode 100644
index 0000000000..d57f15ef6e
--- /dev/null
+++ b/src/dotnet-ef/Project.cs
@@ -0,0 +1,164 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal class Project
+ {
+ private readonly string _file;
+ private readonly string _framework;
+ private readonly string _configuration;
+
+ public Project(string file, string framework, string configuration)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(file), "file is null or empty.");
+
+ _file = file;
+ _framework = framework;
+ _configuration = configuration;
+ ProjectName = Path.GetFileName(file);
+ }
+
+ public string ProjectName { get; }
+
+ public string AssemblyName { 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 TargetFileName { get; set; }
+ public string TargetFrameworkMoniker { get; set; }
+
+ public static Project FromFile(
+ string file,
+ string buildExtensionsDir,
+ string framework = null,
+ string configuration = null)
+ {
+ Debug.Assert(!string.IsNullOrEmpty(file), "file is null or empty.");
+
+ if (buildExtensionsDir == null)
+ {
+ buildExtensionsDir = Path.Combine(Path.GetDirectoryName(file), "obj");
+ }
+
+ var efTargetsPath = Path.Combine(
+ buildExtensionsDir,
+ Path.GetFileName(file) + ".EntityFrameworkCore.targets");
+ if (!File.Exists(efTargetsPath))
+ {
+ Reporter.WriteVerbose(Resources.WritingFile(efTargetsPath));
+
+ using (var input = typeof(Resources).GetTypeInfo().Assembly.GetManifestResourceStream(
+ "Microsoft.EntityFrameworkCore.Tools.Resources.EntityFrameworkCore.targets"))
+ using (var output = File.OpenWrite(efTargetsPath))
+ {
+ input.CopyTo(output);
+ }
+ }
+
+ IDictionary metadata;
+ var metadataFile = Path.GetTempFileName();
+ try
+ {
+ var propertyArg = "/property:EFProjectMetadataFile=" + metadataFile;
+ if (configuration != null)
+ {
+ propertyArg += ";TargetFramework=" + framework;
+ }
+ if (configuration != null)
+ {
+ propertyArg += ";Configuration=" + configuration;
+ }
+
+ var args = new List
+ {
+ "msbuild",
+ "/target:GetEFProjectMetadata",
+ propertyArg,
+ "/verbosity:quiet",
+ "/nologo"
+ };
+
+ if (file != null)
+ {
+ args.Add(file);
+ }
+
+ var exitCode = Exe.Run("dotnet", args);
+ if (exitCode != 0)
+ {
+ throw new CommandException(Resources.GetMetadataFailed);
+ }
+
+ metadata = File.ReadLines(metadataFile).Select(l => l.Split(new[] { ':' }, 2))
+ .ToDictionary(s => s[0], s => s[1].TrimStart());
+ }
+ finally
+ {
+ File.Delete(metadataFile);
+ }
+
+ var platformTarget = metadata["PlatformTarget"];
+ if (platformTarget.Length == 0)
+ {
+ platformTarget = metadata["Platform"];
+ }
+
+ return new Project(file, framework, configuration)
+ {
+ AssemblyName = metadata["AssemblyName"],
+ OutputPath = metadata["OutputPath"],
+ PlatformTarget = platformTarget,
+ ProjectAssetsFile = metadata["ProjectAssetsFile"],
+ ProjectDir = metadata["ProjectDir"],
+ RootNamespace = metadata["RootNamespace"],
+ TargetFileName = metadata["TargetFileName"],
+ TargetFrameworkMoniker = metadata["TargetFrameworkMoniker"]
+ };
+ }
+
+ public void Build()
+ {
+ var args = new List();
+
+ args.Add("build");
+
+ if (_file != null)
+ {
+ args.Add(_file);
+ }
+
+ // TODO: Only build for the first framework when unspecified
+ if (_framework != null)
+ {
+ args.Add("--framework");
+ args.Add(_framework);
+ }
+
+ if (_configuration != null)
+ {
+ args.Add("--configuration");
+ args.Add(_configuration);
+ }
+
+ args.Add("/verbosity:quiet");
+ args.Add("/nologo");
+
+
+ var exitCode = Exe.Run("dotnet", args);
+ if (exitCode != 0)
+ {
+ throw new CommandException(Resources.BuildFailed);
+ }
+ }
+ }
+}
diff --git a/src/dotnet-ef/ProjectOptions.cs b/src/dotnet-ef/ProjectOptions.cs
new file mode 100644
index 0000000000..2ab970047c
--- /dev/null
+++ b/src/dotnet-ef/ProjectOptions.cs
@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal class ProjectOptions
+ {
+ private CommandOption _project;
+ private CommandOption _startupProject;
+ private CommandOption _framework;
+ private CommandOption _configuration;
+ private CommandOption _msbuildprojectextensionspath;
+
+ public CommandOption Project
+ => _project;
+
+ public CommandOption StartupProject
+ => _startupProject;
+
+ public CommandOption Framework
+ => _framework;
+
+ public CommandOption Configuration
+ => _configuration;
+
+ public CommandOption MSBuildProjectExtensionsPath
+ => _msbuildprojectextensionspath;
+
+ public void Configure(CommandLineApplication command)
+ {
+ _project = command.Option("-p|--project ", Resources.ProjectDescription);
+ _startupProject = command.Option("-s|--startup-project ", Resources.StartupProjectDescription);
+ _framework = command.Option("--framework ", Resources.FrameworkDescription);
+ _configuration = command.Option("--configuration ", Resources.ConfigurationDescription);
+ _msbuildprojectextensionspath = command.Option("--msbuildprojectextensionspath ", Resources.ProjectExtensionsDescription);
+ }
+ }
+}
diff --git a/src/dotnet-ef/Properties/AssemblyInfo.cs b/src/dotnet-ef/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..b251f4820c
--- /dev/null
+++ b/src/dotnet-ef/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo(
+ "dotnet-ef.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/dotnet-ef/Properties/Resources.Designer.cs b/src/dotnet-ef/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..ff9f7d09c1
--- /dev/null
+++ b/src/dotnet-ef/Properties/Resources.Designer.cs
@@ -0,0 +1,379 @@
+//
+
+using System.Reflection;
+using System.Resources;
+using JetBrains.Annotations;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Properties
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.EntityFrameworkCore.Tools.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// Build failed.
+ ///
+ public static string BuildFailed
+ => GetString("BuildFailed");
+
+ ///
+ /// The configuration to use.
+ ///
+ public static string ConfigurationDescription
+ => GetString("ConfigurationDescription");
+
+ ///
+ /// The connection string to the database.
+ ///
+ public static string ConnectionDescription
+ => GetString("ConnectionDescription");
+
+ ///
+ /// The DbContext to use.
+ ///
+ public static string ContextDescription
+ => GetString("ContextDescription");
+
+ ///
+ /// The name of the DbContext.
+ ///
+ public static string ContextNameDescription
+ => GetString("ContextNameDescription");
+
+ ///
+ /// Use attributes to configure the model (where possible). If omitted, only the fluent API is used.
+ ///
+ public static string DataAnnotationsDescription
+ => GetString("DataAnnotationsDescription");
+
+ ///
+ /// Commands to manage the database.
+ ///
+ public static string DatabaseDescription
+ => GetString("DatabaseDescription");
+
+ ///
+ /// Drops the database.
+ ///
+ public static string DatabaseDropDescription
+ => GetString("DatabaseDropDescription");
+
+ ///
+ /// Show which database would be dropped, but don't drop it.
+ ///
+ public static string DatabaseDropDryRunDescription
+ => GetString("DatabaseDropDryRunDescription");
+
+ ///
+ /// Don't confirm.
+ ///
+ public static string DatabaseDropForceDescription
+ => GetString("DatabaseDropForceDescription");
+
+ ///
+ /// Updates the database to a specified migration.
+ ///
+ public static string DatabaseUpdateDescription
+ => GetString("DatabaseUpdateDescription");
+
+ ///
+ /// Commands to manage DbContext types.
+ ///
+ public static string DbContextDescription
+ => GetString("DbContextDescription");
+
+ ///
+ /// Gets information about a DbContext type.
+ ///
+ public static string DbContextInfoDescription
+ => GetString("DbContextInfoDescription");
+
+ ///
+ /// Lists available DbContext types.
+ ///
+ public static string DbContextListDescription
+ => GetString("DbContextListDescription");
+
+ ///
+ /// Scaffolds a DbContext and entity types for a database.
+ ///
+ public static string DbContextScaffoldDescription
+ => GetString("DbContextScaffoldDescription");
+
+ ///
+ /// Overwrite existing files.
+ ///
+ public static string DbContextScaffoldForceDescription
+ => GetString("DbContextScaffoldForceDescription");
+
+ ///
+ /// Entity Framework Core .NET Command Line Tools
+ ///
+ public static string DotnetEfFullName
+ => GetString("DotnetEfFullName");
+
+ ///
+ /// Entity Framework Core Command Line Tools
+ ///
+ public static string EFFullName
+ => GetString("EFFullName");
+
+ ///
+ /// The environment to use. Defaults to "Development".
+ ///
+ public static string EnvironmentDescription
+ => GetString("EnvironmentDescription");
+
+ ///
+ /// The target framework.
+ ///
+ public static string FrameworkDescription
+ => GetString("FrameworkDescription");
+
+ ///
+ /// Unable to retrieve project metadata. Ensure it's an MSBuild-based .NET Core project. If you're using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, Use the --msbuildprojectextensionspath option.
+ ///
+ public static string GetMetadataFailed
+ => GetString("GetMetadataFailed");
+
+ ///
+ /// Generate a script that can be used on a database at any migration.
+ ///
+ public static string IdempotentDescription
+ => GetString("IdempotentDescription");
+
+ ///
+ /// Show JSON output.
+ ///
+ public static string JsonDescription
+ => GetString("JsonDescription");
+
+ ///
+ /// The target migration. If '0', all migrations will be reverted. Defaults to the last migration.
+ ///
+ public static string MigrationDescription
+ => GetString("MigrationDescription");
+
+ ///
+ /// The starting migration. Defaults to '0' (the initial database).
+ ///
+ public static string MigrationFromDescription
+ => GetString("MigrationFromDescription");
+
+ ///
+ /// The name of the migration.
+ ///
+ public static string MigrationNameDescription
+ => GetString("MigrationNameDescription");
+
+ ///
+ /// Adds a new migration.
+ ///
+ public static string MigrationsAddDescription
+ => GetString("MigrationsAddDescription");
+
+ ///
+ /// Commands to manage migrations.
+ ///
+ public static string MigrationsDescription
+ => GetString("MigrationsDescription");
+
+ ///
+ /// Lists available migrations.
+ ///
+ public static string MigrationsListDescription
+ => GetString("MigrationsListDescription");
+
+ ///
+ /// The directory (and sub-namespace) to use. Paths are relative to the project directory. Defaults to "Migrations".
+ ///
+ public static string MigrationsOutputDirDescription
+ => GetString("MigrationsOutputDirDescription");
+
+ ///
+ /// Removes the last migration.
+ ///
+ public static string MigrationsRemoveDescription
+ => GetString("MigrationsRemoveDescription");
+
+ ///
+ /// Don't check to see if the migration has been applied to the database.
+ ///
+ public static string MigrationsRemoveForceDescription
+ => GetString("MigrationsRemoveForceDescription");
+
+ ///
+ /// Generates a SQL script from migrations.
+ ///
+ public static string MigrationsScriptDescription
+ => GetString("MigrationsScriptDescription");
+
+ ///
+ /// The ending migration. Defaults to the last migration.
+ ///
+ public static string MigrationToDescription
+ => GetString("MigrationToDescription");
+
+ ///
+ /// More than one project was found in the current working directory. Use the --project option.
+ ///
+ public static string MultipleProjects
+ => GetString("MultipleProjects");
+
+ ///
+ /// More than one project was found in directory '{projectDir}'. Specify one using its file name.
+ ///
+ public static string MultipleProjectsInDirectory([CanBeNull] object projectDir)
+ => string.Format(
+ GetString("MultipleProjectsInDirectory", nameof(projectDir)),
+ projectDir);
+
+ ///
+ /// More than one project was found in the current working directory. Use the --startup-project option.
+ ///
+ public static string MultipleStartupProjects
+ => GetString("MultipleStartupProjects");
+
+ ///
+ /// Startup project '{startupProject}' targets framework '.NETStandard'. This framework is not intended for execution and may fail to resolve runtime dependencies. If so, specify a different project using the --startup-project option and try again.
+ ///
+ public static string NETStandardStartupProject([CanBeNull] object startupProject)
+ => string.Format(
+ GetString("NETStandardStartupProject", nameof(startupProject)),
+ startupProject);
+
+ ///
+ /// Don't colorize output.
+ ///
+ public static string NoColorDescription
+ => GetString("NoColorDescription");
+
+ ///
+ /// No project was found. Change the current working directory or use the --project option.
+ ///
+ public static string NoProject
+ => GetString("NoProject");
+
+ ///
+ /// No project was found in directory '{projectDir}'.
+ ///
+ public static string NoProjectInDirectory([CanBeNull] object projectDir)
+ => string.Format(
+ GetString("NoProjectInDirectory", nameof(projectDir)),
+ projectDir);
+
+ ///
+ /// No project was found. Change the current working directory or use the --startup-project option.
+ ///
+ public static string NoStartupProject
+ => GetString("NoStartupProject");
+
+ ///
+ /// The file to write the result to.
+ ///
+ public static string OutputDescription
+ => GetString("OutputDescription");
+
+ ///
+ /// The directory to put files in. Paths are relative to the project directory.
+ ///
+ public static string OutputDirDescription
+ => GetString("OutputDirDescription");
+
+ ///
+ /// Prefix output with level.
+ ///
+ public static string PrefixDescription
+ => GetString("PrefixDescription");
+
+ ///
+ /// The project to use.
+ ///
+ public static string ProjectDescription
+ => GetString("ProjectDescription");
+
+ ///
+ /// The MSBuild project extensions path. Defaults to "obj".
+ ///
+ public static string ProjectExtensionsDescription
+ => GetString("ProjectExtensionsDescription");
+
+ ///
+ /// The provider to use. (E.g. Microsoft.EntityFrameworkCore.SqlServer)
+ ///
+ public static string ProviderDescription
+ => GetString("ProviderDescription");
+
+ ///
+ /// The schemas of tables to generate entity types for.
+ ///
+ public static string SchemasDescription
+ => GetString("SchemasDescription");
+
+ ///
+ /// The startup project to use.
+ ///
+ public static string StartupProjectDescription
+ => GetString("StartupProjectDescription");
+
+ ///
+ /// The tables to generate entity types for.
+ ///
+ public static string TablesDescription
+ => GetString("TablesDescription");
+
+ ///
+ /// Startup project '{startupProject}' targets framework '{targetFramework}'. The Entity Framework Core .NET Command Line Tools don't support this framework.
+ ///
+ public static string UnsupportedFramework([CanBeNull] object startupProject, [CanBeNull] object targetFramework)
+ => string.Format(
+ GetString("UnsupportedFramework", nameof(startupProject), nameof(targetFramework)),
+ startupProject, targetFramework);
+
+ ///
+ /// Using project '{project}'.
+ ///
+ public static string UsingProject([CanBeNull] object project)
+ => string.Format(
+ GetString("UsingProject", nameof(project)),
+ project);
+
+ ///
+ /// Using startup project '{startupProject}'.
+ ///
+ public static string UsingStartupProject([CanBeNull] object startupProject)
+ => string.Format(
+ GetString("UsingStartupProject", nameof(startupProject)),
+ startupProject);
+
+ ///
+ /// Show verbose output.
+ ///
+ public static string VerboseDescription
+ => GetString("VerboseDescription");
+
+ ///
+ /// Writing '{file}'...
+ ///
+ public static string WritingFile([CanBeNull] object file)
+ => string.Format(
+ GetString("WritingFile", nameof(file)),
+ file);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/dotnet-ef/Properties/Resources.Designer.tt b/src/dotnet-ef/Properties/Resources.Designer.tt
new file mode 100644
index 0000000000..2f70174a83
--- /dev/null
+++ b/src/dotnet-ef/Properties/Resources.Designer.tt
@@ -0,0 +1,5 @@
+<#
+ Session["ResourceFile"] = "Resources.resx";
+ Session["AccessModifier"] = "internal";
+#>
+<#@ include file="..\..\..\tools\Resources.tt" #>
\ No newline at end of file
diff --git a/src/dotnet-ef/Properties/Resources.resx b/src/dotnet-ef/Properties/Resources.resx
new file mode 100644
index 0000000000..f52aa5f042
--- /dev/null
+++ b/src/dotnet-ef/Properties/Resources.resx
@@ -0,0 +1,288 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ Build failed.
+
+
+ The configuration to use.
+
+
+ The connection string to the database.
+
+
+ The DbContext to use.
+
+
+ The name of the DbContext.
+
+
+ Use attributes to configure the model (where possible). If omitted, only the fluent API is used.
+
+
+ Commands to manage the database.
+
+
+ Drops the database.
+
+
+ Show which database would be dropped, but don't drop it.
+
+
+ Don't confirm.
+
+
+ Updates the database to a specified migration.
+
+
+ Commands to manage DbContext types.
+
+
+ Gets information about a DbContext type.
+
+
+ Lists available DbContext types.
+
+
+ Scaffolds a DbContext and entity types for a database.
+
+
+ Overwrite existing files.
+
+
+ Entity Framework Core .NET Command Line Tools
+
+
+ Entity Framework Core Command Line Tools
+
+
+ The environment to use. Defaults to "Development".
+
+
+ The target framework.
+
+
+ Unable to retrieve project metadata. Ensure it's an MSBuild-based .NET Core project. If you're using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, Use the --msbuildprojectextensionspath option.
+
+
+ Generate a script that can be used on a database at any migration.
+
+
+ Show JSON output.
+
+
+ The target migration. If '0', all migrations will be reverted. Defaults to the last migration.
+
+
+ The starting migration. Defaults to '0' (the initial database).
+
+
+ The name of the migration.
+
+
+ Adds a new migration.
+
+
+ Commands to manage migrations.
+
+
+ Lists available migrations.
+
+
+ The directory (and sub-namespace) to use. Paths are relative to the project directory. Defaults to "Migrations".
+
+
+ Removes the last migration.
+
+
+ Don't check to see if the migration has been applied to the database.
+
+
+ Generates a SQL script from migrations.
+
+
+ The ending migration. Defaults to the last migration.
+
+
+ More than one project was found in the current working directory. Use the --project option.
+
+
+ More than one project was found in directory '{projectDir}'. Specify one using its file name.
+
+
+ More than one project was found in the current working directory. Use the --startup-project option.
+
+
+ Startup project '{startupProject}' targets framework '.NETStandard'. This framework is not intended for execution and may fail to resolve runtime dependencies. If so, specify a different project using the --startup-project option and try again.
+
+
+ Don't colorize output.
+
+
+ No project was found. Change the current working directory or use the --project option.
+
+
+ No project was found in directory '{projectDir}'.
+
+
+ No project was found. Change the current working directory or use the --startup-project option.
+
+
+ The file to write the result to.
+
+
+ The directory to put files in. Paths are relative to the project directory.
+
+
+ Prefix output with level.
+
+
+ The project to use.
+
+
+ The MSBuild project extensions path. Defaults to "obj".
+
+
+ The provider to use. (E.g. Microsoft.EntityFrameworkCore.SqlServer)
+
+
+ The schemas of tables to generate entity types for.
+
+
+ The startup project to use.
+
+
+ The tables to generate entity types for.
+
+
+ Startup project '{startupProject}' targets framework '{targetFramework}'. The Entity Framework Core .NET Command Line Tools don't support this framework.
+
+
+ Using project '{project}'.
+
+
+ Using startup project '{startupProject}'.
+
+
+ Show verbose output.
+
+
+ Writing '{file}'...
+
+
\ No newline at end of file
diff --git a/src/dotnet-ef/Resources/EntityFrameworkCore.targets b/src/dotnet-ef/Resources/EntityFrameworkCore.targets
new file mode 100644
index 0000000000..bfd937118d
--- /dev/null
+++ b/src/dotnet-ef/Resources/EntityFrameworkCore.targets
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/dotnet-ef/RootCommand.cs b/src/dotnet-ef/RootCommand.cs
new file mode 100644
index 0000000000..b2e6da722d
--- /dev/null
+++ b/src/dotnet-ef/RootCommand.cs
@@ -0,0 +1,260 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.Versioning;
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Commands;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+using EFCommand = Microsoft.EntityFrameworkCore.Tools.Commands.RootCommand;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal class RootCommand : CommandBase
+ {
+ private CommandLineApplication _command;
+ private CommandOption _project;
+ private CommandOption _startupProject;
+ private CommandOption _framework;
+ private CommandOption _configuration;
+ private CommandOption _msbuildprojectextensionspath;
+ private CommandOption _help;
+ private IList _args;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.FullName = Resources.DotnetEfFullName;
+
+ var options = new ProjectOptions();
+ options.Configure(command);
+
+ _project = options.Project;
+ _startupProject = options.StartupProject;
+ _framework = options.Framework;
+ _configuration = options.Configuration;
+ _msbuildprojectextensionspath = options.MSBuildProjectExtensionsPath;
+
+ command.VersionOption("--version", GetVersion);
+ _help = command.Option("-h|--help", description: null);
+
+ _args = command.RemainingArguments;
+
+ base.Configure(command);
+
+ _command = command;
+ }
+
+ protected override int Execute()
+ {
+ var commands = _args.TakeWhile(a => a[0] != '-').ToList();
+ if (_help.HasValue() || ShouldHelp(commands))
+ {
+ return ShowHelp(_help.HasValue(), commands);
+ }
+
+ var projectFile = FindProjects(
+ _project.Value(),
+ Resources.NoProject,
+ Resources.MultipleProjects);
+ Reporter.WriteVerbose(Resources.UsingProject(projectFile));
+
+ var starupProjectFile = FindProjects(
+ _startupProject.Value(),
+ Resources.NoStartupProject,
+ Resources.MultipleStartupProjects);
+ Reporter.WriteVerbose(Resources.UsingStartupProject(starupProjectFile));
+
+ var project = Project.FromFile(projectFile, _msbuildprojectextensionspath.Value());
+ var startupProject = Project.FromFile(
+ starupProjectFile,
+ _msbuildprojectextensionspath.Value(),
+ _framework.Value(),
+ _configuration.Value());
+
+ startupProject.Build();
+
+ string executable;
+ var args = new List();
+
+ var toolsPath = Path.GetFullPath(
+ Path.Combine(
+ Path.GetDirectoryName(typeof(Program).GetTypeInfo().Assembly.Location),
+ "..",
+ "..",
+ "tools"));
+
+ var targetDir = Path.GetFullPath(Path.Combine(startupProject.ProjectDir, startupProject.OutputPath));
+ var targetPath = Path.Combine(targetDir, project.TargetFileName);
+ var startupTargetPath = Path.Combine(targetDir, startupProject.TargetFileName);
+ var depsFile = Path.Combine(
+ targetDir,
+ startupProject.AssemblyName + ".deps.json");
+ var runtimeConfig = Path.Combine(
+ targetDir,
+ startupProject.AssemblyName + ".runtimeconfig.json");
+ var projectAssetsFile = startupProject.ProjectAssetsFile;
+
+ var targetFramework = new FrameworkName(startupProject.TargetFrameworkMoniker);
+ if (targetFramework.Identifier == ".NETFramework")
+ {
+ executable = Path.Combine(
+ toolsPath,
+ "net451",
+ startupProject.PlatformTarget == "x86"
+ ? "ef.x86.exe"
+ : "ef.exe");
+ }
+ else if (targetFramework.Identifier == ".NETCoreApp"
+ || targetFramework.Identifier == ".NETStandard")
+ {
+ if (targetFramework.Identifier == ".NETStandard")
+ {
+ Reporter.WriteWarning(Resources.NETStandardStartupProject(startupProject.ProjectName));
+ }
+
+ executable = "dotnet";
+ args.Add("exec");
+ args.Add("--depsfile");
+ args.Add(depsFile);
+
+ if (!string.IsNullOrEmpty(projectAssetsFile))
+ {
+ using (var reader = new JsonTextReader(File.OpenText(projectAssetsFile)))
+ {
+ var projectAssets = JObject.ReadFrom(reader);
+ var packageFolders = projectAssets["packageFolders"].Children().Select(p => p.Name);
+
+ foreach (var packageFolder in packageFolders)
+ {
+ args.Add("--additionalprobingpath");
+ args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar));
+ }
+ }
+ }
+
+ if (File.Exists(runtimeConfig))
+ {
+ args.Add("--runtimeconfig");
+ args.Add(runtimeConfig);
+ }
+
+ args.Add(Path.Combine(toolsPath, "netcoreapp1.0", "ef.dll"));
+ }
+ else
+ {
+ throw new CommandException(
+ Resources.UnsupportedFramework(startupProject.ProjectName, targetFramework.Identifier));
+ }
+
+ args.AddRange(_args);
+ args.Add("--assembly");
+ args.Add(targetPath);
+ args.Add("--startup-assembly");
+ args.Add(startupTargetPath);
+ args.Add("--project-dir");
+ args.Add(project.ProjectDir);
+ args.Add("--content-root");
+ args.Add(startupProject.ProjectDir);
+ args.Add("--data-dir");
+ args.Add(targetDir);
+
+ if (Reporter.IsVerbose)
+ {
+ args.Add("--verbose");
+ }
+
+ if (Reporter.NoColor)
+ {
+ args.Add("--no-color");
+ }
+
+ if (Reporter.PrefixOutput)
+ {
+ args.Add("--prefix-output");
+ }
+
+ if (project.RootNamespace.Length != 0)
+ {
+ args.Add("--root-namespace");
+ args.Add(project.RootNamespace);
+ }
+
+ return Exe.Run(executable, args);
+ }
+
+ private static string FindProjects(
+ string path,
+ string errorWhenNoProject,
+ string errorWhenMultipleProjects)
+ {
+ var specified = true;
+ if (path == null)
+ {
+ specified = false;
+ path = Directory.GetCurrentDirectory();
+ }
+ else if (!Directory.Exists(path)) // It's not a directory
+ {
+ return path;
+ }
+
+ var projectFiles = Directory.EnumerateFiles(path, "*.*proj", SearchOption.TopDirectoryOnly)
+ .Where(f => !string.Equals(Path.GetExtension(f), ".xproj", StringComparison.OrdinalIgnoreCase))
+ .Take(2).ToList();
+ if (projectFiles.Count == 0)
+ {
+ throw new CommandException(
+ specified
+ ? Resources.NoProjectInDirectory(path)
+ : errorWhenNoProject);
+ }
+ if (projectFiles.Count != 1)
+ {
+ throw new CommandException(
+ specified
+ ? Resources.MultipleProjectsInDirectory(path)
+ : errorWhenMultipleProjects);
+ }
+
+ return projectFiles[0];
+ }
+
+ private static string GetVersion()
+ => typeof(RootCommand).GetTypeInfo().Assembly.GetCustomAttribute()
+ .InformationalVersion;
+
+ private static bool ShouldHelp(IReadOnlyList commands)
+ => commands.Count == 0
+ || (commands.Count == 1
+ && (commands[0] == "database"
+ || commands[0] == "dbcontext"
+ || commands[0] == "migrations"));
+
+ private int ShowHelp(bool help, IEnumerable commands)
+ {
+ var app = new CommandLineApplication
+ {
+ Name = _command.Name
+ };
+
+ new EFCommand().Configure(app);
+
+ app.FullName = _command.FullName;
+
+ var args = new List(commands);
+ if (help)
+ {
+ args.Add("--help");
+ }
+
+ return app.Execute(args.ToArray());
+ }
+ }
+}
diff --git a/src/dotnet-ef/dotnet-ef.csproj b/src/dotnet-ef/dotnet-ef.csproj
new file mode 100644
index 0000000000..bbe210e0ab
--- /dev/null
+++ b/src/dotnet-ef/dotnet-ef.csproj
@@ -0,0 +1,62 @@
+
+
+
+
+
+ Entity Framework Core .NET Command Line Tools
+ netcoreapp1.0
+ Exe
+ false
+ Microsoft.EntityFrameworkCore.Tools
+ 1.0.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ TextTemplatingFileGenerator
+ Resources.Designer.cs
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.Designer.tt
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ef/AnsiConsole.cs b/src/ef/AnsiConsole.cs
new file mode 100644
index 0000000000..24b1ae9b04
--- /dev/null
+++ b/src/ef/AnsiConsole.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal class AnsiConsole
+ {
+ public static readonly AnsiTextWriter _out = new AnsiTextWriter(Console.Out);
+
+ public static void WriteLine(string text)
+ => _out.WriteLine(text);
+ }
+}
diff --git a/src/ef/AnsiConstants.cs b/src/ef/AnsiConstants.cs
new file mode 100644
index 0000000000..ffa0b88b12
--- /dev/null
+++ b/src/ef/AnsiConstants.cs
@@ -0,0 +1,20 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal static class AnsiConstants
+ {
+ public const string Reset = "\x1b[22m\x1b[39m";
+ public const string Bold = "\x1b[1m";
+ public const string Dark = "\x1b[22m";
+ public const string Black = "\x1b[30m";
+ public const string Red = "\x1b[31m";
+ public const string Green = "\x1b[32m";
+ public const string Yellow = "\x1b[33m";
+ public const string Blue = "\x1b[34m";
+ public const string Magenta = "\x1b[35m";
+ public const string Cyan = "\x1b[36m";
+ public const string Gray = "\x1b[37m";
+ }
+}
diff --git a/src/ef/AnsiTextWriter.cs b/src/ef/AnsiTextWriter.cs
new file mode 100644
index 0000000000..aeb7816d0b
--- /dev/null
+++ b/src/ef/AnsiTextWriter.cs
@@ -0,0 +1,134 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.IO;
+using System.Text.RegularExpressions;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal class AnsiTextWriter
+ {
+ private readonly TextWriter _writer;
+
+ public AnsiTextWriter(TextWriter writer)
+ {
+ _writer = writer;
+ }
+
+ public void WriteLine(string text)
+ {
+ Interpret(text);
+ _writer.Write(Environment.NewLine);
+ }
+
+ private void Interpret(string value)
+ {
+ var matches = Regex.Matches(value, "\x1b\\[([0-9]+)?m");
+
+ var start = 0;
+ foreach (Match match in matches)
+ {
+ var length = match.Index - start;
+ if (length != 0)
+ {
+ _writer.Write(value.Substring(start, length));
+ }
+
+ Apply(match.Groups[1].Value);
+
+ start = match.Index + match.Length;
+ }
+
+ if (start != value.Length)
+ {
+ _writer.Write(value.Substring(start));
+ }
+ }
+
+ private static void Apply(string parameter)
+ {
+ switch (parameter)
+ {
+ case "1":
+ ApplyBold();
+ break;
+
+ case "22":
+ ResetBold();
+ break;
+
+ case "30":
+ ApplyColor(ConsoleColor.Black);
+ break;
+
+ case "31":
+ ApplyColor(ConsoleColor.DarkRed);
+ break;
+
+ case "32":
+ ApplyColor(ConsoleColor.DarkGreen);
+ break;
+
+ case "33":
+ ApplyColor(ConsoleColor.DarkYellow);
+ break;
+
+ case "34":
+ ApplyColor(ConsoleColor.DarkBlue);
+ break;
+
+ case "35":
+ ApplyColor(ConsoleColor.DarkMagenta);
+ break;
+
+ case "36":
+ ApplyColor(ConsoleColor.DarkCyan);
+ break;
+
+ case "37":
+ ApplyColor(ConsoleColor.Gray);
+ break;
+
+ case "39":
+ ResetColor();
+ break;
+
+ default:
+ Debug.Fail("Unsupported parameter: " + parameter);
+ break;
+ }
+ }
+
+ private static void ApplyBold()
+ => Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor | 8);
+
+ private static void ResetBold()
+ => Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor & 7);
+
+ private static void ApplyColor(ConsoleColor color)
+ {
+ var wasBold = ((int)Console.ForegroundColor & 8) != 0;
+
+ Console.ForegroundColor = color;
+
+ if (wasBold)
+ {
+ ApplyBold();
+ }
+ }
+
+ private static void ResetColor()
+ {
+ var wasBold = ((int)Console.ForegroundColor & 8) != 0;
+
+ Console.ResetColor();
+
+ if (wasBold)
+ {
+ ApplyBold();
+ }
+ }
+ }
+}
diff --git a/src/ef/AppDomainOperationExecutor.cs b/src/ef/AppDomainOperationExecutor.cs
new file mode 100644
index 0000000000..7e82703d77
--- /dev/null
+++ b/src/ef/AppDomainOperationExecutor.cs
@@ -0,0 +1,101 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#if NET451
+
+using System;
+using System.Collections;
+using System.IO;
+using System.Reflection;
+using Microsoft.EntityFrameworkCore.Design;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal class AppDomainOperationExecutor : OperationExecutorBase
+ {
+ private readonly object _executor;
+ private readonly AppDomain _domain;
+ private bool _disposed;
+
+ public AppDomainOperationExecutor(
+ string assembly,
+ string startupAssembly,
+ string projectDir,
+ string contentRootPath,
+ string dataDirectory,
+ string rootNamespace,
+ string environment)
+ : base(assembly, startupAssembly, projectDir, contentRootPath, dataDirectory, rootNamespace, environment)
+ {
+ var info = new AppDomainSetup { ApplicationBase = AppBasePath };
+
+ var configurationFile = (startupAssembly ?? assembly) + ".config";
+ if (File.Exists(configurationFile))
+ {
+ Reporter.WriteVerbose(Resources.UsingConfigurationFile(configurationFile));
+ info.ConfigurationFile = configurationFile;
+ }
+
+ _domain = AppDomain.CreateDomain("EntityFrameworkCore.DesignDomain", null, info);
+
+ if (dataDirectory != null)
+ {
+ _domain.SetData("DataDirectory", dataDirectory);
+ }
+
+ var reportHandler = new OperationReportHandler(
+ Reporter.WriteError,
+ Reporter.WriteWarning,
+ Reporter.WriteInformation,
+ Reporter.WriteVerbose);
+
+ _executor = _domain.CreateInstanceAndUnwrap(
+ DesignAssemblyName,
+ ExecutorTypeName,
+ false,
+ BindingFlags.Default,
+ null,
+ new object[]
+ {
+ reportHandler,
+ new Hashtable
+ {
+ { "targetName", AssemblyFileName },
+ { "startupTargetName", StartupAssemblyFileName },
+ { "projectDir", ProjectDirectory },
+ { "contentRootPath", ContentRootPath },
+ { "rootNamespace", RootNamespace },
+ { "environment", EnvironmentName }
+ }
+ },
+ null,
+ null);
+ }
+
+ protected override object CreateResultHandler()
+ => new OperationResultHandler();
+
+ protected override void Execute(string operationName, object resultHandler, IDictionary arguments)
+ => _domain.CreateInstance(
+ DesignAssemblyName,
+ ExecutorTypeName + "+" + operationName,
+ false,
+ BindingFlags.Default,
+ null,
+ new[] { _executor, resultHandler, arguments },
+ null,
+ null);
+
+ public override void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ AppDomain.Unload(_domain);
+ }
+ }
+ }
+}
+
+#endif
diff --git a/src/ef/CommandException.cs b/src/ef/CommandException.cs
new file mode 100644
index 0000000000..74d011b871
--- /dev/null
+++ b/src/ef/CommandException.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal class CommandException : Exception
+ {
+ public CommandException(string message)
+ : base(message)
+ {
+ }
+ }
+}
diff --git a/src/ef/CommandLineUtils/CommandArgument.cs b/src/ef/CommandLineUtils/CommandArgument.cs
new file mode 100644
index 0000000000..52450b1df2
--- /dev/null
+++ b/src/ef/CommandLineUtils/CommandArgument.cs
@@ -0,0 +1,28 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.DotNet.Cli.CommandLine
+{
+ internal class CommandArgument
+ {
+ public CommandArgument()
+ {
+ Values = new List();
+ }
+
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public List Values { get; private set; }
+ public bool MultipleValues { get; set; }
+ public string Value
+ {
+ get
+ {
+ return Values.FirstOrDefault();
+ }
+ }
+ }
+}
diff --git a/src/ef/CommandLineUtils/CommandLineApplication.cs b/src/ef/CommandLineUtils/CommandLineApplication.cs
new file mode 100644
index 0000000000..b4db6116e7
--- /dev/null
+++ b/src/ef/CommandLineUtils/CommandLineApplication.cs
@@ -0,0 +1,636 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Microsoft.DotNet.Cli.CommandLine
+{
+ internal class CommandLineApplication
+ {
+ private enum ParseOptionResult
+ {
+ Succeeded,
+ ShowHelp,
+ ShowVersion,
+ UnexpectedArgs,
+ }
+
+ // Indicates whether the parser should throw an exception when it runs into an unexpected argument.
+ // If this field is set to false, the parser will stop parsing when it sees an unexpected argument, and all
+ // remaining arguments, including the first unexpected argument, will be stored in RemainingArguments property.
+ private readonly bool _throwOnUnexpectedArg;
+
+ public CommandLineApplication(bool throwOnUnexpectedArg = true)
+ {
+ _throwOnUnexpectedArg = throwOnUnexpectedArg;
+ Options = new List();
+ Arguments = new List();
+ Commands = new List();
+ RemainingArguments = new List();
+ Invoke = () => 0;
+ }
+
+ public CommandLineApplication Parent { get; set; }
+ public string Name { get; set; }
+ public string FullName { get; set; }
+ public string Syntax { get; set; }
+ public string Description { get; set; }
+ public List Options { get; private set; }
+ public CommandOption OptionHelp { get; private set; }
+ public CommandOption OptionVersion { get; private set; }
+ public List Arguments { get; private set; }
+ public List RemainingArguments { get; private set; }
+ public bool IsShowingInformation { get; protected set; } // Is showing help or version?
+ public Func Invoke { get; set; }
+ public Func LongVersionGetter { get; set; }
+ public Func ShortVersionGetter { get; set; }
+ public List Commands { get; private set; }
+ public bool HandleResponseFiles { get; set; }
+ public bool AllowArgumentSeparator { get; set; }
+ public bool HandleRemainingArguments { get; set; }
+ public string ArgumentSeparatorHelpText { get; set; }
+
+ public CommandLineApplication Command(string name, bool throwOnUnexpectedArg = true)
+ {
+ return Command(name, _ => { }, throwOnUnexpectedArg);
+ }
+
+ public CommandLineApplication Command(string name, Action configuration,
+ bool throwOnUnexpectedArg = true)
+ {
+ var command = new CommandLineApplication(throwOnUnexpectedArg) { Name = name, Parent = this };
+ Commands.Add(command);
+ configuration(command);
+ return command;
+ }
+
+ public CommandOption Option(string template, string description, CommandOptionType optionType)
+ {
+ return Option(template, description, optionType, _ => { });
+ }
+
+ public CommandOption Option(string template, string description, CommandOptionType optionType, Action configuration)
+ {
+ var option = new CommandOption(template, optionType) { Description = description };
+ Options.Add(option);
+ configuration(option);
+ return option;
+ }
+
+ public CommandArgument Argument(string name, string description, bool multipleValues = false)
+ {
+ return Argument(name, description, _ => { }, multipleValues);
+ }
+
+ public CommandArgument Argument(string name, string description, Action configuration, bool multipleValues = false)
+ {
+ var lastArg = Arguments.LastOrDefault();
+ if (lastArg != null && lastArg.MultipleValues)
+ {
+ var message = string.Format("The last argument '{0}' accepts multiple values. No more argument can be added.",
+ lastArg.Name);
+ throw new InvalidOperationException(message);
+ }
+
+ var argument = new CommandArgument { Name = name, Description = description, MultipleValues = multipleValues };
+ Arguments.Add(argument);
+ configuration(argument);
+ return argument;
+ }
+
+ public void OnExecute(Func invoke)
+ {
+ Invoke = invoke;
+ }
+
+ public void OnExecute(Func> invoke)
+ {
+ Invoke = () => invoke().Result;
+ }
+
+ public int Execute(params string[] args)
+ {
+ CommandLineApplication command = this;
+ IEnumerator arguments = null;
+
+ if (HandleResponseFiles)
+ {
+ args = ExpandResponseFiles(args).ToArray();
+ }
+
+ for (var index = 0; index < args.Length; index++)
+ {
+ var arg = args[index];
+
+ bool isLongOption = arg.StartsWith("--");
+ if (isLongOption || arg.StartsWith("-"))
+ {
+ CommandOption option;
+ var result = ParseOption(isLongOption, command, args, ref index, out option);
+ if (result == ParseOptionResult.ShowHelp)
+ {
+ command.ShowHelp();
+ return 0;
+ }
+ else if (result == ParseOptionResult.ShowVersion)
+ {
+ command.ShowVersion();
+ return 0;
+ }
+ }
+ else
+ {
+ var subcommand = ParseSubCommand(arg, command);
+ if (subcommand != null)
+ {
+ command = subcommand;
+ }
+ else
+ {
+ if (arguments == null)
+ {
+ arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator());
+ }
+
+ if (arguments.MoveNext())
+ {
+ arguments.Current.Values.Add(arg);
+ }
+ else
+ {
+ HandleUnexpectedArg(command, args, index, argTypeName: "command or argument");
+ }
+ }
+ }
+ }
+
+ return command.Invoke();
+ }
+
+ private ParseOptionResult ParseOption(
+ bool isLongOption,
+ CommandLineApplication command,
+ string[] args,
+ ref int index,
+ out CommandOption option)
+ {
+ option = null;
+ ParseOptionResult result = ParseOptionResult.Succeeded;
+ var arg = args[index];
+
+ int optionPrefixLength = isLongOption ? 2 : 1;
+ string[] optionComponents = arg.Substring(optionPrefixLength).Split(new[] { ':', '=' }, 2);
+ string optionName = optionComponents[0];
+
+ if (isLongOption)
+ {
+ option = command.Options.SingleOrDefault(
+ opt => string.Equals(opt.LongName, optionName, StringComparison.Ordinal));
+ }
+ else
+ {
+ option = command.Options.SingleOrDefault(
+ opt => string.Equals(opt.ShortName, optionName, StringComparison.Ordinal));
+
+ if (option == null)
+ {
+ option = command.Options.SingleOrDefault(
+ opt => string.Equals(opt.SymbolName, optionName, StringComparison.Ordinal));
+ }
+ }
+
+ if (option == null)
+ {
+ if (isLongOption && string.IsNullOrEmpty(optionName) &&
+ !command._throwOnUnexpectedArg && AllowArgumentSeparator)
+ {
+ // a stand-alone "--" is the argument separator, so skip it and
+ // handle the rest of the args as unexpected args
+ index++;
+ }
+
+ HandleUnexpectedArg(command, args, index, argTypeName: "option");
+ result = ParseOptionResult.UnexpectedArgs;
+ }
+ else if (command.OptionHelp == option)
+ {
+ result = ParseOptionResult.ShowHelp;
+ }
+ else if (command.OptionVersion == option)
+ {
+ result = ParseOptionResult.ShowVersion;
+ }
+ else
+ {
+ if (optionComponents.Length == 2)
+ {
+ if (!option.TryParse(optionComponents[1]))
+ {
+ command.ShowHint();
+ throw new CommandParsingException(command,
+ $"Unexpected value '{optionComponents[1]}' for option '{optionName}'");
+ }
+ }
+ else
+ {
+ if (option.OptionType == CommandOptionType.NoValue ||
+ option.OptionType == CommandOptionType.BoolValue)
+ {
+ // No value is needed for this option
+ option.TryParse(null);
+ }
+ else
+ {
+ index++;
+ arg = args[index];
+ if (!option.TryParse(arg))
+ {
+ command.ShowHint();
+ throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{optionName}'");
+
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private CommandLineApplication ParseSubCommand(string arg, CommandLineApplication command)
+ {
+ foreach (var subcommand in command.Commands)
+ {
+ if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase))
+ {
+ return subcommand;
+ }
+ }
+
+ return null;
+ }
+
+ // Helper method that adds a help option
+ public CommandOption HelpOption(string template)
+ {
+ // Help option is special because we stop parsing once we see it
+ // So we store it separately for further use
+ OptionHelp = Option(template, "Show help information", CommandOptionType.NoValue);
+
+ return OptionHelp;
+ }
+
+ public CommandOption VersionOption(string template,
+ string shortFormVersion,
+ string longFormVersion = null)
+ {
+ if (longFormVersion == null)
+ {
+ return VersionOption(template, () => shortFormVersion);
+ }
+ else
+ {
+ return VersionOption(template, () => shortFormVersion, () => longFormVersion);
+ }
+ }
+
+ // Helper method that adds a version option
+ public CommandOption VersionOption(string template,
+ Func shortFormVersionGetter,
+ Func longFormVersionGetter = null)
+ {
+ // Version option is special because we stop parsing once we see it
+ // So we store it separately for further use
+ OptionVersion = Option(template, "Show version information", CommandOptionType.NoValue);
+ ShortVersionGetter = shortFormVersionGetter;
+ LongVersionGetter = longFormVersionGetter ?? shortFormVersionGetter;
+
+ return OptionVersion;
+ }
+
+ // Show short hint that reminds users to use help option
+ public void ShowHint()
+ {
+ if (OptionHelp != null)
+ {
+ Console.WriteLine(string.Format("Specify --{0} for a list of available options and commands.", OptionHelp.LongName));
+ }
+ }
+
+ // Show full help
+ public void ShowHelp(string commandName = null)
+ {
+ var headerBuilder = new StringBuilder("Usage:");
+ var usagePrefixLength = headerBuilder.Length;
+ for (var cmd = this; cmd != null; cmd = cmd.Parent)
+ {
+ cmd.IsShowingInformation = true;
+ if (cmd != this && cmd.Arguments.Any())
+ {
+ var args = string.Join(" ", cmd.Arguments.Select(arg => arg.Name));
+ headerBuilder.Insert(usagePrefixLength, string.Format(" {0} {1}", cmd.Name, args));
+ }
+ else
+ {
+ headerBuilder.Insert(usagePrefixLength, string.Format(" {0}", cmd.Name));
+ }
+ }
+
+ CommandLineApplication target;
+
+ if (commandName == null || string.Equals(Name, commandName, StringComparison.OrdinalIgnoreCase))
+ {
+ target = this;
+ }
+ else
+ {
+ target = Commands.SingleOrDefault(cmd => string.Equals(cmd.Name, commandName, StringComparison.OrdinalIgnoreCase));
+
+ if (target != null)
+ {
+ headerBuilder.AppendFormat(" {0}", commandName);
+ }
+ else
+ {
+ // The command name is invalid so don't try to show help for something that doesn't exist
+ target = this;
+ }
+
+ }
+
+ var optionsBuilder = new StringBuilder();
+ var commandsBuilder = new StringBuilder();
+ var argumentsBuilder = new StringBuilder();
+ var argumentSeparatorBuilder = new StringBuilder();
+
+ int maxArgLen = 0;
+ for (var cmd = target; cmd != null; cmd = cmd.Parent)
+ {
+ if (cmd.Arguments.Any())
+ {
+ if (cmd == target)
+ {
+ headerBuilder.Append(" [arguments]");
+ }
+
+ if (argumentsBuilder.Length == 0)
+ {
+ argumentsBuilder.AppendLine();
+ argumentsBuilder.AppendLine("Arguments:");
+ }
+
+ maxArgLen = Math.Max(maxArgLen, MaxArgumentLength(cmd.Arguments));
+ }
+ }
+
+ for (var cmd = target; cmd != null; cmd = cmd.Parent)
+ {
+ if (cmd.Arguments.Any())
+ {
+ var outputFormat = " {0}{1}";
+ foreach (var arg in cmd.Arguments)
+ {
+ argumentsBuilder.AppendFormat(
+ outputFormat,
+ arg.Name.PadRight(maxArgLen + 2),
+ arg.Description);
+ argumentsBuilder.AppendLine();
+ }
+ }
+ }
+
+ if (target.Options.Any())
+ {
+ headerBuilder.Append(" [options]");
+
+ optionsBuilder.AppendLine();
+ optionsBuilder.AppendLine("Options:");
+ var maxOptLen = MaxOptionTemplateLength(target.Options);
+ var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxOptLen + 2);
+ foreach (var opt in target.Options)
+ {
+ optionsBuilder.AppendFormat(outputFormat, opt.Template, opt.Description);
+ optionsBuilder.AppendLine();
+ }
+ }
+
+ if (target.Commands.Any())
+ {
+ headerBuilder.Append(" [command]");
+
+ commandsBuilder.AppendLine();
+ commandsBuilder.AppendLine("Commands:");
+ var maxCmdLen = MaxCommandLength(target.Commands);
+ var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxCmdLen + 2);
+ foreach (var cmd in target.Commands.OrderBy(c => c.Name))
+ {
+ commandsBuilder.AppendFormat(outputFormat, cmd.Name, cmd.Description);
+ commandsBuilder.AppendLine();
+ }
+
+ if (OptionHelp != null)
+ {
+ commandsBuilder.AppendLine();
+ commandsBuilder.AppendFormat("Use \"{0} [command] --help\" for more information about a command.", Name);
+ commandsBuilder.AppendLine();
+ }
+ }
+
+ if (target.AllowArgumentSeparator || target.HandleRemainingArguments)
+ {
+ if (target.AllowArgumentSeparator)
+ {
+ headerBuilder.Append(" [[--] ...]]");
+ }
+ else
+ {
+ headerBuilder.Append(" [args]");
+ }
+
+ if (!string.IsNullOrEmpty(target.ArgumentSeparatorHelpText))
+ {
+ argumentSeparatorBuilder.AppendLine();
+ argumentSeparatorBuilder.AppendLine("Args:");
+ argumentSeparatorBuilder.AppendLine($" {target.ArgumentSeparatorHelpText}");
+ argumentSeparatorBuilder.AppendLine();
+ }
+ }
+
+ headerBuilder.AppendLine();
+
+ var nameAndVersion = new StringBuilder();
+ nameAndVersion.AppendLine(GetFullNameAndVersion());
+ nameAndVersion.AppendLine();
+
+ Console.Write("{0}{1}{2}{3}{4}{5}", nameAndVersion, headerBuilder, argumentsBuilder, optionsBuilder, commandsBuilder, argumentSeparatorBuilder);
+ }
+
+ public void ShowVersion()
+ {
+ for (var cmd = this; cmd != null; cmd = cmd.Parent)
+ {
+ cmd.IsShowingInformation = true;
+ }
+
+ Console.WriteLine(FullName);
+ Console.WriteLine(LongVersionGetter());
+ }
+
+ public string GetFullNameAndVersion()
+ {
+ return ShortVersionGetter == null ? FullName : string.Format("{0} {1}", FullName, ShortVersionGetter());
+ }
+
+ public void ShowRootCommandFullNameAndVersion()
+ {
+ var rootCmd = this;
+ while (rootCmd.Parent != null)
+ {
+ rootCmd = rootCmd.Parent;
+ }
+
+ Console.WriteLine(rootCmd.GetFullNameAndVersion());
+ Console.WriteLine();
+ }
+
+ private int MaxOptionTemplateLength(IEnumerable options)
+ {
+ var maxLen = 0;
+ foreach (var opt in options)
+ {
+ maxLen = opt.Template.Length > maxLen ? opt.Template.Length : maxLen;
+ }
+ return maxLen;
+ }
+
+ private int MaxCommandLength(IEnumerable commands)
+ {
+ var maxLen = 0;
+ foreach (var cmd in commands)
+ {
+ maxLen = cmd.Name.Length > maxLen ? cmd.Name.Length : maxLen;
+ }
+ return maxLen;
+ }
+
+ private int MaxArgumentLength(IEnumerable arguments)
+ {
+ var maxLen = 0;
+ foreach (var arg in arguments)
+ {
+ maxLen = arg.Name.Length > maxLen ? arg.Name.Length : maxLen;
+ }
+ return maxLen;
+ }
+
+ private void HandleUnexpectedArg(CommandLineApplication command, string[] args, int index, string argTypeName)
+ {
+ if (command._throwOnUnexpectedArg)
+ {
+ command.ShowHint();
+ throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'");
+ }
+ else
+ {
+ command.RemainingArguments.Add(args[index]);
+ }
+ }
+
+ private IEnumerable ExpandResponseFiles(IEnumerable args)
+ {
+ foreach (var arg in args)
+ {
+ if (!arg.StartsWith("@", StringComparison.Ordinal))
+ {
+ yield return arg;
+ }
+ else
+ {
+ var fileName = arg.Substring(1);
+
+ var responseFileArguments = ParseResponseFile(fileName);
+
+ // ParseResponseFile can suppress expanding this response file by
+ // returning null. In that case, we'll treat the response
+ // file token as a regular argument.
+
+ if (responseFileArguments == null)
+ {
+ yield return arg;
+ }
+ else
+ {
+ foreach (var responseFileArgument in responseFileArguments)
+ yield return responseFileArgument.Trim();
+ }
+ }
+ }
+ }
+
+ private IEnumerable ParseResponseFile(string fileName)
+ {
+ if (!HandleResponseFiles)
+ return null;
+
+ if (!File.Exists(fileName))
+ {
+ throw new InvalidOperationException($"Response file '{fileName}' doesn't exist.");
+ }
+
+ return File.ReadLines(fileName);
+ }
+
+ private class CommandArgumentEnumerator : IEnumerator
+ {
+ private readonly IEnumerator _enumerator;
+
+ public CommandArgumentEnumerator(IEnumerator enumerator)
+ {
+ _enumerator = enumerator;
+ }
+
+ public CommandArgument Current
+ {
+ get
+ {
+ return _enumerator.Current;
+ }
+ }
+
+ object IEnumerator.Current
+ {
+ get
+ {
+ return Current;
+ }
+ }
+
+ public void Dispose()
+ {
+ _enumerator.Dispose();
+ }
+
+ public bool MoveNext()
+ {
+ if (Current == null || !Current.MultipleValues)
+ {
+ return _enumerator.MoveNext();
+ }
+
+ // If current argument allows multiple values, we don't move forward and
+ // all later values will be added to current CommandArgument.Values
+ return true;
+ }
+
+ public void Reset()
+ {
+ _enumerator.Reset();
+ }
+ }
+ }
+}
diff --git a/src/ef/CommandLineUtils/CommandLineApplicationExtensions.cs b/src/ef/CommandLineUtils/CommandLineApplicationExtensions.cs
new file mode 100644
index 0000000000..1c43455ee1
--- /dev/null
+++ b/src/ef/CommandLineUtils/CommandLineApplicationExtensions.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.DotNet.Cli.CommandLine
+{
+ internal static class CommandLineApplicationExtensions
+ {
+ public static CommandOption Option(this CommandLineApplication command, string template, string description)
+ => command.Option(
+ template,
+ description,
+ template.IndexOf('<') != -1
+ ? template.EndsWith(">...")
+ ? CommandOptionType.MultipleValue
+ : CommandOptionType.SingleValue
+ : CommandOptionType.NoValue);
+ }
+}
diff --git a/src/ef/CommandLineUtils/CommandOption.cs b/src/ef/CommandLineUtils/CommandOption.cs
new file mode 100644
index 0000000000..b686f7e2c3
--- /dev/null
+++ b/src/ef/CommandLineUtils/CommandOption.cs
@@ -0,0 +1,135 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.DotNet.Cli.CommandLine
+{
+ internal class CommandOption
+ {
+ public CommandOption(string template, CommandOptionType optionType)
+ {
+ Template = template;
+ OptionType = optionType;
+ Values = new List();
+
+ foreach (var part in Template.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries))
+ {
+ if (part.StartsWith("--"))
+ {
+ LongName = part.Substring(2);
+ }
+ else if (part.StartsWith("-"))
+ {
+ var optName = part.Substring(1);
+
+ // If there is only one char and it is not an English letter, it is a symbol option (e.g. "-?")
+ if (optName.Length == 1 && !IsEnglishLetter(optName[0]))
+ {
+ SymbolName = optName;
+ }
+ else
+ {
+ ShortName = optName;
+ }
+ }
+ else if (part.StartsWith("<") && part.EndsWith(">"))
+ {
+ ValueName = part.Substring(1, part.Length - 2);
+ }
+ else if (optionType == CommandOptionType.MultipleValue && part.StartsWith("<") && part.EndsWith(">..."))
+ {
+ ValueName = part.Substring(1, part.Length - 5);
+ }
+ else
+ {
+ throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template));
+ }
+ }
+
+ if (string.IsNullOrEmpty(LongName) && string.IsNullOrEmpty(ShortName) && string.IsNullOrEmpty(SymbolName))
+ {
+ throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template));
+ }
+ }
+
+ public string Template { get; set; }
+ public string ShortName { get; set; }
+ public string LongName { get; set; }
+ public string SymbolName { get; set; }
+ public string ValueName { get; set; }
+ public string Description { get; set; }
+ public List Values { get; private set; }
+ public bool? BoolValue { get; private set; }
+ public CommandOptionType OptionType { get; private set; }
+
+ public bool TryParse(string value)
+ {
+ switch (OptionType)
+ {
+ case CommandOptionType.MultipleValue:
+ Values.Add(value);
+ break;
+ case CommandOptionType.SingleValue:
+ if (Values.Any())
+ {
+ return false;
+ }
+ Values.Add(value);
+ break;
+ case CommandOptionType.BoolValue:
+ if (Values.Any())
+ {
+ return false;
+ }
+
+ if (value == null)
+ {
+ // add null to indicate that the option was present, but had no value
+ Values.Add(null);
+ BoolValue = true;
+ }
+ else
+ {
+ bool boolValue;
+ if (!bool.TryParse(value, out boolValue))
+ {
+ return false;
+ }
+
+ Values.Add(value);
+ BoolValue = boolValue;
+ }
+ break;
+ case CommandOptionType.NoValue:
+ if (value != null)
+ {
+ return false;
+ }
+ // Add a value to indicate that this option was specified
+ Values.Add("on");
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+
+ public bool HasValue()
+ {
+ return Values.Any();
+ }
+
+ public string Value()
+ {
+ return HasValue() ? Values[0] : null;
+ }
+
+ private bool IsEnglishLetter(char c)
+ {
+ return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/ef/CommandLineUtils/CommandOptionType.cs b/src/ef/CommandLineUtils/CommandOptionType.cs
new file mode 100644
index 0000000000..5f7d37f029
--- /dev/null
+++ b/src/ef/CommandLineUtils/CommandOptionType.cs
@@ -0,0 +1,13 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.DotNet.Cli.CommandLine
+{
+ internal enum CommandOptionType
+ {
+ MultipleValue,
+ SingleValue,
+ BoolValue,
+ NoValue
+ }
+}
diff --git a/src/ef/CommandLineUtils/CommandParsingException.cs b/src/ef/CommandLineUtils/CommandParsingException.cs
new file mode 100644
index 0000000000..09f6d69181
--- /dev/null
+++ b/src/ef/CommandLineUtils/CommandParsingException.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.DotNet.Cli.CommandLine
+{
+ internal class CommandParsingException : Exception
+ {
+ public CommandParsingException(CommandLineApplication command, string message)
+ : base(message)
+ {
+ Command = command;
+ }
+
+ public CommandLineApplication Command { get; }
+ }
+}
diff --git a/src/ef/Commands/CommandBase.cs b/src/ef/Commands/CommandBase.cs
new file mode 100644
index 0000000000..626b9e30b2
--- /dev/null
+++ b/src/ef/Commands/CommandBase.cs
@@ -0,0 +1,37 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal abstract class CommandBase
+ {
+ public virtual void Configure(CommandLineApplication command)
+ {
+ var verbose = command.Option("-v|--verbose", Resources.VerboseDescription);
+ var noColor = command.Option("--no-color", Resources.NoColorDescription);
+ var prefixOutput = command.Option("--prefix-output", Resources.PrefixDescription);
+
+ command.OnExecute(
+ () =>
+ {
+ Reporter.IsVerbose = verbose.HasValue();
+ Reporter.NoColor = noColor.HasValue();
+ Reporter.PrefixOutput = prefixOutput.HasValue();
+
+ Validate();
+
+ return Execute();
+ });
+ }
+
+ protected virtual void Validate()
+ {
+ }
+
+ protected virtual int Execute()
+ => 0;
+ }
+}
diff --git a/src/ef/Commands/ContextCommandBase.cs b/src/ef/Commands/ContextCommandBase.cs
new file mode 100644
index 0000000000..168f5daf9e
--- /dev/null
+++ b/src/ef/Commands/ContextCommandBase.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal class ContextCommandBase : ProjectCommandBase
+ {
+ private CommandOption _context;
+
+ protected CommandOption Context
+ => _context;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ _context = command.Option("-c|--context ", Resources.ContextDescription);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/DatabaseCommand.cs b/src/ef/Commands/DatabaseCommand.cs
new file mode 100644
index 0000000000..ccff76f6cd
--- /dev/null
+++ b/src/ef/Commands/DatabaseCommand.cs
@@ -0,0 +1,21 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal class DatabaseCommand : HelpCommandBase
+ {
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.DatabaseDescription;
+
+ command.Command("drop", new DatabaseDropCommand().Configure);
+ command.Command("update", new DatabaseUpdateCommand().Configure);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/DatabaseDropCommand.Configure.cs b/src/ef/Commands/DatabaseDropCommand.Configure.cs
new file mode 100644
index 0000000000..8c899ee65d
--- /dev/null
+++ b/src/ef/Commands/DatabaseDropCommand.Configure.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal partial class DatabaseDropCommand : ContextCommandBase
+ {
+ private CommandOption _force;
+ private CommandOption _dryRun;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.DatabaseDropDescription;
+
+ _force = command.Option("-f|--force", Resources.DatabaseDropForceDescription);
+ _dryRun = command.Option("--dry-run", Resources.DatabaseDropDryRunDescription);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/DatabaseDropCommand.cs b/src/ef/Commands/DatabaseDropCommand.cs
new file mode 100644
index 0000000000..bbf3418225
--- /dev/null
+++ b/src/ef/Commands/DatabaseDropCommand.cs
@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ partial class DatabaseDropCommand
+ {
+ protected override int Execute()
+ {
+ var executor = CreateExecutor();
+
+ var result = executor.GetContextInfo(Context.Value());
+ var databaseName = result["DatabaseName"] as string;
+ var dataSource = result["DataSource"] as string;
+
+ if (_dryRun.HasValue())
+ {
+ Reporter.WriteInformation(Resources.DatabaseDropDryRun(databaseName, dataSource));
+
+ return 0;
+ }
+
+ if (!_force.HasValue())
+ {
+ Reporter.WriteInformation(Resources.DatabaseDropPrompt(databaseName, dataSource));
+ var response = Console.ReadLine().Trim().ToUpperInvariant();
+ if (response != "Y")
+ {
+ return 1;
+ }
+ }
+
+ executor.DropDatabase(Context.Value());
+
+ return base.Execute();
+ }
+ }
+}
diff --git a/src/ef/Commands/DatabaseUpdateCommand.Configure.cs b/src/ef/Commands/DatabaseUpdateCommand.Configure.cs
new file mode 100644
index 0000000000..633fa58da2
--- /dev/null
+++ b/src/ef/Commands/DatabaseUpdateCommand.Configure.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal partial class DatabaseUpdateCommand : ContextCommandBase
+ {
+ private CommandArgument _migration;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.DatabaseUpdateDescription;
+
+ _migration = command.Argument("", Resources.MigrationDescription);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/DatabaseUpdateCommand.cs b/src/ef/Commands/DatabaseUpdateCommand.cs
new file mode 100644
index 0000000000..c23f1e7104
--- /dev/null
+++ b/src/ef/Commands/DatabaseUpdateCommand.cs
@@ -0,0 +1,15 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ partial class DatabaseUpdateCommand
+ {
+ protected override int Execute()
+ {
+ CreateExecutor().UpdateDatabase(_migration.Value, Context.Value());
+
+ return base.Execute();
+ }
+ }
+}
diff --git a/src/ef/Commands/DbContextCommand.cs b/src/ef/Commands/DbContextCommand.cs
new file mode 100644
index 0000000000..dc47be6b02
--- /dev/null
+++ b/src/ef/Commands/DbContextCommand.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal class DbContextCommand : HelpCommandBase
+ {
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.DbContextDescription;
+
+ command.Command("info", new DbContextInfoCommand().Configure);
+ command.Command("list", new DbContextListCommand().Configure);
+ command.Command("scaffold", new DbContextScaffoldCommand().Configure);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/DbContextInfoCommand.Configure.cs b/src/ef/Commands/DbContextInfoCommand.Configure.cs
new file mode 100644
index 0000000000..641ff97725
--- /dev/null
+++ b/src/ef/Commands/DbContextInfoCommand.Configure.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal partial class DbContextInfoCommand : ContextCommandBase
+ {
+ private CommandOption _json;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.DbContextInfoDescription;
+
+ _json = Json.ConfigureOption(command);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/DbContextInfoCommand.cs b/src/ef/Commands/DbContextInfoCommand.cs
new file mode 100644
index 0000000000..f68c9583cc
--- /dev/null
+++ b/src/ef/Commands/DbContextInfoCommand.cs
@@ -0,0 +1,41 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ partial class DbContextInfoCommand
+ {
+ protected override int Execute()
+ {
+ var result = CreateExecutor().GetContextInfo(Context.Value());
+
+ if (_json.HasValue())
+ {
+ ReportJsonResult(result);
+ }
+ else
+ {
+ ReportResult(result);
+ }
+
+ return base.Execute();
+ }
+
+ private static void ReportJsonResult(IDictionary result)
+ {
+ Reporter.WriteData("{");
+ Reporter.WriteData(" \"databaseName\": \"" + Json.Escape(result["DatabaseName"] as string) + "\",");
+ Reporter.WriteData(" \"dataSource\": \"" + Json.Escape(result["DataSource"] as string) + "\"");
+ Reporter.WriteData("}");
+ }
+
+ private static void ReportResult(IDictionary result)
+ {
+ Reporter.WriteData(Resources.DatabaseName(result["DatabaseName"]));
+ Reporter.WriteData(Resources.DataSource(result["DataSource"]));
+ }
+ }
+}
diff --git a/src/ef/Commands/DbContextListCommand.Configure.cs b/src/ef/Commands/DbContextListCommand.Configure.cs
new file mode 100644
index 0000000000..47eda37563
--- /dev/null
+++ b/src/ef/Commands/DbContextListCommand.Configure.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal partial class DbContextListCommand : ProjectCommandBase
+ {
+ private CommandOption _json;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.DbContextListDescription;
+
+ _json = Json.ConfigureOption(command);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/DbContextListCommand.cs b/src/ef/Commands/DbContextListCommand.cs
new file mode 100644
index 0000000000..43351441c7
--- /dev/null
+++ b/src/ef/Commands/DbContextListCommand.cs
@@ -0,0 +1,77 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ partial class DbContextListCommand
+ {
+ protected override int Execute()
+ {
+ var types = CreateExecutor().GetContextTypes().ToList();
+
+ if (_json.HasValue())
+ {
+ ReportJsonResults(types);
+ }
+ else
+ {
+ ReportResults(types);
+ }
+
+ return base.Execute();
+ }
+
+ private static void ReportJsonResults(IReadOnlyList contextTypes)
+ {
+ var nameGroups = contextTypes.GroupBy(t => t["Name"]).ToList();
+ var fullNameGroups = contextTypes.GroupBy(t => t["FullName"]).ToList();
+
+ Reporter.WriteData("[");
+
+ for (var i = 0; i < contextTypes.Count; i++)
+ {
+ var safeName = nameGroups.Count(g => g.Key == contextTypes[i]["Name"]) == 1
+ ? contextTypes[i]["Name"]
+ : fullNameGroups.Count(g => g.Key == contextTypes[i]["FullName"]) == 1
+ ? contextTypes[i]["FullName"]
+ : contextTypes[i]["AssemblyQualifiedName"];
+
+ Reporter.WriteData(" {");
+ Reporter.WriteData(" \"fullName\": \"" + contextTypes[i]["FullName"] + "\",");
+ Reporter.WriteData(" \"safeName\": \"" + safeName + "\",");
+ Reporter.WriteData(" \"name\": \"" + contextTypes[i]["Name"] + "\",");
+ Reporter.WriteData(" \"assemblyQualifiedName\": \"" + contextTypes[i]["AssemblyQualifiedName"] + "\"");
+
+ var line = " }";
+ if (i != contextTypes.Count - 1)
+ {
+ line += ",";
+ }
+
+ Reporter.WriteData(line);
+ }
+
+ Reporter.WriteData("]");
+ }
+
+ private static void ReportResults(IEnumerable contextTypes)
+ {
+ var any = false;
+ foreach (var contextType in contextTypes)
+ {
+ Reporter.WriteData(contextType["FullName"] as string);
+ any = true;
+ }
+
+ if (!any)
+ {
+ Reporter.WriteInformation(Resources.NoDbContext);
+ }
+ }
+ }
+}
diff --git a/src/ef/Commands/DbContextScaffoldCommand.Configure.cs b/src/ef/Commands/DbContextScaffoldCommand.Configure.cs
new file mode 100644
index 0000000000..19314721b3
--- /dev/null
+++ b/src/ef/Commands/DbContextScaffoldCommand.Configure.cs
@@ -0,0 +1,39 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal partial class DbContextScaffoldCommand : ProjectCommandBase
+ {
+ private CommandArgument _connection;
+ private CommandArgument _provider;
+ private CommandOption _dataAnnotations;
+ private CommandOption _context;
+ private CommandOption _force;
+ private CommandOption _outputDir;
+ private CommandOption _schemas;
+ private CommandOption _tables;
+ private CommandOption _json;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.DbContextScaffoldDescription;
+
+ _connection = command.Argument("", Resources.ConnectionDescription);
+ _provider = command.Argument("", Resources.ProviderDescription);
+
+ _dataAnnotations = command.Option("-d|--data-annotations", Resources.DataAnnotationsDescription);
+ _context = command.Option("-c|--context ", Resources.ContextNameDescription);
+ _force = command.Option("-f|--force", Resources.DbContextScaffoldForceDescription);
+ _outputDir = command.Option("-o|--output-dir ", Resources.OutputDirDescription);
+ _schemas = command.Option("--schema ...", Resources.SchemasDescription);
+ _tables = command.Option("-t|--table ...", Resources.TablesDescription);
+ _json = Json.ConfigureOption(command);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/DbContextScaffoldCommand.cs b/src/ef/Commands/DbContextScaffoldCommand.cs
new file mode 100644
index 0000000000..ce2d0b13cf
--- /dev/null
+++ b/src/ef/Commands/DbContextScaffoldCommand.cs
@@ -0,0 +1,64 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ partial class DbContextScaffoldCommand
+ {
+ protected override void Validate()
+ {
+ base.Validate();
+
+ if (string.IsNullOrEmpty(_connection.Value))
+ {
+ throw new CommandException(Resources.MissingArgument(_connection.Name));
+ }
+ if (string.IsNullOrEmpty(_provider.Value))
+ {
+ throw new CommandException(Resources.MissingArgument(_provider.Name));
+ }
+ }
+
+ protected override int Execute()
+ {
+ var filesCreated = CreateExecutor().ScaffoldContext(
+ _provider.Value,
+ _connection.Value,
+ _outputDir.Value(),
+ _context.Value(),
+ _schemas.Values,
+ _tables.Values,
+ _dataAnnotations.HasValue(),
+ _force.HasValue())
+ .ToList();
+ if (_json.HasValue())
+ {
+ ReportJsonResults(filesCreated);
+ }
+
+ return base.Execute();
+ }
+
+ private void ReportJsonResults(IReadOnlyList files)
+ {
+ Reporter.WriteData("[");
+
+ for (var i = 0; i < files.Count; i++)
+ {
+ var line = " \"" + Json.Escape(files[i]) + "\"";
+ if (i != files.Count - 1)
+ {
+ line += ",";
+ }
+
+ Reporter.WriteData(line);
+ }
+
+ Reporter.WriteData("]");
+ }
+ }
+}
diff --git a/src/ef/Commands/EFCommandBase.cs b/src/ef/Commands/EFCommandBase.cs
new file mode 100644
index 0000000000..2f671024f1
--- /dev/null
+++ b/src/ef/Commands/EFCommandBase.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal abstract class EFCommandBase : CommandBase
+ {
+ public override void Configure(CommandLineApplication command)
+ {
+ command.HelpOption("-h|--help");
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/EnvironmentCommandBase.cs b/src/ef/Commands/EnvironmentCommandBase.cs
new file mode 100644
index 0000000000..4032566506
--- /dev/null
+++ b/src/ef/Commands/EnvironmentCommandBase.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal class EnvironmentCommandBase : EFCommandBase
+ {
+ private CommandOption _environment;
+
+ protected CommandOption Environment
+ => _environment;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ _environment = command.Option("-e|--environment ", Resources.EnvironmentDescription);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/HelpCommandBase.cs b/src/ef/Commands/HelpCommandBase.cs
new file mode 100644
index 0000000000..1f44ec2df1
--- /dev/null
+++ b/src/ef/Commands/HelpCommandBase.cs
@@ -0,0 +1,26 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal class HelpCommandBase : EFCommandBase
+ {
+ private CommandLineApplication _command;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ _command = command;
+
+ base.Configure(command);
+ }
+
+ protected override int Execute()
+ {
+ _command.ShowHelp();
+
+ return base.Execute();
+ }
+ }
+}
diff --git a/src/ef/Commands/MigrationsAddCommand.Configure.cs b/src/ef/Commands/MigrationsAddCommand.Configure.cs
new file mode 100644
index 0000000000..f80bfbca26
--- /dev/null
+++ b/src/ef/Commands/MigrationsAddCommand.Configure.cs
@@ -0,0 +1,27 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal partial class MigrationsAddCommand : ContextCommandBase
+ {
+ private CommandArgument _name;
+ private CommandOption _outputDir;
+ private CommandOption _json;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.MigrationsAddDescription;
+
+ _name = command.Argument("", Resources.MigrationNameDescription);
+
+ _outputDir = command.Option("-o|--output-dir ", Resources.MigrationsOutputDirDescription);
+ _json = Json.ConfigureOption(command);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/MigrationsAddCommand.cs b/src/ef/Commands/MigrationsAddCommand.cs
new file mode 100644
index 0000000000..60f3de60a4
--- /dev/null
+++ b/src/ef/Commands/MigrationsAddCommand.cs
@@ -0,0 +1,46 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ partial class MigrationsAddCommand
+ {
+ protected override void Validate()
+ {
+ base.Validate();
+
+ if (string.IsNullOrEmpty(_name.Value))
+ {
+ throw new CommandException(Resources.MissingArgument(_name.Name));
+ }
+ }
+
+ protected override int Execute()
+ {
+ var files = CreateExecutor().AddMigration(_name.Value, _outputDir.Value(), Context.Value());
+
+ if (_json.HasValue())
+ {
+ ReportJson(files);
+ }
+ else
+ {
+ Reporter.WriteInformation(Resources.MigrationsAddCompleted);
+ }
+
+ return base.Execute();
+ }
+
+ private static void ReportJson(IDictionary files)
+ {
+ Reporter.WriteData("{");
+ Reporter.WriteData(" \"migrationFile\": \"" + Json.Escape(files["MigrationFile"] as string) + "\",");
+ Reporter.WriteData(" \"metadataFile\": \"" + Json.Escape(files["MetadataFile"] as string) + "\",");
+ Reporter.WriteData(" \"snapshotFile\": \"" + Json.Escape(files["SnapshotFile"] as string) + "\"");
+ Reporter.WriteData("}");
+ }
+ }
+}
diff --git a/src/ef/Commands/MigrationsCommand.cs b/src/ef/Commands/MigrationsCommand.cs
new file mode 100644
index 0000000000..f8e5ae8145
--- /dev/null
+++ b/src/ef/Commands/MigrationsCommand.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal class MigrationsCommand : HelpCommandBase
+ {
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.MigrationsDescription;
+
+ command.Command("add", new MigrationsAddCommand().Configure);
+ command.Command("list", new MigrationsListCommand().Configure);
+ command.Command("remove", new MigrationsRemoveCommand().Configure);
+ command.Command("script", new MigrationsScriptCommand().Configure);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/MigrationsListCommand.Configure.cs b/src/ef/Commands/MigrationsListCommand.Configure.cs
new file mode 100644
index 0000000000..888f463cf1
--- /dev/null
+++ b/src/ef/Commands/MigrationsListCommand.Configure.cs
@@ -0,0 +1,22 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal partial class MigrationsListCommand : ContextCommandBase
+ {
+ private CommandOption _json;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.MigrationsListDescription;
+
+ _json = Json.ConfigureOption(command);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/MigrationsListCommand.cs b/src/ef/Commands/MigrationsListCommand.cs
new file mode 100644
index 0000000000..4ccad09e6e
--- /dev/null
+++ b/src/ef/Commands/MigrationsListCommand.cs
@@ -0,0 +1,73 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ partial class MigrationsListCommand
+ {
+ protected override int Execute()
+ {
+ var migrations = CreateExecutor().GetMigrations(Context.Value()).ToList();
+
+ if (_json.HasValue())
+ {
+ ReportJsonResults(migrations);
+ }
+ else
+ {
+ ReportResults(migrations);
+ }
+
+ return base.Execute();
+ }
+
+ private static void ReportJsonResults(IReadOnlyList migrations)
+ {
+ var nameGroups = migrations.GroupBy(m => m["Name"]).ToList();
+
+ Reporter.WriteData("[");
+
+ for (var i = 0; i < migrations.Count; i++)
+ {
+ var safeName = nameGroups.Count(g => g.Key == migrations[i]["Name"]) == 1
+ ? migrations[i]["Name"]
+ : migrations[i]["Id"];
+
+ Reporter.WriteData(" {");
+ Reporter.WriteData(" \"id\": \"" + migrations[i]["Id"] + "\",");
+ Reporter.WriteData(" \"name\": \"" + migrations[i]["Name"] + "\",");
+ Reporter.WriteData(" \"safeName\": \"" + safeName + "\"");
+
+ var line = " }";
+ if (i != migrations.Count - 1)
+ {
+ line += ",";
+ }
+
+ Reporter.WriteData(line);
+ }
+
+ Reporter.WriteData("]");
+ }
+
+ private static void ReportResults(IEnumerable migrations)
+ {
+ var any = false;
+ foreach (var migration in migrations)
+ {
+ Reporter.WriteData(migration["Id"] as string);
+ any = true;
+ }
+
+ if (!any)
+ {
+ Reporter.WriteInformation(Resources.NoMigrations);
+ }
+ }
+ }
+}
diff --git a/src/ef/Commands/MigrationsRemoveCommand.Configure.cs b/src/ef/Commands/MigrationsRemoveCommand.Configure.cs
new file mode 100644
index 0000000000..7c1ab8024e
--- /dev/null
+++ b/src/ef/Commands/MigrationsRemoveCommand.Configure.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal partial class MigrationsRemoveCommand : ContextCommandBase
+ {
+ private CommandOption _force;
+ private CommandOption _json;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.MigrationsRemoveDescription;
+
+ _force = command.Option("-f|--force", Resources.MigrationsRemoveForceDescription);
+ _json = Json.ConfigureOption(command);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/MigrationsRemoveCommand.cs b/src/ef/Commands/MigrationsRemoveCommand.cs
new file mode 100644
index 0000000000..5d0b907f19
--- /dev/null
+++ b/src/ef/Commands/MigrationsRemoveCommand.cs
@@ -0,0 +1,40 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ partial class MigrationsRemoveCommand
+ {
+ protected override int Execute()
+ {
+ var deletedFiles = CreateExecutor().RemoveMigration(Context.Value(), _force.HasValue()).ToList();
+ if (_json.HasValue())
+ {
+ ReportJsonResults(deletedFiles);
+ }
+
+ return base.Execute();
+ }
+
+ private void ReportJsonResults(IReadOnlyList files)
+ {
+ Reporter.WriteData("[");
+
+ for (var i = 0; i < files.Count; i++)
+ {
+ var line = " \"" + Json.Escape(files[i]) + "\"";
+ if (i != files.Count - 1)
+ {
+ line += ",";
+ }
+
+ Reporter.WriteData(line);
+ }
+
+ Reporter.WriteData("]");
+ }
+ }
+}
diff --git a/src/ef/Commands/MigrationsScriptCommand.Configure.cs b/src/ef/Commands/MigrationsScriptCommand.Configure.cs
new file mode 100644
index 0000000000..9ecddc4b3f
--- /dev/null
+++ b/src/ef/Commands/MigrationsScriptCommand.Configure.cs
@@ -0,0 +1,29 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal partial class MigrationsScriptCommand : ContextCommandBase
+ {
+ private CommandArgument _from;
+ private CommandArgument _to;
+ private CommandOption _output;
+ private CommandOption _idempotent;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ command.Description = Resources.MigrationsScriptDescription;
+
+ _from = command.Argument("", Resources.MigrationFromDescription);
+ _to = command.Argument("", Resources.MigrationToDescription);
+
+ _output = command.Option("-o|--output ", Resources.OutputDescription);
+ _idempotent = command.Option("-i|--idempotent", Resources.IdempotentDescription);
+
+ base.Configure(command);
+ }
+ }
+}
diff --git a/src/ef/Commands/MigrationsScriptCommand.cs b/src/ef/Commands/MigrationsScriptCommand.cs
new file mode 100644
index 0000000000..c38e694b80
--- /dev/null
+++ b/src/ef/Commands/MigrationsScriptCommand.cs
@@ -0,0 +1,39 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.IO;
+using System.Text;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ partial class MigrationsScriptCommand
+ {
+ protected override int Execute()
+ {
+ var sql = CreateExecutor().ScriptMigration(
+ _from.Value,
+ _to.Value,
+ _idempotent.HasValue(),
+ Context.Value());
+
+ if (!_output.HasValue())
+ {
+ Reporter.WriteData(sql);
+ }
+ else
+ {
+ var directory = Path.GetDirectoryName(_output.Value());
+ if (!string.IsNullOrEmpty(directory))
+ {
+ Directory.CreateDirectory(directory);
+ }
+
+ Reporter.WriteVerbose(Resources.WritingFile(_output.Value()));
+ File.WriteAllText(_output.Value(), sql, Encoding.UTF8);
+ }
+
+ return base.Execute();
+ }
+ }
+}
diff --git a/src/ef/Commands/ProjectCommandBase.cs b/src/ef/Commands/ProjectCommandBase.cs
new file mode 100644
index 0000000000..040d61b675
--- /dev/null
+++ b/src/ef/Commands/ProjectCommandBase.cs
@@ -0,0 +1,68 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal abstract class ProjectCommandBase : EnvironmentCommandBase
+ {
+ private CommandOption _assembly;
+ private CommandOption _startupAssembly;
+ private CommandOption _dataDir;
+ private CommandOption _projectDir;
+ private CommandOption _contentRoot;
+ private CommandOption _rootNamespace;
+ private CommandOption _noAppDomain;
+
+ public override void Configure(CommandLineApplication command)
+ {
+ _assembly = command.Option("-a|--assembly ", Resources.AssemblyDescription);
+ _noAppDomain = command.Option("--no-appdomain", Resources.NoAppDomainDescription);
+ _startupAssembly = command.Option("-s|--startup-assembly ", Resources.StartupAssemblyDescription);
+ _dataDir = command.Option("--data-dir ", Resources.DataDirDescription);
+ _projectDir = command.Option("--project-dir ", Resources.ProjectDirDescription);
+ _contentRoot = command.Option("--content-root ", Resources.ContentRootDescription);
+ _rootNamespace = command.Option("--root-namespace ", Resources.RootNamespaceDescription);
+
+ base.Configure(command);
+ }
+
+ protected override void Validate()
+ {
+ base.Validate();
+
+ if (!_assembly.HasValue())
+ {
+ throw new CommandException(Resources.MissingOption(_assembly.LongName));
+ }
+ }
+
+ protected IOperationExecutor CreateExecutor()
+ {
+ // TODO: Re-throw TypeLoadException and FileNotFoundException?
+#if NET451
+ if (!_noAppDomain.HasValue())
+ {
+ return new AppDomainOperationExecutor(
+ _assembly.Value(),
+ _startupAssembly.Value(),
+ _projectDir.Value(),
+ _contentRoot.Value(),
+ _dataDir.Value(),
+ _rootNamespace.Value(),
+ Environment.Value());
+ }
+#endif
+ return new ReflectionOperationExecutor(
+ _assembly.Value(),
+ _startupAssembly.Value(),
+ _projectDir.Value(),
+ _contentRoot.Value(),
+ _dataDir.Value(),
+ _rootNamespace.Value(),
+ Environment.Value());
+ }
+ }
+}
diff --git a/src/ef/Commands/RootCommand.cs b/src/ef/Commands/RootCommand.cs
new file mode 100644
index 0000000000..6f8f8dc9fd
--- /dev/null
+++ b/src/ef/Commands/RootCommand.cs
@@ -0,0 +1,49 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+using Microsoft.DotNet.Cli.CommandLine;
+using static Microsoft.EntityFrameworkCore.Tools.AnsiConstants;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Commands
+{
+ internal class RootCommand : HelpCommandBase
+ {
+ public override void Configure(CommandLineApplication command)
+ {
+ command.FullName = Resources.EFFullName;
+
+ // NB: Update ShouldHelp in dotnet-ef when adding new command groups
+ command.Command("database", new DatabaseCommand().Configure);
+ command.Command("dbcontext", new DbContextCommand().Configure);
+ command.Command("migrations", new MigrationsCommand().Configure);
+
+ command.VersionOption("--version", GetVersion);
+
+ base.Configure(command);
+ }
+
+ protected override int Execute()
+ {
+ Reporter.WriteInformation(
+ string.Join(
+ Environment.NewLine,
+ string.Empty,
+ Reporter.Colorize(@" _/\__ ", s => s.Insert(21, Bold + Gray)),
+ Reporter.Colorize(@" ---==/ \\ ", s => s.Insert(20, Bold + Gray)),
+ Reporter.Colorize(@" ___ ___ |. \|\ ", s => s.Insert(26, Bold).Insert(21, Dark).Insert(20, Bold + Gray).Insert(9, Dark + Magenta)),
+ Reporter.Colorize(@" | __|| __| | ) \\\ ", s => s.Insert(20, Bold + Gray).Insert(8, Dark + Magenta)),
+ Reporter.Colorize(@" | _| | _| \_/ | //|\\ ", s => s.Insert(20, Bold + Gray).Insert(8, Dark + Magenta)),
+ Reporter.Colorize(@" |___||_| / \\\/\\", s => s.Insert(33, Reset).Insert(23, Bold + Gray).Insert(8, Dark + Magenta)),
+ string.Empty));
+
+ return base.Execute();
+ }
+
+ private static string GetVersion()
+ => typeof(RootCommand).GetTypeInfo().Assembly.GetCustomAttribute()
+ .InformationalVersion;
+ }
+}
diff --git a/src/ef/IOperationExecutor.cs b/src/ef/IOperationExecutor.cs
new file mode 100644
index 0000000000..4a2339eba4
--- /dev/null
+++ b/src/ef/IOperationExecutor.cs
@@ -0,0 +1,33 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal interface IOperationExecutor : IDisposable
+ {
+ IDictionary AddMigration(string name, string outputDir, string contextType);
+ IEnumerable RemoveMigration(string contextType, bool force);
+ IEnumerable GetMigrations(string contextType);
+ void DropDatabase(string contextType);
+ IDictionary GetContextInfo(string name);
+ string GetContextType(string name);
+ void UpdateDatabase(string migration, string contextType);
+ IEnumerable GetContextTypes();
+
+ IEnumerable ScaffoldContext(
+ string provider,
+ string connectionString,
+ string outputDir,
+ string dbContextClassName,
+ IEnumerable schemaFilters,
+ IEnumerable tableFilters,
+ bool useDataAnnotations,
+ bool overwriteFiles);
+
+ string ScriptMigration(string fromMigration, string toMigration, bool idempotent, string contextType);
+ }
+}
diff --git a/src/ef/Json.cs b/src/ef/Json.cs
new file mode 100644
index 0000000000..ba94ce0eb6
--- /dev/null
+++ b/src/ef/Json.cs
@@ -0,0 +1,17 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal static class Json
+ {
+ public static CommandOption ConfigureOption(CommandLineApplication command)
+ => command.Option("--json", Resources.JsonDescription);
+
+ public static string Escape(string text)
+ => text.Replace("\\", "\\\\").Replace("\"", "\\\"");
+ }
+}
diff --git a/src/ef/OperationExecutorBase.cs b/src/ef/OperationExecutorBase.cs
new file mode 100644
index 0000000000..931116c2f4
--- /dev/null
+++ b/src/ef/OperationExecutorBase.cs
@@ -0,0 +1,200 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using Microsoft.EntityFrameworkCore.Tools.Properties;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal abstract class OperationExecutorBase : IOperationExecutor
+ {
+ private const string DataDirEnvName = "ADONET_DATA_DIR";
+ public const string DesignAssemblyName = "Microsoft.EntityFrameworkCore.Design";
+ protected const string ExecutorTypeName = "Microsoft.EntityFrameworkCore.Design.OperationExecutor";
+
+ private static readonly IDictionary EmptyArguments = new Dictionary(0);
+ public string AppBasePath { get; }
+
+ protected string AssemblyFileName { get; set; }
+ protected string StartupAssemblyFileName { get; set; }
+ protected string ContentRootPath { get; }
+ protected string ProjectDirectory { get; }
+ protected string RootNamespace { get; }
+ protected string EnvironmentName { get; }
+
+ protected OperationExecutorBase(
+ string assembly,
+ string startupAssembly,
+ string projectDir,
+ string contentRootPath,
+ string dataDirectory,
+ string rootNamespace,
+ string environment)
+ {
+ AssemblyFileName = Path.GetFileNameWithoutExtension(assembly);
+ StartupAssemblyFileName = startupAssembly == null
+ ? AssemblyFileName
+ : Path.GetFileNameWithoutExtension(startupAssembly);
+
+ AppBasePath = Path.GetDirectoryName(startupAssembly ?? assembly);
+ if (!Path.IsPathRooted(AppBasePath))
+ {
+ AppBasePath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), AppBasePath));
+ }
+
+ ContentRootPath = contentRootPath ?? AppBasePath;
+ RootNamespace = rootNamespace ?? AssemblyFileName;
+ ProjectDirectory = projectDir ?? Directory.GetCurrentDirectory();
+ EnvironmentName = environment;
+
+ Reporter.WriteVerbose(Resources.UsingAssembly(AssemblyFileName));
+ Reporter.WriteVerbose(Resources.UsingStartupAssembly(StartupAssemblyFileName));
+ Reporter.WriteVerbose(Resources.UsingApplicationBase(AppBasePath));
+ Reporter.WriteVerbose(Resources.UsingContentRoot(ContentRootPath));
+ Reporter.WriteVerbose(Resources.UsingRootNamespace(RootNamespace));
+ Reporter.WriteVerbose(Resources.UsingProjectDir(ProjectDirectory));
+
+ if (dataDirectory != null)
+ {
+ Reporter.WriteVerbose(Resources.UsingDataDir(dataDirectory));
+ Environment.SetEnvironmentVariable(DataDirEnvName, dataDirectory);
+ }
+ }
+
+ public virtual void Dispose()
+ {
+ }
+
+ protected abstract dynamic CreateResultHandler();
+ protected abstract void Execute(string operationName, object resultHandler, IDictionary arguments);
+
+ private TResult InvokeOperation(string operation)
+ => InvokeOperation(operation, EmptyArguments);
+
+ private TResult InvokeOperation(string operation, IDictionary arguments)
+ => (TResult)InvokeOperationImpl(operation, arguments);
+
+ private void InvokeOperation(string operation, IDictionary arguments)
+ => InvokeOperationImpl(operation, arguments);
+
+ private object InvokeOperationImpl(string operationName, IDictionary arguments)
+ {
+ var resultHandler = CreateResultHandler();
+
+ var currentDirectory = Directory.GetCurrentDirectory();
+ Directory.SetCurrentDirectory(AppBasePath);
+ try
+ {
+ Execute(operationName, resultHandler, arguments);
+ }
+ finally
+ {
+ Directory.SetCurrentDirectory(currentDirectory);
+ }
+
+ if (resultHandler.ErrorType != null)
+ {
+ throw new WrappedException(
+ resultHandler.ErrorType,
+ resultHandler.ErrorMessage,
+ resultHandler.ErrorStackTrace);
+ }
+
+ return resultHandler.Result;
+ }
+
+ public IDictionary AddMigration(string name, string outputDir, string contextType)
+ => InvokeOperation("AddMigration",
+ new Dictionary
+ {
+ ["name"] = name,
+ ["outputDir"] = outputDir,
+ ["contextType"] = contextType
+ });
+
+ public IEnumerable RemoveMigration(string contextType, bool force)
+ => InvokeOperation>("RemoveMigration",
+ new Dictionary
+ {
+ ["contextType"] = contextType,
+ ["force"] = force
+ });
+
+ public IEnumerable GetMigrations(string contextType)
+ => InvokeOperation>("GetMigrations",
+ new Dictionary
+ {
+ ["contextType"] = contextType
+ });
+
+ public void DropDatabase(string contextType)
+ => InvokeOperation("DropDatabase",
+ new Dictionary
+ {
+ ["contextType"] = contextType
+ });
+
+ public IDictionary GetContextInfo(string name)
+ => InvokeOperation("GetContextInfo",
+ new Dictionary
+ {
+ ["contextType"] = name
+ });
+
+ public void UpdateDatabase(string migration, string contextType)
+ => InvokeOperation("UpdateDatabase",
+ new Dictionary
+ {
+ ["targetMigration"] = migration,
+ ["contextType"] = contextType
+ });
+
+ public IEnumerable GetContextTypes()
+ => InvokeOperation>("GetContextTypes");
+
+ public IEnumerable ScaffoldContext(string provider,
+ string connectionString,
+ string outputDir,
+ string dbContextClassName,
+ IEnumerable schemaFilters,
+ IEnumerable tableFilters,
+ bool useDataAnnotations,
+ bool overwriteFiles)
+ => InvokeOperation>("ScaffoldContext",
+ new Dictionary
+ {
+ ["provider"] = provider,
+ ["connectionString"] = connectionString,
+ ["outputDir"] = outputDir,
+ ["dbContextClassName"] = dbContextClassName,
+ ["schemaFilters"] = schemaFilters,
+ ["tableFilters"] = tableFilters,
+ ["useDataAnnotations"] = useDataAnnotations,
+ ["overwriteFiles"] = overwriteFiles
+ });
+
+ public string ScriptMigration(
+ string fromMigration,
+ string toMigration,
+ bool idempotent,
+ string contextType)
+ => InvokeOperation("ScriptMigration",
+ new Dictionary
+ {
+ ["fromMigration"] = fromMigration,
+ ["toMigration"] = toMigration,
+ ["idempotent"] = idempotent,
+ ["contextType"] = contextType
+ });
+
+ public string GetContextType(string name)
+ => InvokeOperation("GetContextType",
+ new Dictionary
+ {
+ ["name"] = name
+ });
+ }
+}
diff --git a/src/ef/Program.cs b/src/ef/Program.cs
new file mode 100644
index 0000000000..03eeb328be
--- /dev/null
+++ b/src/ef/Program.cs
@@ -0,0 +1,46 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Commands;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal static class Program
+ {
+ private static int Main(string[] args)
+ {
+ var app = new CommandLineApplication()
+ {
+ Name = "ef"
+ };
+
+ new RootCommand().Configure(app);
+
+ try
+ {
+ return app.Execute(args);
+ }
+ catch (Exception ex)
+ {
+ var wrappedException = ex as WrappedException;
+ if (ex is CommandException
+ || ex is CommandParsingException
+ || (wrappedException != null
+ && wrappedException.Type == "Microsoft.EntityFrameworkCore.Design.OperationException"))
+ {
+ Reporter.WriteVerbose(ex.ToString());
+ }
+ else
+ {
+ Reporter.WriteInformation(ex.ToString());
+ }
+
+ Reporter.WriteError(ex.Message);
+
+ return 1;
+ }
+ }
+ }
+}
diff --git a/src/ef/Properties/AssemblyInfo.cs b/src/ef/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..f7cf8bb06a
--- /dev/null
+++ b/src/ef/Properties/AssemblyInfo.cs
@@ -0,0 +1,7 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo(
+ "ef.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/ef/Properties/Resources.Designer.cs b/src/ef/Properties/Resources.Designer.cs
new file mode 100644
index 0000000000..023051d147
--- /dev/null
+++ b/src/ef/Properties/Resources.Designer.cs
@@ -0,0 +1,431 @@
+//
+
+using System.Reflection;
+using System.Resources;
+using JetBrains.Annotations;
+
+namespace Microsoft.EntityFrameworkCore.Tools.Properties
+{
+ ///
+ /// This API supports the Entity Framework Core infrastructure and is not intended to be used
+ /// directly from your code. This API may change or be removed in future releases.
+ ///
+ internal static class Resources
+ {
+ private static readonly ResourceManager _resourceManager
+ = new ResourceManager("Microsoft.EntityFrameworkCore.Tools.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly);
+
+ ///
+ /// The assembly to use.
+ ///
+ public static string AssemblyDescription
+ => GetString("AssemblyDescription");
+
+ ///
+ /// The connection string to the database.
+ ///
+ public static string ConnectionDescription
+ => GetString("ConnectionDescription");
+
+ ///
+ /// The content root path. Defaults to the startup assembly directory.
+ ///
+ public static string ContentRootDescription
+ => GetString("ContentRootDescription");
+
+ ///
+ /// The DbContext to use.
+ ///
+ public static string ContextDescription
+ => GetString("ContextDescription");
+
+ ///
+ /// The name of the DbContext.
+ ///
+ public static string ContextNameDescription
+ => GetString("ContextNameDescription");
+
+ ///
+ /// Use attributes to configure the model (where possible). If omitted, only the fluent API is used.
+ ///
+ public static string DataAnnotationsDescription
+ => GetString("DataAnnotationsDescription");
+
+ ///
+ /// Commands to manage the database.
+ ///
+ public static string DatabaseDescription
+ => GetString("DatabaseDescription");
+
+ ///
+ /// Drops the database.
+ ///
+ public static string DatabaseDropDescription
+ => GetString("DatabaseDropDescription");
+
+ ///
+ /// This would drop the database '{database}' on server '{dataSource}'.
+ ///
+ public static string DatabaseDropDryRun([CanBeNull] object database, [CanBeNull] object dataSource)
+ => string.Format(
+ GetString("DatabaseDropDryRun", nameof(database), nameof(dataSource)),
+ database, dataSource);
+
+ ///
+ /// Show which database would be dropped, but don't drop it.
+ ///
+ public static string DatabaseDropDryRunDescription
+ => GetString("DatabaseDropDryRunDescription");
+
+ ///
+ /// Don't confirm.
+ ///
+ public static string DatabaseDropForceDescription
+ => GetString("DatabaseDropForceDescription");
+
+ ///
+ /// Are you sure you want to drop the database '{database}' on server '{dataSource}'? (y/N)
+ ///
+ public static string DatabaseDropPrompt([CanBeNull] object database, [CanBeNull] object dataSource)
+ => string.Format(
+ GetString("DatabaseDropPrompt", nameof(database), nameof(dataSource)),
+ database, dataSource);
+
+ ///
+ /// Database name: {database}
+ ///
+ public static string DatabaseName([CanBeNull] object database)
+ => string.Format(
+ GetString("DatabaseName", nameof(database)),
+ database);
+
+ ///
+ /// Updates the database to a specified migration.
+ ///
+ public static string DatabaseUpdateDescription
+ => GetString("DatabaseUpdateDescription");
+
+ ///
+ /// The data directory.
+ ///
+ public static string DataDirDescription
+ => GetString("DataDirDescription");
+
+ ///
+ /// Data source: {dataSource}
+ ///
+ public static string DataSource([CanBeNull] object dataSource)
+ => string.Format(
+ GetString("DataSource", nameof(dataSource)),
+ dataSource);
+
+ ///
+ /// Commands to manage DbContext types.
+ ///
+ public static string DbContextDescription
+ => GetString("DbContextDescription");
+
+ ///
+ /// Gets information about a DbContext type.
+ ///
+ public static string DbContextInfoDescription
+ => GetString("DbContextInfoDescription");
+
+ ///
+ /// Lists available DbContext types.
+ ///
+ public static string DbContextListDescription
+ => GetString("DbContextListDescription");
+
+ ///
+ /// Scaffolds a DbContext and entity types for a database.
+ ///
+ public static string DbContextScaffoldDescription
+ => GetString("DbContextScaffoldDescription");
+
+ ///
+ /// Overwrite existing files.
+ ///
+ public static string DbContextScaffoldForceDescription
+ => GetString("DbContextScaffoldForceDescription");
+
+ ///
+ /// Entity Framework Core Command Line Tools
+ ///
+ public static string EFFullName
+ => GetString("EFFullName");
+
+ ///
+ /// The environment to use. Defaults to "Development".
+ ///
+ public static string EnvironmentDescription
+ => GetString("EnvironmentDescription");
+
+ ///
+ /// Generate a script that can be used on a database at any migration.
+ ///
+ public static string IdempotentDescription
+ => GetString("IdempotentDescription");
+
+ ///
+ /// Show JSON output.
+ ///
+ public static string JsonDescription
+ => GetString("JsonDescription");
+
+ ///
+ /// The target migration. If '0', all migrations will be reverted. Defaults to the last migration.
+ ///
+ public static string MigrationDescription
+ => GetString("MigrationDescription");
+
+ ///
+ /// The starting migration. Defaults to '0' (the initial database).
+ ///
+ public static string MigrationFromDescription
+ => GetString("MigrationFromDescription");
+
+ ///
+ /// The name of the migration.
+ ///
+ public static string MigrationNameDescription
+ => GetString("MigrationNameDescription");
+
+ ///
+ /// Done. To undo this action, use 'ef migrations remove'
+ ///
+ public static string MigrationsAddCompleted
+ => GetString("MigrationsAddCompleted");
+
+ ///
+ /// Adds a new migration.
+ ///
+ public static string MigrationsAddDescription
+ => GetString("MigrationsAddDescription");
+
+ ///
+ /// Commands to manage migrations.
+ ///
+ public static string MigrationsDescription
+ => GetString("MigrationsDescription");
+
+ ///
+ /// Lists available migrations.
+ ///
+ public static string MigrationsListDescription
+ => GetString("MigrationsListDescription");
+
+ ///
+ /// The directory (and sub-namespace) to use. Paths are relative to the project directory. Defaults to "Migrations".
+ ///
+ public static string MigrationsOutputDirDescription
+ => GetString("MigrationsOutputDirDescription");
+
+ ///
+ /// Removes the last migration.
+ ///
+ public static string MigrationsRemoveDescription
+ => GetString("MigrationsRemoveDescription");
+
+ ///
+ /// Don't check to see if the migration has been applied to the database.
+ ///
+ public static string MigrationsRemoveForceDescription
+ => GetString("MigrationsRemoveForceDescription");
+
+ ///
+ /// Generates a SQL script from migrations.
+ ///
+ public static string MigrationsScriptDescription
+ => GetString("MigrationsScriptDescription");
+
+ ///
+ /// The ending migration. Defaults to the last migration.
+ ///
+ public static string MigrationToDescription
+ => GetString("MigrationToDescription");
+
+ ///
+ /// Missing required argument '{arg}'.
+ ///
+ public static string MissingArgument([CanBeNull] object arg)
+ => string.Format(
+ GetString("MissingArgument", nameof(arg)),
+ arg);
+
+ ///
+ /// Missing required option '--{option}'.
+ ///
+ public static string MissingOption([CanBeNull] object option)
+ => string.Format(
+ GetString("MissingOption", nameof(option)),
+ option);
+
+ ///
+ /// Don't use app domains. Always implied on .NET Core.
+ ///
+ public static string NoAppDomainDescription
+ => GetString("NoAppDomainDescription");
+
+ ///
+ /// Don't colorize output.
+ ///
+ public static string NoColorDescription
+ => GetString("NoColorDescription");
+
+ ///
+ /// No DbContext was found.
+ ///
+ public static string NoDbContext
+ => GetString("NoDbContext");
+
+ ///
+ /// No migrations were found.
+ ///
+ public static string NoMigrations
+ => GetString("NoMigrations");
+
+ ///
+ /// The file to write the result to.
+ ///
+ public static string OutputDescription
+ => GetString("OutputDescription");
+
+ ///
+ /// The directory to put files in. Paths are relative to the project directory.
+ ///
+ public static string OutputDirDescription
+ => GetString("OutputDirDescription");
+
+ ///
+ /// Prefix output with level.
+ ///
+ public static string PrefixDescription
+ => GetString("PrefixDescription");
+
+ ///
+ /// The project directory. Defaults to the current directory.
+ ///
+ public static string ProjectDirDescription
+ => GetString("ProjectDirDescription");
+
+ ///
+ /// The provider to use. (E.g. Microsoft.EntityFrameworkCore.SqlServer)
+ ///
+ public static string ProviderDescription
+ => GetString("ProviderDescription");
+
+ ///
+ /// The root namespace. Defaults to the target assembly name.
+ ///
+ public static string RootNamespaceDescription
+ => GetString("RootNamespaceDescription");
+
+ ///
+ /// The schemas of tables to generate entity types for.
+ ///
+ public static string SchemasDescription
+ => GetString("SchemasDescription");
+
+ ///
+ /// The startup assembly to use. Defaults to the target assembly.
+ ///
+ public static string StartupAssemblyDescription
+ => GetString("StartupAssemblyDescription");
+
+ ///
+ /// The tables to generate entity types for.
+ ///
+ public static string TablesDescription
+ => GetString("TablesDescription");
+
+ ///
+ /// Using application base '{appBase}'.
+ ///
+ public static string UsingApplicationBase([CanBeNull] object appBase)
+ => string.Format(
+ GetString("UsingApplicationBase", nameof(appBase)),
+ appBase);
+
+ ///
+ /// Using assembly '{assembly}'.
+ ///
+ public static string UsingAssembly([CanBeNull] object assembly)
+ => string.Format(
+ GetString("UsingAssembly", nameof(assembly)),
+ assembly);
+
+ ///
+ /// Using configuration file '{config}'.
+ ///
+ public static string UsingConfigurationFile([CanBeNull] object config)
+ => string.Format(
+ GetString("UsingConfigurationFile", nameof(config)),
+ config);
+
+ ///
+ /// Using content root '{contentRoot}'.
+ ///
+ public static string UsingContentRoot([CanBeNull] object contentRoot)
+ => string.Format(
+ GetString("UsingContentRoot", nameof(contentRoot)),
+ contentRoot);
+
+ ///
+ /// Using data directory '{dataDir}'.
+ ///
+ public static string UsingDataDir([CanBeNull] object dataDir)
+ => string.Format(
+ GetString("UsingDataDir", nameof(dataDir)),
+ dataDir);
+
+ ///
+ /// Using project directory '{projectDir}'.
+ ///
+ public static string UsingProjectDir([CanBeNull] object projectDir)
+ => string.Format(
+ GetString("UsingProjectDir", nameof(projectDir)),
+ projectDir);
+
+ ///
+ /// Using root namespace '{rootNamespace}'.
+ ///
+ public static string UsingRootNamespace([CanBeNull] object rootNamespace)
+ => string.Format(
+ GetString("UsingRootNamespace", nameof(rootNamespace)),
+ rootNamespace);
+
+ ///
+ /// Using startup assembly '{startupAssembly}'.
+ ///
+ public static string UsingStartupAssembly([CanBeNull] object startupAssembly)
+ => string.Format(
+ GetString("UsingStartupAssembly", nameof(startupAssembly)),
+ startupAssembly);
+
+ ///
+ /// Show verbose output.
+ ///
+ public static string VerboseDescription
+ => GetString("VerboseDescription");
+
+ ///
+ /// Writing '{file}'...
+ ///
+ public static string WritingFile([CanBeNull] object file)
+ => string.Format(
+ GetString("WritingFile", nameof(file)),
+ file);
+
+ private static string GetString(string name, params string[] formatterNames)
+ {
+ var value = _resourceManager.GetString(name);
+ for (var i = 0; i < formatterNames.Length; i++)
+ {
+ value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}");
+ }
+
+ return value;
+ }
+ }
+}
diff --git a/src/ef/Properties/Resources.Designer.tt b/src/ef/Properties/Resources.Designer.tt
new file mode 100644
index 0000000000..2f70174a83
--- /dev/null
+++ b/src/ef/Properties/Resources.Designer.tt
@@ -0,0 +1,5 @@
+<#
+ Session["ResourceFile"] = "Resources.resx";
+ Session["AccessModifier"] = "internal";
+#>
+<#@ include file="..\..\..\tools\Resources.tt" #>
\ No newline at end of file
diff --git a/src/ef/Properties/Resources.resx b/src/ef/Properties/Resources.resx
new file mode 100644
index 0000000000..c342dce4f6
--- /dev/null
+++ b/src/ef/Properties/Resources.resx
@@ -0,0 +1,306 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The assembly to use.
+
+
+ The connection string to the database.
+
+
+ The content root path. Defaults to the startup assembly directory.
+
+
+ The DbContext to use.
+
+
+ The name of the DbContext.
+
+
+ Use attributes to configure the model (where possible). If omitted, only the fluent API is used.
+
+
+ Commands to manage the database.
+
+
+ Drops the database.
+
+
+ This would drop the database '{database}' on server '{dataSource}'.
+
+
+ Show which database would be dropped, but don't drop it.
+
+
+ Don't confirm.
+
+
+ Are you sure you want to drop the database '{database}' on server '{dataSource}'? (y/N)
+
+
+ Database name: {database}
+
+
+ Updates the database to a specified migration.
+
+
+ The data directory.
+
+
+ Data source: {dataSource}
+
+
+ Commands to manage DbContext types.
+
+
+ Gets information about a DbContext type.
+
+
+ Lists available DbContext types.
+
+
+ Scaffolds a DbContext and entity types for a database.
+
+
+ Overwrite existing files.
+
+
+ Entity Framework Core Command Line Tools
+
+
+ The environment to use. Defaults to "Development".
+
+
+ Generate a script that can be used on a database at any migration.
+
+
+ Show JSON output.
+
+
+ The target migration. If '0', all migrations will be reverted. Defaults to the last migration.
+
+
+ The starting migration. Defaults to '0' (the initial database).
+
+
+ The name of the migration.
+
+
+ Done. To undo this action, use 'ef migrations remove'
+
+
+ Adds a new migration.
+
+
+ Commands to manage migrations.
+
+
+ Lists available migrations.
+
+
+ The directory (and sub-namespace) to use. Paths are relative to the project directory. Defaults to "Migrations".
+
+
+ Removes the last migration.
+
+
+ Don't check to see if the migration has been applied to the database.
+
+
+ Generates a SQL script from migrations.
+
+
+ The ending migration. Defaults to the last migration.
+
+
+ Missing required argument '{arg}'.
+
+
+ Missing required option '--{option}'.
+
+
+ Don't use app domains. Always implied on .NET Core.
+
+
+ Don't colorize output.
+
+
+ No DbContext was found.
+
+
+ No migrations were found.
+
+
+ The file to write the result to.
+
+
+ The directory to put files in. Paths are relative to the project directory.
+
+
+ Prefix output with level.
+
+
+ The project directory. Defaults to the current directory.
+
+
+ The provider to use. (E.g. Microsoft.EntityFrameworkCore.SqlServer)
+
+
+ The root namespace. Defaults to the target assembly name.
+
+
+ The schemas of tables to generate entity types for.
+
+
+ The startup assembly to use. Defaults to the target assembly.
+
+
+ The tables to generate entity types for.
+
+
+ Using application base '{appBase}'.
+
+
+ Using assembly '{assembly}'.
+
+
+ Using configuration file '{config}'.
+
+
+ Using content root '{contentRoot}'.
+
+
+ Using data directory '{dataDir}'.
+
+
+ Using project directory '{projectDir}'.
+
+
+ Using root namespace '{rootNamespace}'.
+
+
+ Using startup assembly '{startupAssembly}'.
+
+
+ Show verbose output.
+
+
+ Writing '{file}'...
+
+
\ No newline at end of file
diff --git a/src/ef/ReflectionOperationExecutor.cs b/src/ef/ReflectionOperationExecutor.cs
new file mode 100644
index 0000000000..e29f7dc7db
--- /dev/null
+++ b/src/ef/ReflectionOperationExecutor.cs
@@ -0,0 +1,100 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Reflection;
+
+#if NET451
+using System.IO;
+#endif
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal class ReflectionOperationExecutor : OperationExecutorBase
+ {
+ private readonly object _executor;
+ private readonly Assembly _commandsAssembly;
+ private const string ReportHandlerTypeName = "Microsoft.EntityFrameworkCore.Design.OperationReportHandler";
+ private const string ResultHandlerTypeName = "Microsoft.EntityFrameworkCore.Design.OperationResultHandler";
+ private readonly Type _resultHandlerType;
+
+ public ReflectionOperationExecutor(
+ string assembly,
+ string startupAssembly,
+ string projectDir,
+ string contentRootPath,
+ string dataDirectory,
+ string rootNamespace,
+ string environment)
+ : base(assembly, startupAssembly, projectDir, contentRootPath, dataDirectory, rootNamespace, environment)
+ {
+#if NET451
+ AppDomain.CurrentDomain.AssemblyResolve += ResolveAssembly;
+#endif
+
+ _commandsAssembly = Assembly.Load(new AssemblyName { Name = DesignAssemblyName });
+ var reportHandlerType = _commandsAssembly.GetType(ReportHandlerTypeName, throwOnError: true, ignoreCase: false);
+
+ var reportHandler = Activator.CreateInstance(
+ reportHandlerType,
+ (Action)Reporter.WriteError,
+ (Action)Reporter.WriteWarning,
+ (Action)Reporter.WriteInformation,
+ (Action)Reporter.WriteVerbose);
+
+ _executor = Activator.CreateInstance(
+ _commandsAssembly.GetType(ExecutorTypeName, throwOnError: true, ignoreCase: false),
+ reportHandler,
+ new Dictionary
+ {
+ { "targetName", AssemblyFileName },
+ { "startupTargetName", StartupAssemblyFileName },
+ { "projectDir", ProjectDirectory },
+ { "contentRootPath", ContentRootPath },
+ { "rootNamespace", RootNamespace },
+ { "environment", EnvironmentName }
+ });
+
+ _resultHandlerType = _commandsAssembly.GetType(ResultHandlerTypeName, throwOnError: true, ignoreCase: false);
+ }
+
+ protected override object CreateResultHandler()
+ => Activator.CreateInstance(_resultHandlerType);
+
+ protected override void Execute(string operationName, object resultHandler, IDictionary arguments)
+ => Activator.CreateInstance(
+ _commandsAssembly.GetType(ExecutorTypeName + "+" + operationName, throwOnError: true, ignoreCase: true),
+ _executor,
+ resultHandler,
+ arguments);
+
+#if NET451
+ private Assembly ResolveAssembly(object sender, ResolveEventArgs args)
+ {
+ var assemblyName = new AssemblyName(args.Name);
+
+ foreach (var extension in new[] { ".dll", ".exe" })
+ {
+ var path = Path.Combine(AppBasePath, assemblyName.Name + extension);
+ if (File.Exists(path))
+ {
+ try
+ {
+ return Assembly.LoadFrom(path);
+ }
+ catch
+ {
+ }
+ }
+ }
+
+ return null;
+ }
+
+ public override void Dispose()
+ => AppDomain.CurrentDomain.AssemblyResolve -= ResolveAssembly;
+#endif
+ }
+}
diff --git a/src/ef/Reporter.cs b/src/ef/Reporter.cs
new file mode 100644
index 0000000000..d433e9b69f
--- /dev/null
+++ b/src/ef/Reporter.cs
@@ -0,0 +1,58 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Linq;
+using static Microsoft.EntityFrameworkCore.Tools.AnsiConstants;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ internal static class Reporter
+ {
+ public static bool IsVerbose { get; set; }
+ public static bool NoColor { get; set; }
+ public static bool PrefixOutput { get; set; }
+
+ public static string Colorize(string value, Func colorizeFunc)
+ => NoColor ? value : colorizeFunc(value);
+
+ public static void WriteError(string message)
+ => WriteLine(Prefix("error: ", Colorize(message, x => Bold + Red + x + Reset)));
+
+ public static void WriteWarning(string message)
+ => WriteLine(Prefix("warn: ", Colorize(message, x => Bold + Yellow + x + Reset)));
+
+ public static void WriteInformation(string message)
+ => WriteLine(Prefix("info: ", message));
+
+ public static void WriteData(string message)
+ => WriteLine(Prefix("data: ", Colorize(message, x => Bold + Gray + x + Reset)));
+
+ public static void WriteVerbose(string message)
+ {
+ if (IsVerbose)
+ {
+ WriteLine(Prefix("verbose: ", Colorize(message, x => Bold + Black + x + Reset)));
+ }
+ }
+
+ private static string Prefix(string prefix, string value)
+ => PrefixOutput
+ ? string.Join(
+ Environment.NewLine,
+ value.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Select(l => prefix + l))
+ : value;
+
+ private static void WriteLine(string value)
+ {
+ if (NoColor)
+ {
+ Console.WriteLine(value);
+ }
+ else
+ {
+ AnsiConsole.WriteLine(value);
+ }
+ }
+ }
+}
diff --git a/src/ef/WrappedException.cs b/src/ef/WrappedException.cs
new file mode 100644
index 0000000000..55b4012208
--- /dev/null
+++ b/src/ef/WrappedException.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ public class WrappedException : Exception
+ {
+ private readonly string _stackTrace;
+
+ public WrappedException(string type, string message, string stackTrace)
+ : base(message)
+ {
+ Type = type;
+ _stackTrace = stackTrace;
+ }
+
+ public string Type { get; }
+
+ public override string ToString()
+ => _stackTrace;
+ }
+}
diff --git a/src/ef/ef.csproj b/src/ef/ef.csproj
new file mode 100644
index 0000000000..b782d63c31
--- /dev/null
+++ b/src/ef/ef.csproj
@@ -0,0 +1,50 @@
+
+
+
+
+
+ netcoreapp1.0;net451
+ Entity Framework Core Command Line Tools
+ Exe
+ ef.x86
+ false
+ Microsoft.EntityFrameworkCore.Tools
+ 1.0.0
+
+
+
+
+
+
+
+
+
+
+
+
+ TextTemplatingFileGenerator
+ Resources.Designer.cs
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.Designer.tt
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/dotnet-ef.Tests/CommandExceptionTest.cs b/test/dotnet-ef.Tests/CommandExceptionTest.cs
new file mode 100644
index 0000000000..c8c4ba125f
--- /dev/null
+++ b/test/dotnet-ef.Tests/CommandExceptionTest.cs
@@ -0,0 +1,18 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ public class CommandExceptionTest
+ {
+ [Fact]
+ public void Ctor_works()
+ {
+ var ex = new CommandException("Message1");
+
+ Assert.Equal("Message1", ex.Message);
+ }
+ }
+}
diff --git a/test/dotnet-ef.Tests/CommandsTest.cs b/test/dotnet-ef.Tests/CommandsTest.cs
new file mode 100644
index 0000000000..bdcbfbe17a
--- /dev/null
+++ b/test/dotnet-ef.Tests/CommandsTest.cs
@@ -0,0 +1,74 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.DotNet.Cli.CommandLine;
+using Xunit;
+
+using EFCommand = Microsoft.EntityFrameworkCore.Tools.Commands.RootCommand;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ public class CommandsTest
+ {
+ [Fact]
+ public void Short_names_are_unique()
+ {
+ foreach (var command in GetCommands())
+ {
+ foreach (var group in command.Options.GroupBy(o => o.ShortName))
+ {
+ Assert.True(
+ group.Key == null || group.Count() == 1,
+ "Duplicate short names on command '" + GetFullName(command) + "': " +
+ string.Join("; ", group.Select(o => o.Template)));
+ }
+ }
+ }
+
+ private static IEnumerable GetCommands()
+ {
+ var app = new CommandLineApplication()
+ {
+ Name = "dotnet ef"
+ };
+
+ new EFCommand().Configure(app);
+
+ return GetCommands(app);
+ }
+
+ private static IEnumerable GetCommands(CommandLineApplication command)
+ {
+ var commands = new Stack();
+ commands.Push(command);
+
+ while (commands.Count != 0)
+ {
+ command = commands.Pop();
+
+ yield return command;
+
+ foreach (var subcommand in command.Commands)
+ {
+ commands.Push(subcommand);
+ }
+ }
+ }
+
+ private static string GetFullName(CommandLineApplication command)
+ {
+ var names = new Stack();
+
+ while (command != null)
+ {
+ names.Push(command.Name);
+
+ command = command.Parent;
+ }
+
+ return string.Join(" ", names);
+ }
+ }
+}
diff --git a/test/dotnet-ef.Tests/ExeTest.cs b/test/dotnet-ef.Tests/ExeTest.cs
new file mode 100644
index 0000000000..f39700455e
--- /dev/null
+++ b/test/dotnet-ef.Tests/ExeTest.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Reflection;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ public class ExeTest
+ {
+ [Fact]
+ public void ToArguments_works()
+ {
+ var result = ToArguments(
+ new[] {
+ "Good",
+ "Good\\",
+ "Needs quotes",
+ "Needs escaping\\",
+ "Needs escaping\\\\",
+ "Needs \"escaping\"",
+ "Needs \\\"escaping\"",
+ "Needs escaping\\\\too"
+ });
+
+ Assert.Equal(
+ "Good " +
+ "Good\\ " +
+ "\"Needs quotes\" " +
+ "\"Needs escaping\\\\\" " +
+ "\"Needs escaping\\\\\\\\\" " +
+ "\"Needs \\\"escaping\\\"\" " +
+ "\"Needs \\\\\\\"escaping\\\"\" " +
+ "\"Needs escaping\\\\\\\\too\"",
+ result);
+ }
+
+ private static string ToArguments(IReadOnlyList args)
+ => (string)typeof(Exe).GetTypeInfo().GetMethod("ToArguments", BindingFlags.Static | BindingFlags.NonPublic)
+ .Invoke(null, new object[] { args });
+ }
+}
diff --git a/test/dotnet-ef.Tests/dotnet-ef.Tests.csproj b/test/dotnet-ef.Tests/dotnet-ef.Tests.csproj
new file mode 100644
index 0000000000..952ff1a646
--- /dev/null
+++ b/test/dotnet-ef.Tests/dotnet-ef.Tests.csproj
@@ -0,0 +1,18 @@
+
+
+
+
+
+ netcoreapp1.0
+ Microsoft.EntityFrameworkCore.Tools
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/ef.Tests/AppDomainOperationExecutorTest.cs b/test/ef.Tests/AppDomainOperationExecutorTest.cs
new file mode 100644
index 0000000000..90afd05678
--- /dev/null
+++ b/test/ef.Tests/AppDomainOperationExecutorTest.cs
@@ -0,0 +1,363 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#if NET452
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using Microsoft.EntityFrameworkCore.Internal;
+using Microsoft.EntityFrameworkCore.Relational.Design.Specification.Tests.TestUtilities;
+using Microsoft.EntityFrameworkCore.Tools.TestUtilities;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ [Collection("OperationExecutorTests")]
+ public class AppDomainOperationExecutorTest
+ {
+ private IOperationExecutor CreateExecutorFromBuildResult(BuildFileResult build, string rootNamespace = null)
+ => new AppDomainOperationExecutor(build.TargetPath,
+ build.TargetPath,
+ build.TargetDir,
+ build.TargetDir,
+ build.TargetDir,
+ rootNamespace,
+ environment: null);
+
+ [Fact]
+ public void Assembly_load_errors_are_wrapped()
+ {
+ var targetDir = AppDomain.CurrentDomain.BaseDirectory;
+ using (var executor = new AppDomainOperationExecutor(Assembly.GetExecutingAssembly().Location, Path.Combine(targetDir, "Unknown.dll"), targetDir, null, null, null, null))
+ {
+ Assert.Throws(() => executor.GetContextTypes());
+ }
+ }
+
+ [Fact]
+ public void GetMigrations_filters_by_context_name()
+ {
+ using (var directory = new TempDirectory())
+ {
+ var targetDir = directory.Path;
+ var source = new BuildSource
+ {
+ TargetDir = targetDir,
+ References =
+ {
+ BuildReference.ByName("System.Diagnostics.DiagnosticSource", true),
+ BuildReference.ByName("System.Interactive.Async", true),
+ BuildReference.ByName("System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
+ BuildReference.ByName("Microsoft.AspNetCore.Hosting.Abstractions", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Design", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational.Design", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Memory", true),
+ BuildReference.ByName("Microsoft.Extensions.Configuration.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.FileProviders.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Options", true),
+ BuildReference.ByName("Remotion.Linq", true)
+ },
+ Sources = { @"
+ using Microsoft.EntityFrameworkCore;
+ using Microsoft.EntityFrameworkCore.Infrastructure;
+ using Microsoft.EntityFrameworkCore.Migrations;
+
+ namespace MyProject
+ {
+ internal class Context1 : DbContext
+ {
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.UseSqlServer(""Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SimpleProject.SimpleContext;Integrated Security=True"");
+ }
+ }
+
+ internal class Context2 : DbContext
+ {
+ }
+
+ namespace Migrations
+ {
+ namespace Context1Migrations
+ {
+ [DbContext(typeof(Context1))]
+ [Migration(""000000000000000_Context1Migration"")]
+ public class Context1Migration : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ }
+ }
+ }
+
+ namespace Context2Migrations
+ {
+ [DbContext(typeof(Context2))]
+ [Migration(""000000000000000_Context2Migration"")]
+ public class Context2Migration : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ }
+ }
+ }
+ }
+ }" }
+ };
+
+ var build = source.Build();
+ using (var executor = CreateExecutorFromBuildResult(build, "MyProject"))
+ {
+ var migrations = executor.GetMigrations("Context1");
+
+ Assert.Equal(1, migrations.Count());
+ }
+ }
+ }
+
+ [Fact]
+ public void GetContextType_works_with_multiple_assemblies()
+ {
+ using (var directory = new TempDirectory())
+ {
+ var targetDir = directory.Path;
+ var contextsSource = new BuildSource
+ {
+ TargetDir = targetDir,
+ References =
+ {
+ BuildReference.ByName("Microsoft.EntityFrameworkCore", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Design", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging.Abstractions", true)
+ },
+ Sources = { @"
+ using Microsoft.EntityFrameworkCore;
+
+ namespace MyProject
+ {
+ public class Context1 : DbContext
+ {
+ }
+
+ public class Context2 : DbContext
+ {
+ }
+ }" }
+ };
+ var contextsBuild = contextsSource.Build();
+ var migrationsSource = new BuildSource
+ {
+ TargetDir = targetDir,
+ References =
+ {
+ BuildReference.ByName("System.Reflection.Metadata", true),
+ BuildReference.ByName("Microsoft.AspNetCore.Hosting.Abstractions", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore"),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational.Design", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Configuration.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.FileProviders.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Options", true),
+ BuildReference.ByPath(contextsBuild.TargetPath)
+ },
+ Sources = { @"
+ using Microsoft.EntityFrameworkCore;
+ using Microsoft.EntityFrameworkCore.Infrastructure;
+ using Microsoft.EntityFrameworkCore.Migrations;
+
+ namespace MyProject
+ {
+ internal class Context3 : DbContext
+ {
+ }
+
+ namespace Migrations
+ {
+ namespace Context1Migrations
+ {
+ [DbContext(typeof(Context1))]
+ [Migration(""000000000000000_Context1Migration"")]
+ public class Context1Migration : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ }
+ }
+ }
+
+ namespace Context2Migrations
+ {
+ [DbContext(typeof(Context2))]
+ [Migration(""000000000000000_Context2Migration"")]
+ public class Context2Migration : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ }
+ }
+ }
+ }
+ }" }
+ };
+ var build = migrationsSource.Build();
+ using (var executor = CreateExecutorFromBuildResult(build, "MyProject"))
+ {
+ var contextTypes = executor.GetContextTypes();
+
+ Assert.Equal(3, contextTypes.Count());
+ }
+ }
+ }
+
+ [Fact]
+ public void AddMigration_begins_new_namespace_when_foreign_migrations()
+ {
+ using (var directory = new TempDirectory())
+ {
+ var targetDir = directory.Path;
+ var source = new BuildSource
+ {
+ TargetDir = targetDir,
+ References =
+ {
+ BuildReference.ByName("System.Diagnostics.DiagnosticSource", true),
+ BuildReference.ByName("System.Interactive.Async", true),
+ BuildReference.ByName("System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
+ BuildReference.ByName("Microsoft.AspNetCore.Hosting.Abstractions", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Design", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational.Design", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Memory", true),
+ BuildReference.ByName("Microsoft.Extensions.Configuration.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.FileProviders.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Options", true),
+ BuildReference.ByName("Remotion.Linq", true)
+ },
+ Sources = { @"
+ using Microsoft.EntityFrameworkCore;
+ using Microsoft.EntityFrameworkCore.Infrastructure;
+ using Microsoft.EntityFrameworkCore.Migrations;
+
+ namespace MyProject
+ {
+ internal class MyFirstContext : DbContext
+ {
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.UseSqlServer(""Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=MyProject.MyFirstContext"");
+ }
+ }
+
+ internal class MySecondContext : DbContext
+ {
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.UseSqlServer(""Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=MyProject.MySecondContext"");
+ }
+ }
+
+ namespace Migrations
+ {
+ [DbContext(typeof(MyFirstContext))]
+ [Migration(""20151006140723_InitialCreate"")]
+ public class InitialCreate : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ }
+ }
+ }
+ }" }
+ };
+ var build = source.Build();
+ using (var executor = CreateExecutorFromBuildResult(build, "MyProject"))
+ {
+ var artifacts = executor.AddMigration("MyMigration", /*outputDir:*/ null, "MySecondContext");
+ Assert.Equal(3, artifacts.Keys.Count);
+ Assert.True(Directory.Exists(Path.Combine(targetDir, @"Migrations\MySecond")));
+ }
+ }
+ }
+
+ [Fact]
+ public void Throws_for_no_parameterless_constructor()
+ {
+ using (var directory = new TempDirectory())
+ {
+ var targetDir = directory.Path;
+ var source = new BuildSource
+ {
+ TargetDir = targetDir,
+ References =
+ {
+ BuildReference.ByName("System.Diagnostics.DiagnosticSource", true),
+ BuildReference.ByName("System.Interactive.Async", true),
+ BuildReference.ByName("System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
+ BuildReference.ByName("Microsoft.AspNetCore.Hosting.Abstractions", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Design", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational.Design", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Memory", true),
+ BuildReference.ByName("Microsoft.Extensions.Configuration.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.FileProviders.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Options", true),
+ BuildReference.ByName("Remotion.Linq", true)
+ },
+ Sources = { @"
+ using Microsoft.EntityFrameworkCore;
+ using Microsoft.EntityFrameworkCore.Infrastructure;
+ using Microsoft.EntityFrameworkCore.Migrations;
+
+ namespace MyProject
+ {
+ internal class MyContext : DbContext
+ {
+ public MyContext(DbContextOptions options) :base(options) {}
+ }
+ }" }
+ };
+ var build = source.Build();
+ using (var executor = CreateExecutorFromBuildResult(build, "MyProject"))
+ {
+ var ex = Assert.Throws(
+ () => executor.GetMigrations("MyContext"));
+
+ Assert.Equal(
+ DesignStrings.NoParameterlessConstructor("MyContext"),
+ ex.Message);
+ }
+ }
+ }
+ }
+}
+#endif
diff --git a/test/ef.Tests/CommandsTest.cs b/test/ef.Tests/CommandsTest.cs
new file mode 100644
index 0000000000..43feb7d0e9
--- /dev/null
+++ b/test/ef.Tests/CommandsTest.cs
@@ -0,0 +1,73 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.DotNet.Cli.CommandLine;
+using Microsoft.EntityFrameworkCore.Tools.Commands;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ public class CommandsTest
+ {
+ [Fact]
+ public void Short_names_are_unique()
+ {
+ foreach (var command in GetCommands())
+ {
+ foreach (var group in command.Options.GroupBy(o => o.ShortName))
+ {
+ Assert.True(
+ group.Key == null || group.Count() == 1,
+ "Duplicate short names on command '" + GetFullName(command) + "': " +
+ string.Join("; ", group.Select(o => o.Template)));
+ }
+ }
+ }
+
+ private static IEnumerable GetCommands()
+ {
+ var app = new CommandLineApplication()
+ {
+ Name = "ef"
+ };
+
+ new RootCommand().Configure(app);
+
+ return GetCommands(app);
+ }
+
+ private static IEnumerable GetCommands(CommandLineApplication command)
+ {
+ var commands = new Stack();
+ commands.Push(command);
+
+ while (commands.Count != 0)
+ {
+ command = commands.Pop();
+
+ yield return command;
+
+ foreach (var subcommand in command.Commands)
+ {
+ commands.Push(subcommand);
+ }
+ }
+ }
+
+ private static string GetFullName(CommandLineApplication command)
+ {
+ var names = new Stack();
+
+ while (command != null)
+ {
+ names.Push(command.Name);
+
+ command = command.Parent;
+ }
+
+ return string.Join(" ", names);
+ }
+ }
+}
diff --git a/test/ef.Tests/SimpleProjectTest.cs b/test/ef.Tests/SimpleProjectTest.cs
new file mode 100644
index 0000000000..6222e3b31c
--- /dev/null
+++ b/test/ef.Tests/SimpleProjectTest.cs
@@ -0,0 +1,208 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+#if NET452
+
+using System;
+using System.Collections;
+using System.IO;
+using System.Linq;
+using Microsoft.EntityFrameworkCore.Relational.Design.Specification.Tests.TestUtilities;
+using Microsoft.EntityFrameworkCore.Tools.TestUtilities;
+using Xunit;
+
+namespace Microsoft.EntityFrameworkCore.Tools
+{
+ [Collection("OperationExecutorTests")]
+ public class SimpleProjectTest : IClassFixture
+ {
+ private readonly SimpleProject _project;
+
+ public SimpleProjectTest(SimpleProject project)
+ {
+ _project = project;
+ }
+
+ private void AssertDefaultMigrationName(IDictionary artifacts)
+ => Assert.Contains("namespace SimpleProject.Migrations", File.ReadAllText(artifacts["MigrationFile"] as string));
+
+ [Fact]
+ public void AddMigration()
+ {
+ var artifacts = _project.Executor.AddMigration("EmptyMigration", "CustomFolder", "SimpleContext");
+ Assert.NotNull(artifacts);
+ Assert.NotNull(artifacts["MigrationFile"]);
+ Assert.NotNull(artifacts["MetadataFile"]);
+ Assert.NotNull(artifacts["SnapshotFile"]);
+ Assert.True(Directory.Exists(Path.Combine(_project.TargetDir, "CustomFolder")));
+ Assert.Contains("namespace SimpleProject.CustomFolder", File.ReadAllText(artifacts["MigrationFile"] as string));
+ }
+
+ [Fact]
+ public void AddMigration_output_dir_relative_to_projectdir()
+ {
+ var artifacts = _project.Executor.AddMigration("EmptyMigration1", "./CustomFolder", "SimpleContext");
+ Assert.NotNull(artifacts);
+ Assert.StartsWith(Path.Combine(_project.TargetDir, "CustomFolder"), artifacts["MigrationFile"] as string);
+ Assert.Contains("namespace SimpleProject.CustomFolder", File.ReadAllText(artifacts["MigrationFile"] as string));
+ }
+
+ [Fact]
+ public void AddMigration_output_dir_relative_out_of_to_projectdir()
+ {
+ var artifacts = _project.Executor.AddMigration("EmptyMigration1", "../CustomFolder", "SimpleContext");
+ Assert.NotNull(artifacts);
+ Assert.StartsWith(Path.GetFullPath(Path.Combine(_project.TargetDir, "../CustomFolder")), artifacts["MigrationFile"] as string);
+ AssertDefaultMigrationName(artifacts);
+ }
+
+ [Fact]
+ public void AddMigration_output_dir_absolute_path_in_project()
+ {
+ var outputDir = Path.Combine(_project.TargetDir, "A/B/C");
+ var artifacts = _project.Executor.AddMigration("EmptyMigration1", outputDir, "SimpleContext");
+ Assert.NotNull(artifacts);
+ Assert.Equal(Path.Combine(outputDir, Path.GetFileName(artifacts["MigrationFile"] as string)), artifacts["MigrationFile"]);
+ Assert.Contains("namespace SimpleProject.A.B.C", File.ReadAllText(artifacts["MigrationFile"] as string));
+ }
+
+ [Fact]
+ public void AddMigration_output_dir_absolute_path_outside_project()
+ {
+ var outputDir = Path.GetTempPath();
+ var artifacts = _project.Executor.AddMigration("EmptyMigration1", outputDir, "SimpleContext");
+ Assert.NotNull(artifacts);
+ Assert.StartsWith(outputDir, artifacts["MigrationFile"] as string);
+ AssertDefaultMigrationName(artifacts);
+ }
+
+ [Theory]
+ [InlineData("")]
+ [InlineData(" ")]
+ [InlineData(null)]
+ public void AddMigration_handles_empty_output_dir(string outputDir)
+ {
+ var artifacts = _project.Executor.AddMigration("EmptyMigration2", outputDir, "SimpleContext");
+ Assert.NotNull(artifacts);
+ Assert.StartsWith(Path.Combine(_project.TargetDir, "Migrations"), artifacts["MigrationFile"] as string);
+ AssertDefaultMigrationName(artifacts);
+ }
+
+ [Fact]
+ public void ScriptMigration()
+ {
+ var sql = _project.Executor.ScriptMigration(null, "InitialCreate", false, "SimpleContext");
+ Assert.NotEmpty(sql);
+ }
+
+ [Fact]
+ public void GetContextType()
+ {
+ var contextTypeName = _project.Executor.GetContextType("SimpleContext");
+ Assert.StartsWith("SimpleProject.SimpleContext, ", contextTypeName);
+ }
+
+ [Fact]
+ public void GetContextTypes()
+ {
+ var contextTypes = _project.Executor.GetContextTypes();
+ Assert.Equal(1, contextTypes.Count());
+ }
+
+ [Fact]
+ public void GetMigrations()
+ {
+ var migrations = _project.Executor.GetMigrations("SimpleContext");
+ Assert.Equal(1, migrations.Count());
+ }
+
+ [Fact]
+ public void GetContextInfo_returns_connection_string()
+ {
+ var info = _project.Executor.GetContextInfo("SimpleContext");
+ Assert.Equal(@"(localdb)\MSSQLLocalDB", info["DataSource"]);
+ Assert.Equal("SimpleProject.SimpleContext", info["DatabaseName"]);
+ }
+
+ public class SimpleProject : IDisposable
+ {
+ private readonly TempDirectory _directory = new TempDirectory();
+
+ public SimpleProject()
+ {
+ var source = new BuildSource
+ {
+ TargetDir = TargetDir,
+ References =
+ {
+ BuildReference.ByName("System.Diagnostics.DiagnosticSource", true),
+ BuildReference.ByName("System.Interactive.Async", true),
+ BuildReference.ByName("System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"),
+ BuildReference.ByName("Microsoft.AspNetCore.Hosting.Abstractions", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Design", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.Relational.Design", true),
+ BuildReference.ByName("Microsoft.EntityFrameworkCore.SqlServer", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Caching.Memory", true),
+ BuildReference.ByName("Microsoft.Extensions.Configuration.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection", true),
+ BuildReference.ByName("Microsoft.Extensions.DependencyInjection.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.FileProviders.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging", true),
+ BuildReference.ByName("Microsoft.Extensions.Logging.Abstractions", true),
+ BuildReference.ByName("Microsoft.Extensions.Options", true),
+ BuildReference.ByName("Remotion.Linq", true)
+ },
+ Sources = { @"
+ using Microsoft.EntityFrameworkCore;
+ using Microsoft.EntityFrameworkCore.Infrastructure;
+ using Microsoft.EntityFrameworkCore.Migrations;
+
+ namespace SimpleProject
+ {
+ internal class SimpleContext : DbContext
+ {
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ optionsBuilder.UseSqlServer(""Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=SimpleProject.SimpleContext;Integrated Security=True"");
+ }
+ }
+
+ namespace Migrations
+ {
+ [DbContext(typeof(SimpleContext))]
+ [Migration(""20141010222726_InitialCreate"")]
+ public class InitialCreate : Migration
+ {
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ }
+ }
+ }
+ }" }
+ };
+ var build = source.Build();
+ Executor = new AppDomainOperationExecutor(build.TargetPath,
+ build.TargetPath,
+ build.TargetDir,
+ build.TargetDir,
+ build.TargetDir,
+ "SimpleProject",
+ null);
+ }
+
+ public string TargetDir => _directory.Path;
+
+ internal IOperationExecutor Executor { get; }
+
+ public void Dispose()
+ {
+ Executor.Dispose();
+ _directory.Dispose();
+ }
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/test/ef.Tests/TestUtilities/TempDirectory.cs b/test/ef.Tests/TestUtilities/TempDirectory.cs
new file mode 100644
index 0000000000..51a10ba2f8
--- /dev/null
+++ b/test/ef.Tests/TestUtilities/TempDirectory.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.IO;
+using IOPath = System.IO.Path;
+
+namespace Microsoft.EntityFrameworkCore.Tools.TestUtilities
+{
+ public class TempDirectory : IDisposable
+ {
+ public TempDirectory()
+ {
+ Path = IOPath.Combine(IOPath.GetTempPath(), IOPath.GetRandomFileName());
+ Directory.CreateDirectory(Path);
+ }
+
+ public string Path { get; }
+
+ public void Dispose()
+ => Directory.Delete(Path, recursive: true);
+ }
+}
diff --git a/test/ef.Tests/ef.Tests.csproj b/test/ef.Tests/ef.Tests.csproj
new file mode 100644
index 0000000000..123880e94b
--- /dev/null
+++ b/test/ef.Tests/ef.Tests.csproj
@@ -0,0 +1,33 @@
+
+
+
+
+
+ net452;netcoreapp1.1
+ netcoreapp1.1
+ Microsoft.EntityFrameworkCore.Tools
+
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/Resources.tt b/tools/Resources.tt
index 352ca302d7..255ade2bc6 100644
--- a/tools/Resources.tt
+++ b/tools/Resources.tt
@@ -10,7 +10,7 @@
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ import namespace="EnvDTE" #>
<#
- var model = LoadResources((string)Session["ResourceFile"]);
+ var model = LoadResources();
#>
//
@@ -24,7 +24,7 @@ namespace <#= model.Namespace #>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
///
- public static class <#= model.Class #>
+ <#= model.AccessModifier #> static class <#= model.Class #>
{
private static readonly ResourceManager _resourceManager
= new ResourceManager("<#= model.ResourceName #>", typeof(<#= model.Class #>).GetTypeInfo().Assembly);
@@ -76,13 +76,19 @@ namespace <#= model.Namespace #>
}
}
<#+
- ResourceFile LoadResources(string resourceFile)
+ ResourceFile LoadResources()
{
var result = new ResourceFile();
+ if (Session.ContainsKey("AccessModifier"))
+ {
+ result.AccessModifier = (string)Session["AccessModifier"];
+ };
+
var services = (IServiceProvider)Host;
var dte = (DTE)services.GetService(typeof(DTE));
+ var resourceFile = (string)Session["ResourceFile"];
if (!Path.IsPathRooted(resourceFile))
{
resourceFile = Host.ResolvePath(resourceFile);
@@ -139,6 +145,7 @@ namespace <#= model.Namespace #>
class ResourceFile
{
public string Namespace { get; set; }
+ public string AccessModifier { get; set; } = "public";
public string Class { get; set; }
public string ResourceName { get; set; }
public IEnumerable Resources { get; set; }