Skip to content

Commit

Permalink
Tweak PR #10356
Browse files Browse the repository at this point in the history
  • Loading branch information
bricelam committed Jan 19, 2018
1 parent b271241 commit 1e5756a
Show file tree
Hide file tree
Showing 15 changed files with 73 additions and 257 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.EntityFrameworkCore.Scaffolding.Internal;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Storage.Converters;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -51,6 +52,7 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices(
.AddSingleton<ModelCodeGeneratorDependencies>()
.AddSingleton<ProviderCodeGeneratorDependencies>()
.AddSingleton<RelationalTypeMapperDependencies>()
.AddSingleton<ValueConverterSelectorDependencies>()
.AddSingleton<ICandidateNamingService, CandidateNamingService>()
.AddSingleton<ICSharpDbContextGenerator, CSharpDbContextGenerator>()
.AddSingleton<ICSharpEntityTypeGenerator, CSharpEntityTypeGenerator>()
Expand All @@ -70,6 +72,7 @@ public static IServiceCollection AddEntityFrameworkDesignTimeServices(
.AddSingleton<IReverseEngineerScaffolder, ReverseEngineerScaffolder>()
.AddSingleton<IScaffoldingModelFactory, RelationalScaffoldingModelFactory>()
.AddSingleton<IScaffoldingTypeMapper, ScaffoldingTypeMapper>()
.AddSingleton<IValueConverterSelector, ValueConverterSelector>()
.AddTransient<MigrationsScaffolderDependencies>()
.AddTransient<IMigrationsScaffolder, MigrationsScaffolder>()
.AddTransient<ISnapshotModelProcessor, SnapshotModelProcessor>()
Expand Down
34 changes: 33 additions & 1 deletion src/EFCore.Design/Design/Internal/DatabaseOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,21 @@ public virtual ModelFiles ScaffoldContext(
@namespace += "." + subNamespace;
}

var absoluteOutputDir = outputDir != null
? Path.GetFullPath(Path.Combine(_projectDir, outputDir))
: _projectDir;
var absoluteContextDir = outputContextDir != null
? Path.GetFullPath(Path.Combine(_projectDir, outputContextDir))
: absoluteOutputDir;
var relativeContextDir = MakeDirRelative(absoluteOutputDir, absoluteContextDir);

var scaffoldedModel = scaffolder.ScaffoldModel(
connectionString,
tables,
schemas,
@namespace,
_language,
relativeContextDir,
dbContextClassName,
useDataAnnotations,
useDatabaseNames);
Expand All @@ -96,7 +105,6 @@ public virtual ModelFiles ScaffoldContext(
scaffoldedModel,
_projectDir,
outputDir,
outputContextDir,
overwriteFiles);
}

Expand Down Expand Up @@ -126,5 +134,29 @@ private string SubnamespaceFromOutputPath(string projectDir, string outputDir)
? string.Join(".", subPath.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries))
: null;
}

private static string MakeDirRelative(string root, string path)
{
var relativeUri = new Uri(NormalizeDir(root)).MakeRelativeUri(new Uri(NormalizeDir(path)));

return Uri.UnescapeDataString(relativeUri.ToString()).Replace('/', Path.DirectorySeparatorChar);
}

private static string NormalizeDir(string path)
{
if (string.IsNullOrEmpty(path))
{
return path;
}

var last = path[path.Length - 1];
if (last == Path.DirectorySeparatorChar
|| last == Path.AltDirectorySeparatorChar)
{
return path;
}

return path + Path.DirectorySeparatorChar;
}
}
}
2 changes: 2 additions & 0 deletions src/EFCore.Design/Scaffolding/IModelCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ public interface IModelCodeGenerator : ILanguageBasedService
/// </summary>
/// <param name="model"> The model. </param>
/// <param name="namespace"> The namespace. </param>
/// <param name="contextDir"> The directory of the <see cref="DbContext"/>. </param>
/// <param name="contextName"> The name of the <see cref="DbContext"/>. </param>
/// <param name="connectionString"> The connection string. </param>
/// <param name="dataAnnotations"> A value indicating whether to use data annotations. </param>
/// <returns> The generated model. </returns>
ScaffoldedModel GenerateModel(
[NotNull] IModel model,
[NotNull] string @namespace,
[NotNull] string contextDir,
[NotNull] string contextName,
[NotNull] string connectionString,
bool dataAnnotations);
Expand Down
4 changes: 2 additions & 2 deletions src/EFCore.Design/Scaffolding/IReverseEngineerScaffolder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public interface IReverseEngineerScaffolder
/// <param name="schemas"> A list of schemas to include. Empty to include all schemas. </param>
/// <param name="namespace"> The namespace of the model. </param>
/// <param name="language"> The programming language to scaffold for. </param>
/// <param name="outputDbContextDir"> The DbContext output dirctory. </param>
/// <param name="contextName"> The <see cref="DbContext"/> name. </param>
/// <param name="useDataAnnotations"> True to scaffold data annotations. </param>
/// <param name="useDatabaseNames"> True to use the database schema names directly. </param>
Expand All @@ -29,6 +30,7 @@ ScaffoldedModel ScaffoldModel(
[NotNull] IEnumerable<string> schemas,
[NotNull] string @namespace,
[NotNull] string language,
[CanBeNull] string outputDbContextDir,
[CanBeNull] string contextName,
bool useDataAnnotations,
bool useDatabaseNames);
Expand All @@ -39,14 +41,12 @@ ScaffoldedModel ScaffoldModel(
/// <param name="scaffoldedModel"> The scaffolded model. </param>
/// <param name="projectDir"> The project directory. </param>
/// <param name="outputDir"> The output dirctory. </param>
/// <param name="outputDbContextDir"> The DbContext output dirctory. </param>
/// <param name="overwriteFiles"> True to overwrite any existing files. </param>
/// <returns> The model files. </returns>
ModelFiles Save(
[NotNull] ScaffoldedModel scaffoldedModel,
[NotNull] string projectDir,
[CanBeNull] string outputDir,
[CanBeNull] string outputDbContextDir,
bool overwriteFiles);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +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.IO;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
Expand Down Expand Up @@ -58,12 +59,14 @@ public CSharpModelGenerator(
public override ScaffoldedModel GenerateModel(
IModel model,
string @namespace,
string contextDir,
string contextName,
string connectionString,
bool useDataAnnotations)
{
Check.NotNull(model, nameof(model));
Check.NotEmpty(@namespace, nameof(@namespace));
Check.NotNull(contextDir, nameof(contextDir));
Check.NotEmpty(contextName, nameof(contextName));
Check.NotEmpty(connectionString, nameof(connectionString));

Expand All @@ -73,7 +76,7 @@ public override ScaffoldedModel GenerateModel(

// output DbContext .cs file
var dbContextFileName = contextName + FileExtension;
resultingFiles.ContextFile = new ScaffoldedFile { Path = dbContextFileName, Code = generatedCode };
resultingFiles.ContextFile = new ScaffoldedFile { Path = Path.Combine(contextDir, dbContextFileName), Code = generatedCode };

foreach (var entityType in model.GetEntityTypes())
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public virtual ScaffoldedModel ScaffoldModel(
IEnumerable<string> schemas,
string @namespace,
string language,
string outputDbContextDir,
string contextName,
bool useDataAnnotations,
bool useDatabaseNames)
Expand Down Expand Up @@ -107,7 +108,7 @@ public virtual ScaffoldedModel ScaffoldModel(

var codeGenerator = ModelCodeGeneratorSelector.Select(language);

return codeGenerator.GenerateModel(model, @namespace, contextName, connectionString, useDataAnnotations);
return codeGenerator.GenerateModel(model, @namespace, outputDbContextDir ?? string.Empty, contextName, connectionString, useDataAnnotations);
}

/// <summary>
Expand All @@ -118,7 +119,6 @@ public virtual ModelFiles Save(
ScaffoldedModel scaffoldedModel,
string projectDir,
string outputDir,
string outputDbContextDir,
bool overwriteFiles)
{
Check.NotEmpty(projectDir, nameof(projectDir));
Expand All @@ -127,26 +127,13 @@ public virtual ModelFiles Save(
? projectDir
: Path.GetFullPath(Path.Combine(projectDir, outputDir));

string outputDbContextDir1 = null;
if (outputDbContextDir == null)
{
outputDbContextDir1 = outputDir;
}
else
{
outputDbContextDir1 = Path.IsPathRooted(outputDbContextDir)
? outputDbContextDir
: Path.GetFullPath(Path.Combine(projectDir, outputDbContextDir));
}

CheckOutputFiles(scaffoldedModel, outputDir, overwriteFiles);
CheckOutputFiles(scaffoldedModel, outputDbContextDir1, overwriteFiles);

var files = new ModelFiles();
Directory.CreateDirectory(outputDir);
Directory.CreateDirectory(outputDbContextDir1);

var contextPath = Path.Combine(outputDbContextDir1, scaffoldedModel.ContextFile.Path);
var contextPath = Path.GetFullPath(Path.Combine(outputDir, scaffoldedModel.ContextFile.Path));
Directory.CreateDirectory(Path.GetDirectoryName(contextPath));
File.WriteAllText(contextPath, scaffoldedModel.ContextFile.Code, Encoding.UTF8);
files.ContextFile = contextPath;

Expand Down
2 changes: 2 additions & 0 deletions src/EFCore.Design/Scaffolding/ModelCodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ public ModelCodeGenerator([NotNull] ModelCodeGeneratorDependencies dependencies)
/// </summary>
/// <param name="model"> The model. </param>
/// <param name="namespace"> The namespace. </param>
/// <param name="contextDir"> The directory of the <see cref="DbContext"/>. </param>
/// <param name="contextName"> The name of the <see cref="DbContext"/>. </param>
/// <param name="connectionString"> The connection string. </param>
/// <param name="dataAnnotations"> A value indicating whether to use data annotations. </param>
/// <returns> The generated model. </returns>
public abstract ScaffoldedModel GenerateModel(
IModel model,
string @namespace,
string contextDir,
string contextName,
string connectionString,
bool dataAnnotations);
Expand Down
10 changes: 10 additions & 0 deletions src/EFCore.Tools/tools/EntityFrameworkCore.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ Register-TabExpansion Scaffold-DbContext @{
Project = { GetProjects }
StartupProject = { GetProjects }
OutputDir = { <# Disabled. Otherwise, paths would be relative to the solution directory. #> }
OutputDbContextDir = { <# Disabled. Otherwise, paths would be relative to the solution directory. #> }
}

<#
Expand All @@ -277,6 +278,9 @@ Register-TabExpansion Scaffold-DbContext @{
.PARAMETER OutputDir
The directory to put files in. Paths are relative to the project directory.
.PARAMETER OutputDbContextDir
The directory to put DbContext file in. Paths are relative to the project directory.
.PARAMETER Context
The name of the DbContext to generate.
Expand Down Expand Up @@ -313,6 +317,7 @@ function Scaffold-DbContext
[Parameter(Position = 1, Mandatory = $true)]
[string] $Provider,
[string] $OutputDir,
[string] $OutputDbContextDir,
[string] $Context,
[string[]] $Schemas = @(),
[string[]] $Tables = @(),
Expand All @@ -331,6 +336,11 @@ function Scaffold-DbContext
{
$params += '--output-dir', $OutputDir
}

if ($OutputDbContextDir)
{
$params += '--output-dbcontext-dir', $OutputDbContextDir
}

if ($Context)
{
Expand Down
3 changes: 3 additions & 0 deletions src/dotnet-ef/Properties/Resources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -294,4 +294,7 @@
<data name="MigrationsRemoveRevertDescription" xml:space="preserve">
<value>Revert the migration if it has been applied to the database.</value>
</data>
<data name="OutputDbContextDirDescription" xml:space="preserve">
<value>The directory to put DbContext file in. Paths are relative to the project directory.</value>
</data>
</root>
67 changes: 0 additions & 67 deletions test/EFCore.Design.Tests/Design/OperationExecutorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@
// 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.Data.SqlClient;
using System.IO;
using Microsoft.EntityFrameworkCore.TestUtilities;
using Xunit;

namespace Microsoft.EntityFrameworkCore.Design
Expand Down Expand Up @@ -63,69 +59,6 @@ public void Execute_enumerates_results()
Assert.Equal(new[] { "Twilight Sparkle", "Princess Celestia" }, handler.Result);
}

[Theory(Skip = "DatabaseOperations.ScaffoldContext throws exception")]
[InlineData("FakeOutputDir", null)]
[InlineData("FakeOutputDir", "FakeOutputDir")]
[InlineData("FakeOutputDir", "FakeContextOutputDir")]
[InlineData("FakeOutputDir", "../AnotherFakeProject")]
[InlineData("FakeOutputDir", "../AnotherFakeProject/FakeContextOutputDir")]
[InlineData("FakeOutputDir", "rooted/AnotherFakeProject")]
[InlineData("FakeOutputDir", "rooted/AnotherFakeProject/FakeContextOutputDir")]
public void OperationExecutor_ScaffoldContext_generates_separate_context_output_path(string outputDir, string outputDbContextDir)
{
IOperationReportHandler reportHandler = new OperationReportHandler();
var resultHandler = new OperationResultHandler();
var projectPath = Path.Combine(new TempDirectory().Path, "FakeProjectDir");
var executorArgs = new Dictionary<string, object>
{
{ "targetName", "FakeTarget"},
{ "startupTargetName", "Microsoft.EntityFrameworkCore.Design.Tests"},
{ "projectDir", projectPath},
{ "rootNamespace", "FakeRootNamespace"},
{ "language", "C#"},
};
var executor = new OperationExecutor(reportHandler, executorArgs);
var connectionString = new SqlConnectionStringBuilder
{
DataSource = @"(localdb)\MSSQLLocalDB",
InitialCatalog = "CommandConfiguration",
IntegratedSecurity = true,
}.ConnectionString;

if (outputDbContextDir != null && outputDbContextDir.StartsWith("rooted"))
{
var altDirName = outputDbContextDir.Substring(7);
outputDbContextDir = Path.Combine(new TempDirectory().Path, altDirName);
}

var scaffolderArgs = new Dictionary<string, object>
{
{ "connectionString", connectionString},
{ "provider", "Microsoft.EntityFrameworkCore.SqlServer"},
{ "outputDir", outputDir},
{ "outputDbContextDir", outputDbContextDir},
{ "dbContextClassName", "FakeDbContextClassName"},
{ "schemaFilters", new[]{"FakeSchemaFilter"}},
{ "tableFilters", new[] {"FakeTableFilter"}},
{ "useDataAnnotations", false},
{ "overwriteFiles", true},
{ "useDatabaseNames", false},
};

new OperationExecutor.ScaffoldContext(executor, resultHandler, scaffolderArgs);

var files = (Hashtable)resultHandler.Result;
var fullContextPath = Path.GetDirectoryName((string)files["ContextFile"]);
var contextPath = new DirectoryInfo(fullContextPath).Name;
var expectedOutputPath = outputDir;
if (outputDbContextDir != null && outputDbContextDir != outputDir)
{
expectedOutputPath = new DirectoryInfo(outputDbContextDir).Name;
}

Assert.Equal(expectedOutputPath, contextPath);
}

private IEnumerable<string> YieldResults()
{
yield return "Twilight Sparkle";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +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.IO;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.TestUtilities;
Expand Down Expand Up @@ -32,11 +33,12 @@ public void WriteCode_works()
var result = generator.GenerateModel(
modelBuilder.Model,
"TestNamespace",
Path.Combine("..", "TestContextDir" + Path.DirectorySeparatorChar),
"TestContext",
"Data Source=Test",
dataAnnotations: true);

Assert.Equal("TestContext.cs", result.ContextFile.Path);
Assert.Equal(Path.Combine("..", "TestContextDir", "TestContext.cs"), result.ContextFile.Path);
Assert.Equal(
@"using System;
using Microsoft.EntityFrameworkCore;
Expand Down
Loading

0 comments on commit 1e5756a

Please sign in to comment.