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

Support edit and refresh with CLI config file #2738

Merged
merged 2 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions src/GUI/EFCorePowerTools/EFCorePowerToolsPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public sealed class EFCorePowerToolsPackage : AsyncPackage
private readonly DacpacAnalyzerHandler dacpacAnalyzerHandler;
private readonly DabBuilderHandler dabBuilderHandler;
private readonly ErDiagramHandler erDiagramHandler;
private readonly CliHandler cliHandler;
private IServiceProvider extensionServices;

public EFCorePowerToolsPackage()
Expand All @@ -74,6 +75,7 @@ public EFCorePowerToolsPackage()
dacpacAnalyzerHandler = new DacpacAnalyzerHandler(this);
dabBuilderHandler = new DabBuilderHandler(this);
erDiagramHandler = new ErDiagramHandler(this);
cliHandler = new CliHandler(this);
}

internal EnvDTE80.DTE2 Dte2()
Expand Down Expand Up @@ -331,8 +333,15 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke
private static bool IsConfigFile(string itemName)
{
return itemName != null
&& itemName.StartsWith("efpt.", StringComparison.OrdinalIgnoreCase)
&& itemName.EndsWith(".config.json", StringComparison.OrdinalIgnoreCase);
&& ((itemName.StartsWith("efpt.", StringComparison.OrdinalIgnoreCase)
&& itemName.EndsWith(".config.json", StringComparison.OrdinalIgnoreCase))
|| itemName.Equals("efcpt-config.json", StringComparison.OrdinalIgnoreCase));
}

private static bool IsCliConfigFile(string itemName)
{
return itemName != null
&& itemName.Equals("efcpt-config.json", StringComparison.OrdinalIgnoreCase);
}

private static bool IsDabConfigFile(string itemName)
Expand Down Expand Up @@ -670,7 +679,14 @@ private async void OnReverseEngineerConfigFileMenuInvokeHandler(object sender, E
return;
}

await reverseEngineerHandler.ReverseEngineerCodeFirstAsync(project, filename, false);
if (IsCliConfigFile(item.Text))
{
await cliHandler.EditConfigAsync(project);
}
else
{
await reverseEngineerHandler.ReverseEngineerCodeFirstAsync(project, filename, false);
}
}
else if (menuCommand.CommandID.ID == PkgCmdIDList.cmdidReverseEngineerRefresh)
{
Expand All @@ -679,7 +695,14 @@ private async void OnReverseEngineerConfigFileMenuInvokeHandler(object sender, E
return;
}

await reverseEngineerHandler.ReverseEngineerCodeFirstAsync(project, filename, true);
if (IsCliConfigFile(item.Text))
{
await cliHandler.RunCliAsync(project);
}
else
{
await reverseEngineerHandler.ReverseEngineerCodeFirstAsync(project, filename, true);
}
}
else if (menuCommand.CommandID.ID == PkgCmdIDList.cmdidDabStart)
{
Expand Down Expand Up @@ -805,7 +828,7 @@ private async void OnProjectContextMenuInvokeHandler(object sender, EventArgs e)

if (await project.IsMsBuildSqlProjOrMsBuildSqlProjectAsync())
{
connectionName = await project.GetOutPutAssemblyPathAsync();
connectionName = await project.GetDacpacPathAsync();
}
}

Expand Down
27 changes: 27 additions & 0 deletions src/GUI/RevEng.Shared/Providers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,33 @@ public static DatabaseType ToDatabaseType(this string providerAlias, bool isDacp
}
}

public static string ToDatabaseShortName(this DatabaseType databaseType)
{
switch (databaseType)
{
case DatabaseType.Undefined:
return "Undefined";
case DatabaseType.SQLServer:
return "mssql";
case DatabaseType.SQLite:
return "sqlite";
case DatabaseType.Npgsql:
return "npgsql";
case DatabaseType.Mysql:
return "mysql";
case DatabaseType.Oracle:
return "oracle";
case DatabaseType.SQLServerDacpac:
return "mssql";
case DatabaseType.Firebird:
return "firebird";
case DatabaseType.Snowflake:
return "snowflake";
default:
return "Undefined";
}
}

public static HashSet<DatabaseType> GetDabProviders()
{
return new HashSet<DatabaseType>
Expand Down
20 changes: 14 additions & 6 deletions src/GUI/Shared/Extensions/ProjectExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ public static async Task<string> GetStartupProjectOutputPathAsync()
}
}

public static async Task<string> GetDacpacPathAsync(this Project project)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

var assemblyName = await project.GetAttributeAsync("SqlTargetPath");

if (string.IsNullOrEmpty(assemblyName))
{
assemblyName = await project.GetAttributeAsync("TargetPath");
}

return assemblyName;
}

public static async Task<string> GetOutPutAssemblyPathAsync(this Project project)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
Expand All @@ -54,7 +68,6 @@ public static async Task<string> GetOutPutAssemblyPathAsync(this Project project

var assemblyNameExe = assemblyName + ".exe";
var assemblyNameDll = assemblyName + ".dll";
var assemblyNameDacpac = assemblyName + ".dacpac";

var outputPath = await GetOutputPathAsync(project);

Expand All @@ -73,11 +86,6 @@ public static async Task<string> GetOutPutAssemblyPathAsync(this Project project
return Path.Combine(outputPath, assemblyNameDll);
}

if (File.Exists(Path.Combine(outputPath, assemblyNameDacpac)))
{
return Path.Combine(outputPath, assemblyNameDacpac);
}

return null;
}

Expand Down
245 changes: 245 additions & 0 deletions src/GUI/Shared/Handlers/CliHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,245 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
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.Extensions;
using EFCorePowerTools.Helpers;
using EFCorePowerTools.Locales;
using Microsoft.VisualStudio.Shell;
using RevEng.Common;
using RevEng.Common.Dab;

namespace EFCorePowerTools.Handlers.ReverseEngineer
{
internal class CliHandler
{
private readonly EFCorePowerToolsPackage package;
private readonly VsDataHelper vsDataHelper;

public CliHandler(EFCorePowerToolsPackage package)
{
this.package = package;
vsDataHelper = new VsDataHelper();
}

public async Task EditConfigAsync(Project project)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

try
{
var optionsPath = Path.Combine(Path.GetDirectoryName(project.FullPath), "efcpt-config.json");
if (!File.Exists(optionsPath))
{
return;
}

await VS.Documents.OpenAsync(optionsPath);

Telemetry.TrackEvent("PowerTools.CliEdit");
}
catch (AggregateException ae)
{
foreach (var innerException in ae.Flatten().InnerExceptions)
{
package.LogError(new List<string>(), innerException);
}
}
catch (Exception exception)
{
package.LogError(new List<string>(), exception);
}
}

public async Task RunCliAsync(Project project)
{
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

try
{
if (await VSHelper.IsDebugModeAsync())
{
VSHelper.ShowError(ReverseEngineerLocale.CannotGenerateCodeWhileDebugging);
return;
}

var projectPath = Path.GetDirectoryName(project.FullPath);

var optionsPath = Path.Combine(projectPath, "efcpt-config.json");

if (!File.Exists(optionsPath))
{
return;
}

var userOptions = ReverseEngineerUserOptionsExtensions.TryRead(optionsPath, projectPath);

if (userOptions == null)
{
userOptions = new ReverseEngineerUserOptions();
}

var options = new DataApiBuilderOptions();

DatabaseConnectionModel dbInfo = null;

if (!await ChooseDataBaseConnectionAsync(options, userOptions))
{
await VS.StatusBar.ClearAsync();
return;
}

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

dbInfo = await GetDatabaseInfoAsync(options);

if (dbInfo == null)
{
await VS.StatusBar.ClearAsync();
return;
}

SaveOptions(project, optionsPath, userOptions);

LaunchCli(optionsPath, dbInfo);

Telemetry.TrackEvent("PowerTools.CliRefresh");
}
catch (AggregateException ae)
{
foreach (var innerException in ae.Flatten().InnerExceptions)
{
package.LogError(new List<string>(), innerException);
}
}
catch (Exception exception)
{
package.LogError(new List<string>(), exception);
}
}

private async Task<bool> ChooseDataBaseConnectionAsync(DataApiBuilderOptions options, ReverseEngineerUserOptions userOptions)
{
var databaseList = await vsDataHelper.GetDataConnectionsAsync(package);

databaseList = databaseList.Where(databaseList => Providers.GetDabProviders().Contains(databaseList.Value.DatabaseType))
.ToDictionary(databaseList => databaseList.Key, databaseList => databaseList.Value);

var dacpacList = await SqlProjHelper.GetDacpacFilesInActiveSolutionAsync();

var psd = package.GetView<IPickServerDatabaseDialog>();

if (databaseList.Any())
{
psd.PublishConnections(databaseList.Select(m => new DatabaseConnectionModel
{
ConnectionName = m.Value.ConnectionName,
ConnectionString = m.Value.ConnectionString,
DatabaseType = m.Value.DatabaseType,
DataConnection = m.Value.DataConnection,
}));
}

if (dacpacList != null && dacpacList.Any())
{
psd.PublishDefinitions(dacpacList.Select(m => new DatabaseConnectionModel
{
FilePath = m,
DatabaseType = DatabaseType.SQLServerDacpac,
}));
}

psd.PublishCodeGenerationMode(CodeGenerationMode.EFCore6, new List<CodeGenerationItem>
{
new CodeGenerationItem { Key = (int)CodeGenerationMode.EFCore8, Value = "DAB" },
});

if (!string.IsNullOrEmpty(userOptions.UiHint))
{
psd.PublishUiHint(userOptions.UiHint);
}

psd.PublishSchemas(new List<SchemaInfo>());

var pickDataSourceResult = psd.ShowAndAwaitUserResponse(true);
if (!pickDataSourceResult.ClosedByOK)
{
return false;
}

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

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

return true;
}

private 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.Dacpac = await SqlProjHelper.BuildSqlProjAsync(options.Dacpac);
if (string.IsNullOrEmpty(options.Dacpac))
{
VSHelper.ShowMessage(ReverseEngineerLocale.UnableToBuildSelectedDatabaseProject);
return null;
}

dbInfo.FilePath = options.Dacpac;
}

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

return dbInfo;
}

private void SaveOptions(Project project, string optionsPath, ReverseEngineerUserOptions userOptions)
{
if (userOptions != null && !string.IsNullOrEmpty(userOptions.UiHint))
{
File.WriteAllText(optionsPath + ".user", userOptions.Write(Path.GetDirectoryName(project.FullPath)), Encoding.UTF8);
}
}

private void LaunchCli(string configPath, DatabaseConnectionModel database)
{
var path = Path.GetDirectoryName(configPath);

var proc = new Process();
proc.StartInfo.FileName = "cmd";
proc.StartInfo.Arguments = $" /k \"cd /d {path} && efcpt \"{database.FilePath ?? database.ConnectionString}\" {database.DatabaseType.ToDatabaseShortName()}\"";
proc.Start();
}
}
}
Loading