Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cli: support for Separate tenant schema #7889

Merged
merged 10 commits into from
Mar 2, 2021
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ public async Task ExecuteAsync(CommandLineArgs commandLineArgs)
await SolutionModuleAdder.AddAsync(
solutionFile,
commandLineArgs.Target,
commandLineArgs.Options.GetOrNull(Options.StartupProject.Short, Options.StartupProject.Long),
version,
skipDbMigrations,
withSourceCode,
Expand Down Expand Up @@ -167,12 +166,6 @@ public static class DbMigrations
public const string Skip = "skip-db-migrations";
}

public static class StartupProject
{
public const string Short = "sp";
public const string Long = "startup-project";
}

public static class SourceCode
{
public const string Long = "with-source-code";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ public virtual async Task ExecuteAsync(CommandLineArgs commandLineArgs)
throw new CliUsageException("DbMigrations folder path is missing!");
}

var dbMigratorProjectPath = GetDbMigratorProjectPath(commandLineArgs.Target);
var dbMigrationsFolder = commandLineArgs.Target;

var dbMigratorProjectPath = GetDbMigratorProjectPath(dbMigrationsFolder);
if (dbMigratorProjectPath == null)
{
throw new Exception("DbMigrator is not found!");
Expand All @@ -37,36 +39,88 @@ public virtual async Task ExecuteAsync(CommandLineArgs commandLineArgs)
InstallDotnetEfTool();
}

var addMigrationCmd = $"cd \"{commandLineArgs.Target}\" && " +
$"dotnet ef migrations add Initial -s \"{dbMigratorProjectPath}\"";
var tenantDbContextName = FindTenantDbContextName(dbMigrationsFolder);
var dbContextName = tenantDbContextName != null ?
FindDbContextName(dbMigrationsFolder)
: null;

var migrationOutput = AddMigrationAndGetOutput(dbMigrationsFolder, dbContextName, "Migrations");
var tenantMigrationOutput = tenantDbContextName != null ?
AddMigrationAndGetOutput(dbMigrationsFolder, tenantDbContextName, "TenantMigrations")
: null;

var output = CmdHelper.RunCmdAndGetOutput(addMigrationCmd);
if (output.Contains("Done.") &&
output.Contains("To undo this action") &&
output.Contains("ef migrations remove"))
if (CheckMigrationOutput(migrationOutput) && CheckMigrationOutput(tenantMigrationOutput))
{
// Migration added successfully
CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(dbMigratorProjectPath) + "\" && dotnet run");
await Task.CompletedTask;
}
else
{
var exceptionMsg = "Migrations failed! The following command didn't run successfully:" +
var exceptionMsg = "Migrations failed! A migration command didn't run successfully:" +
Environment.NewLine +
addMigrationCmd +
Environment.NewLine + output;
Environment.NewLine + migrationOutput +
Environment.NewLine +
Environment.NewLine + tenantMigrationOutput;

Logger.LogError(exceptionMsg);
throw new Exception(exceptionMsg);
}
}

private string FindTenantDbContextName(string dbMigrationsFolder)
{
var tenantDbContext = Directory
.GetFiles(dbMigrationsFolder, "*TenantMigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault();

if (tenantDbContext == null)
{
return null;
}

return Path.GetFileName(tenantDbContext).RemovePostFix(".cs");
}

private string FindDbContextName(string dbMigrationsFolder)
{
var dbContext = Directory
.GetFiles(dbMigrationsFolder, "*MigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault(fp => !fp.EndsWith("TenantMigrationsDbContext.cs"));

if (dbContext == null)
{
return null;
}

return Path.GetFileName(dbContext).RemovePostFix(".cs");
}

private static string AddMigrationAndGetOutput(string dbMigrationsFolder, string dbContext, string outputDirectory)
{
var dbContextOption = string.IsNullOrWhiteSpace(dbContext)
? string.Empty
: $"--context {dbContext}";

var addMigrationCmd = $"cd \"{dbMigrationsFolder}\" && " +
$"dotnet ef migrations add Initial --output-dir {outputDirectory} {dbContextOption}";

return CmdHelper.RunCmdAndGetOutput(addMigrationCmd);
}

private static bool IsDotNetEfToolInstalled()
{
var output = CmdHelper.RunCmdAndGetOutput("dotnet tool list -g");
return output.Contains("dotnet-ef");
}

private static bool CheckMigrationOutput(string output)
{
return output == null || (output.Contains("Done.") &&
output.Contains("To undo this action") &&
output.Contains("ef migrations remove"));
}

private void InstallDotnetEfTool()
{
Logger.LogInformation("Installing dotnet-ef tool...");
Expand Down Expand Up @@ -95,4 +149,4 @@ public string GetShortDescription()
return string.Empty;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,17 @@ public override void Execute(ProjectBuildContext context)

private void AdjustOracleDbContextOptionsBuilder(ProjectBuildContext context)
{
var dbContextFactoryFile = context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase))
?? context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));

dbContextFactoryFile.ReplaceText("new DbContextOptionsBuilder",
$"(DbContextOptionsBuilder<{context.BuildArgs.SolutionName.ProjectName}MigrationsDbContext>) new DbContextOptionsBuilder");
}

private void AddMySqlServerVersion(ProjectBuildContext context)
{
var dbContextFactoryFile = context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase))
?? context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));

dbContextFactoryFile.ReplaceText("configuration.GetConnectionString(\"Default\")",
"configuration.GetConnectionString(\"Default\"), MySqlServerVersion.LatestSupportedServerVersion");
Expand Down Expand Up @@ -90,7 +92,8 @@ private void ChangeUseSqlServer(ProjectBuildContext context, string newUseMethod
var efCoreModuleClass = context.Files.First(f => f.Name.EndsWith("EntityFrameworkCoreModule.cs", StringComparison.OrdinalIgnoreCase));
efCoreModuleClass.ReplaceText(oldUseMethod, newUseMethodForEfModule);

var dbContextFactoryFile = context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith("MigrationsDbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase))
?? context.Files.First(f => f.Name.EndsWith("MigrationsDbContextFactory.cs", StringComparison.OrdinalIgnoreCase));
dbContextFactoryFile.ReplaceText(oldUseMethod, newUseMethodForDbContext);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public override void Execute(ProjectBuildContext context)
f.Name.EndsWith(".csproj") ||
f.Name.EndsWith("Module.cs") ||
f.Name.EndsWith("MyProjectNameMigrationsDbContext.cs") ||
f.Name.EndsWith("MyProjectNameMigrationsDbContextBase.cs") ||
f.Name.EndsWith("MyProjectNameGlobalFeatureConfigurator.cs") ||
(f.Name.EndsWith(".cshtml") && f.Name.Contains("MyCompanyName.MyProjectName.Web.Public"))
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public override IEnumerable<ProjectBuildPipelineStep> GetCustomSteps(ProjectBuil
{
var steps = new List<ProjectBuildPipelineStep>();

ConfigureTenantSchema(context, steps);
SwitchDatabaseProvider(context, steps);
DeleteUnrelatedProjects(context, steps);
RemoveMigrations(context, steps);
Expand All @@ -38,6 +39,19 @@ public override IEnumerable<ProjectBuildPipelineStep> GetCustomSteps(ProjectBuil
return steps;
}

private static void ConfigureTenantSchema(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.ExtraProperties.ContainsKey("separate-tenant-schema"))
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations"));
steps.Add(new AppTemplateProjectRenameStep("MyCompanyName.MyProjectName.EntityFrameworkCore.SeparateDbMigrations", "MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations"));
}
else
{
steps.Add(new RemoveProjectFromSolutionStep("MyCompanyName.MyProjectName.EntityFrameworkCore.SeparateDbMigrations"));
}
}

private static void SwitchDatabaseProvider(ProjectBuildContext context, List<ProjectBuildPipelineStep> steps)
{
if (context.BuildArgs.DatabaseProvider == DatabaseProvider.MongoDb)
Expand Down Expand Up @@ -284,6 +298,7 @@ private static void RemoveMigrations(ProjectBuildContext context, List<ProjectBu
SemanticVersion.Parse(context.BuildArgs.Version) > new SemanticVersion(4,1,99))
{
steps.Add(new RemoveFolderStep("/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/Migrations"));
steps.Add(new RemoveFolderStep("/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore.DbMigrations/TenantMigrations"));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,38 @@ public EfCoreMigrationManager()
Logger = NullLogger<EfCoreMigrationManager>.Instance;
}

public void AddMigration(string dbMigrationsCsprojFile, string module, string startupProject)
public void AddMigration(string dbMigrationsCsprojFile, string module)
{
var dbMigrationsProjectFolder = Path.GetDirectoryName(dbMigrationsCsprojFile);
var moduleName = ParseModuleName(module);
var migrationName = "Added_" + moduleName + "_Module" + GetUniquePostFix();

CmdHelper.RunCmd("cd \"" + Path.GetDirectoryName(dbMigrationsCsprojFile) +
"\" && dotnet ef migrations add " + migrationName +
GetStartupProjectOption(startupProject));
var tenantDbContextName = FindTenantDbContextName(dbMigrationsProjectFolder);
var dbContextName = tenantDbContextName != null ?
FindDbContextName(dbMigrationsProjectFolder)
: null;

if (!string.IsNullOrEmpty(tenantDbContextName))
{
RunAddMigrationCommand(dbMigrationsProjectFolder, migrationName, tenantDbContextName, "TenantMigrations");
}

RunAddMigrationCommand(dbMigrationsProjectFolder, migrationName, dbContextName, "Migrations");
}

protected virtual void RunAddMigrationCommand(
string dbMigrationsProjectFolder,
string migrationName,
string dbContext,
string outputDirectory)
{
var dbContextOption = string.IsNullOrWhiteSpace(dbContext)
? string.Empty
: $"--context {dbContext}";

CmdHelper.RunCmd($"cd \"{dbMigrationsProjectFolder}\" && dotnet ef migrations add {migrationName}" +
$" --output-dir {outputDirectory}" +
$" {dbContextOption}");
}

protected virtual string ParseModuleName(string fullModuleName)
Expand All @@ -43,9 +67,32 @@ protected virtual string GetUniquePostFix()
return "_" + new Random().Next(1, 99999);
}

protected virtual string GetStartupProjectOption(string startupProject)
protected virtual string FindDbContextName(string dbMigrationsFolder)
{
return startupProject.IsNullOrWhiteSpace() ? "" : $" -s {startupProject}";
var dbContext = Directory
.GetFiles(dbMigrationsFolder, "*MigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault(fp => !fp.EndsWith("TenantMigrationsDbContext.cs"));

if (dbContext == null)
{
return null;
}

return Path.GetFileName(dbContext).RemovePostFix(".cs");
}

protected virtual string FindTenantDbContextName(string dbMigrationsFolder)
{
var tenantDbContext = Directory
.GetFiles(dbMigrationsFolder, "*TenantMigrationsDbContext.cs", SearchOption.AllDirectories)
.FirstOrDefault();

if (tenantDbContext == null)
{
return null;
}

return Path.GetFileName(tenantDbContext).RemovePostFix(".cs");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ public class SolutionModuleAdder : ITransientDependency
public virtual async Task AddAsync(
[NotNull] string solutionFile,
[NotNull] string moduleName,
string startupProject,
string version,
bool skipDbMigrations = false,
bool withSourceCode = false,
Expand Down Expand Up @@ -128,7 +127,7 @@ public class SolutionModuleAdder : ITransientDependency

await RunBundleForBlazorAsync(projectFiles, module);

ModifyDbContext(projectFiles, module, startupProject, skipDbMigrations);
ModifyDbContext(projectFiles, module, skipDbMigrations);
}

private async Task RunBundleForBlazorAsync(string[] projectFiles, ModuleWithMastersInfo module)
Expand Down Expand Up @@ -450,8 +449,7 @@ private async Task DeleteRedundantHostProjects(string targetModuleFolder, string
}
}

protected void ModifyDbContext(string[] projectFiles, ModuleInfo module, string startupProject,
bool skipDbMigrations = false)
protected void ModifyDbContext(string[] projectFiles, ModuleInfo module, bool skipDbMigrations = false)
{
if (string.IsNullOrWhiteSpace(module.EfCoreConfigureMethodName))
{
Expand All @@ -463,11 +461,6 @@ private async Task DeleteRedundantHostProjects(string targetModuleFolder, string
return;
}

if (string.IsNullOrWhiteSpace(startupProject))
{
startupProject = projectFiles.FirstOrDefault(p => p.EndsWith(".DbMigrator.csproj"));
}

var dbMigrationsProject = projectFiles.FirstOrDefault(p => p.EndsWith(".DbMigrations.csproj"));

if (dbMigrationsProject == null)
Expand Down Expand Up @@ -498,7 +491,7 @@ private async Task DeleteRedundantHostProjects(string targetModuleFolder, string
{
if (addedNewBuilder)
{
EfCoreMigrationManager.AddMigration(dbMigrationsProject, module.Name, startupProject);
EfCoreMigrationManager.AddMigration(dbMigrationsProject, module.Name);
}

RunMigrator(projectFiles);
Expand Down