Skip to content

Commit

Permalink
Diagram generation did not support "mergedacpacs" (#2838)
Browse files Browse the repository at this point in the history
fixes #2835
  • Loading branch information
ErikEJ authored Feb 6, 2025
1 parent 5fbea20 commit 6a718f6
Show file tree
Hide file tree
Showing 9 changed files with 127 additions and 65 deletions.
41 changes: 12 additions & 29 deletions src/Core/RevEng.Core.80/Diagram/DiagramBuilder.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,34 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.EntityFrameworkCore.Scaffolding;
using Microsoft.EntityFrameworkCore.Scaffolding.Metadata;
using Microsoft.Extensions.DependencyInjection;
using RevEng.Common;
using RevEng.Common.Dab;

namespace RevEng.Core.Diagram
{
public class DiagramBuilder
{
private readonly string connectionString;
private readonly ServiceProvider serviceProvider;
private readonly DataApiBuilderOptions dataApiBuilderOptions;
private readonly List<string> schemas = Enumerable.Empty<string>().ToList();

public DiagramBuilder(int databaseType, string connectionString, List<string> schemas)
public DiagramBuilder(DataApiBuilderOptions dataApiBuilderCommandOptions, List<string> schemas)
{
if (string.IsNullOrEmpty(connectionString))
{
throw new ArgumentNullException(nameof(connectionString), @"invalid connection string");
}
dataApiBuilderOptions = dataApiBuilderCommandOptions;

this.connectionString = connectionString.ApplyDatabaseType((DatabaseType)databaseType);
dataApiBuilderOptions.ConnectionString = dataApiBuilderOptions.ConnectionString.ApplyDatabaseType(dataApiBuilderOptions.DatabaseType);

var options = new ReverseEngineerCommandOptions
{
DatabaseType = (DatabaseType)databaseType,
ConnectionString = connectionString,
DatabaseType = dataApiBuilderOptions.DatabaseType,
ConnectionString = dataApiBuilderOptions.ConnectionString,
Dacpac = dataApiBuilderOptions.Dacpac,
MergeDacpacs = dataApiBuilderOptions.MergeDacpacs,
};

this.schemas = schemas;

serviceProvider = new ServiceCollection().AddEfpt(options, [], [], []).BuildServiceProvider();
}

Expand All @@ -48,26 +44,13 @@ public string GetDgmlFileName()
return fileName;
}

public string GetErDiagramFileName()
{
var model = GetModelInternal();

var creator = new DatabaseModelToMermaid(model);

var diagram = creator.CreateMermaid(createMarkdown: false);

var fileName = Path.Join(Path.GetTempPath(), Path.GetRandomFileName() + ".mmd");
File.WriteAllText(fileName, diagram, Encoding.UTF8);

return fileName;
}

private DatabaseModel GetModelInternal()
{
var dbModelFactory = serviceProvider.GetService<IDatabaseModelFactory>();

var dbModelOptions = new DatabaseModelFactoryOptions(schemas: schemas);
var dbModel = dbModelFactory!.Create(connectionString, dbModelOptions);

var dbModel = dbModelFactory!.Create(dataApiBuilderOptions.Dacpac ?? dataApiBuilderOptions.ConnectionString, dbModelOptions);

return dbModel;
}
Expand Down
1 change: 1 addition & 0 deletions src/Core/RevEng.Core.80/Diagram/ErDiagramBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public ErDiagramBuilder(DataApiBuilderOptions dataApiBuilderCommandOptions)
DatabaseType = dataApiBuilderOptions.DatabaseType,
ConnectionString = dataApiBuilderOptions.ConnectionString,
Dacpac = dataApiBuilderOptions.Dacpac,
MergeDacpacs = dataApiBuilderOptions.MergeDacpacs,
};

serviceProvider = new ServiceCollection().AddEfpt(options, [], [], []).BuildServiceProvider();
Expand Down
28 changes: 17 additions & 11 deletions src/Core/efreveng80/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,29 +68,34 @@ public static async System.Threading.Tasks.Task<int> Main(string[] args)
return 0;
}

// dgml <options file> <connection string> [schemas]
if ((args.Length == 3 || args.Length == 4)
&& (args[0] == "dgml")
&& int.TryParse(args[1], out int dbType))
{
&& new FileInfo(args[1]).Exists)
{
var schemas = Enumerable.Empty<string>().ToList();
if (args.Length == 4)
{
schemas = args[3].Split(',', StringSplitOptions.RemoveEmptyEntries).Select(s => s).ToList();
}

var builder = new DiagramBuilder(dbType, args[2], schemas);

var buildResult = string.Empty;
var dabOptions = DataApiBuilderOptionsExtensions.TryRead(args[1]);

if (args[0] == "dgml")
{
buildResult = builder.GetDgmlFileName();
}
else
if (dabOptions == null)
{
buildResult = builder.GetErDiagramFileName();
await Console.Out.WriteLineAsync("Error:");
await Console.Out.WriteLineAsync("Could not read options");
return 1;
}

dabOptions.ConnectionString = args[2];

var builder = new DiagramBuilder(dabOptions, schemas);

var buildResult = string.Empty;

buildResult = builder.GetDgmlFileName();

await Console.Out.WriteLineAsync("Result:");
await Console.Out.WriteLineAsync(buildResult);

Expand Down Expand Up @@ -130,6 +135,7 @@ public static async System.Threading.Tasks.Task<int> Main(string[] args)
}
#endif
#if NET8_0
// erdiagram <options file> <connection string>
if (args.Length == 3
&& args[0] == "erdiagram"
&& new FileInfo(args[1]).Exists)
Expand Down
2 changes: 1 addition & 1 deletion src/GUI/EFCorePowerTools/EFCorePowerToolsPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -907,7 +907,7 @@ await revEngWizardHandler.ReverseEngineerCodeFirstLaunchWizardAsync(new WizardEv
}
}

await databaseDiagramHandler.GenerateAsync(connectionName);
await databaseDiagramHandler.GenerateAsync(project, connectionName);
}
}
catch (Exception ex)
Expand Down
2 changes: 2 additions & 0 deletions src/GUI/RevEng.Shared/Dab/DataApiBuilderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@ public class DataApiBuilderOptions
public string Dacpac { get; set; }

public string ConnectionStringName { get; set; } = "dab-connection-string";

public bool MergeDacpacs { get; set; }
}
}
107 changes: 89 additions & 18 deletions src/GUI/Shared/Handlers/DatabaseDiagramHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Community.VisualStudio.Toolkit;
using EFCorePowerTools.Common.Models;
using EFCorePowerTools.Contracts.ViewModels;
using EFCorePowerTools.Contracts.Views;
using EFCorePowerTools.Handlers.ReverseEngineer;
using EFCorePowerTools.Helpers;
using Microsoft.VisualStudio.Data.Services;
using EFCorePowerTools.Locales;
using Microsoft.VisualStudio.Shell;
using RevEng.Common;
using RevEng.Common.Dab;

namespace EFCorePowerTools.Handlers
{
Expand All @@ -24,37 +26,56 @@ public DatabaseDiagramHandler(EFCorePowerToolsPackage package)
this.package = package;
}

public async Task GenerateAsync(string connectionName = null)
public async Task GenerateAsync(Project project, string connectionName = null)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

try
{
var info = await ChooseDataBaseConnectionAsync(connectionName);

if (info.DatabaseModel == null)
if (await VSHelper.IsDebugModeAsync())
{
VSHelper.ShowError(ReverseEngineerLocale.CannotGenerateCodeWhileDebugging);
return;
}

var connectionString = info.DatabaseModel.ConnectionString;
var optionsPath = Path.Combine(Path.GetTempPath(), "diagram-options.json");

var options = new DataApiBuilderOptions();

options.ProjectPath = Path.GetDirectoryName(project.FullPath);

var info = await ChooseDataBaseConnectionAsync(options, connectionName);

if (info.DatabaseModel.DatabaseType == DatabaseType.SQLServerDacpac)
if (info.DatabaseModel == null)
{
connectionString = await SqlProjHelper.BuildSqlProjectAsync(info.DatabaseModel.FilePath);
await VS.StatusBar.ClearAsync();
return;
}

if (info.DatabaseModel.DataConnection != null)
await VS.StatusBar.ShowMessageAsync(ReverseEngineerLocale.GettingReadyToConnect);

var dbInfo = await GetDatabaseInfoAsync(options);

if (dbInfo == null)
{
info.DatabaseModel.DataConnection.Open();
connectionString = DataProtection.DecryptString(info.DatabaseModel.DataConnection.EncryptedConnectionString);
await VS.StatusBar.ClearAsync();
return;
}

var diagramPath = await GetDiagramAsync(connectionString, info.DatabaseModel.DatabaseType, info.Schemas);
SaveOptions(optionsPath, options);

var diagramPath = await GetDiagramAsync(optionsPath, dbInfo.ConnectionString, info.Schemas);

await ShowDiagramAsync(diagramPath);

Telemetry.TrackEvent("PowerTools.GenerateServerDgml");
Telemetry.TrackEvent("PowerTools.DgmlDiagramBuild");
}
catch (AggregateException ae)
{
foreach (var innerException in ae.Flatten().InnerExceptions)
{
EFCorePowerToolsPackage.LogError(new List<string>(), innerException);
}
}
catch (Exception exception)
{
Expand All @@ -73,10 +94,10 @@ private static List<string> GetSchemas(SchemaInfo[] schemas)
return schemaList;
}

private static async Task<string> GetDiagramAsync(string connectionString, DatabaseType databaseType, SchemaInfo[] schemas)
private static async Task<string> GetDiagramAsync(string optionsPath, string connectionString, SchemaInfo[] schemas)
{
var launcher = new EfRevEngLauncher(null, CodeGenerationMode.EFCore8);
return await launcher.GetDiagramAsync(connectionString, databaseType, GetSchemas(schemas));
return await launcher.GetDiagramAsync(connectionString, optionsPath, GetSchemas(schemas));
}

private static async Task ShowDiagramAsync(string path)
Expand All @@ -87,7 +108,49 @@ private static async Task ShowDiagramAsync(string path)
}
}

private async Task<(DatabaseConnectionModel DatabaseModel, SchemaInfo[] Schemas)> ChooseDataBaseConnectionAsync(string connectionName = null)
private static void SaveOptions(string optionsPath, DataApiBuilderOptions options)
{
File.WriteAllText(optionsPath, options.Write(), Encoding.UTF8);
}

private static async Task<DatabaseConnectionModel> GetDatabaseInfoAsync(DataApiBuilderOptions options)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

var dbInfo = new DatabaseConnectionModel();

if (!string.IsNullOrEmpty(options.ConnectionString))
{
dbInfo.ConnectionString = options.ConnectionString;
dbInfo.DatabaseType = options.DatabaseType;
}

if (!string.IsNullOrEmpty(options.Dacpac))
{
dbInfo.DatabaseType = DatabaseType.SQLServerDacpac;
dbInfo.ConnectionString = $"Data Source=(local);Initial Catalog={Path.GetFileNameWithoutExtension(options.Dacpac)};Integrated Security=true;";
options.ConnectionString = dbInfo.ConnectionString;
options.DatabaseType = dbInfo.DatabaseType;
options.MergeDacpacs = AdvancedOptions.Instance.MergeDacpacs;

options.Dacpac = await SqlProjHelper.BuildSqlProjectAsync(options.Dacpac);
if (string.IsNullOrEmpty(options.Dacpac))
{
VSHelper.ShowMessage(ReverseEngineerLocale.UnableToBuildSelectedDatabaseProject);
return null;
}
}

if (dbInfo.DatabaseType == DatabaseType.Undefined)
{
VSHelper.ShowError($"{ReverseEngineerLocale.UnsupportedProvider}");
return null;
}

return dbInfo;
}

private async Task<(DatabaseConnectionModel DatabaseModel, SchemaInfo[] Schemas)> ChooseDataBaseConnectionAsync(DataApiBuilderOptions options, string connectionName = null)
{
var vsDataHelper = new VsDataHelper();
var databaseList = await vsDataHelper.GetDataConnectionsAsync(package);
Expand All @@ -111,8 +174,8 @@ private static async Task ShowDiagramAsync(string path)
{
return (new DatabaseConnectionModel
{
FilePath = path,
DatabaseType = DatabaseType.SQLServerDacpac,
FilePath = path,
DatabaseType = DatabaseType.SQLServerDacpac,
}, new SchemaInfo[] { });
}
}
Expand Down Expand Up @@ -152,6 +215,14 @@ private static async Task ShowDiagramAsync(string path)
return (null, null);
}

options.Dacpac = pickDataSourceResult.Payload.Connection?.FilePath;

if (pickDataSourceResult.Payload.Connection != null)
{
options.ConnectionString = pickDataSourceResult.Payload.Connection.ConnectionString;
options.DatabaseType = pickDataSourceResult.Payload.Connection.DatabaseType;
}

return (pickDataSourceResult.Payload.Connection, pickDataSourceResult.Payload.Schemas);
}
}
Expand Down
5 changes: 2 additions & 3 deletions src/GUI/Shared/Handlers/ErDiagramHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ public async System.Threading.Tasks.Task BuildErDiagramAsync(Project project, st

options.ProjectPath = Path.GetDirectoryName(project.FullPath);

DatabaseConnectionModel dbInfo = null;

if (!await ChooseDataBaseConnectionAsync(options, uiHint))
{
await VS.StatusBar.ClearAsync();
Expand All @@ -58,7 +56,7 @@ public async System.Threading.Tasks.Task BuildErDiagramAsync(Project project, st

await VS.StatusBar.ShowMessageAsync(ReverseEngineerLocale.GettingReadyToConnect);

dbInfo = await GetDatabaseInfoAsync(options);
var dbInfo = await GetDatabaseInfoAsync(options);

if (dbInfo == null)
{
Expand Down Expand Up @@ -113,6 +111,7 @@ private static async Task<DatabaseConnectionModel> GetDatabaseInfoAsync(DataApiB
dbInfo.ConnectionString = $"Data Source=(local);Initial Catalog={Path.GetFileNameWithoutExtension(options.Dacpac)};Integrated Security=true;";
options.ConnectionString = dbInfo.ConnectionString;
options.DatabaseType = dbInfo.DatabaseType;
options.MergeDacpacs = AdvancedOptions.Instance.MergeDacpacs;

options.Dacpac = await SqlProjHelper.BuildSqlProjectAsync(options.Dacpac);
if (string.IsNullOrEmpty(options.Dacpac))
Expand Down
6 changes: 3 additions & 3 deletions src/GUI/Shared/Handlers/ReverseEngineer/EfRevEngLauncher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,15 @@ public async Task<List<TableModel>> GetTablesAsync(string connectionString, Data
return await GetTablesInternalAsync(arguments);
}

public async Task<string> GetDiagramAsync(string connectionString, DatabaseType databaseType, List<string> schemaList)
public async Task<string> GetDiagramAsync(string connectionString, string optionsPath, List<string> schemaList)
{
var option = "dgml ";

var arguments = option + ((int)databaseType).ToString() + " \"" + connectionString.Replace("\"", "\\\"") + "\" \"" + string.Join(",", schemaList) + "\"";
var arguments = option + " \"" + optionsPath.Replace("\"", "\\\"") + "\" " + " \"" + connectionString.Replace("\"", "\\\"") + "\" \"" + string.Join(",", schemaList) + "\"";

if (schemaList.Count == 0)
{
arguments = option + ((int)databaseType).ToString() + " \"" + connectionString.Replace("\"", "\\\"") + "\"";
arguments = option + " \"" + optionsPath.Replace("\"", "\\\"") + "\" " + " \"" + connectionString.Replace("\"", "\\\"") + "\"";
}

var filePath = await GetDiagramInternalAsync(arguments);
Expand Down
Binary file modified src/GUI/lib/efreveng80.exe.zip
Binary file not shown.

0 comments on commit 6a718f6

Please sign in to comment.