diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/ConfigureDacDeployOptionsAnnotation.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/ConfigureDacDeployOptionsAnnotation.cs new file mode 100644 index 00000000..70ec72c9 --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/ConfigureDacDeployOptionsAnnotation.cs @@ -0,0 +1,11 @@ +using Microsoft.SqlServer.Dac; + +namespace Aspire.Hosting.ApplicationModel; + +/// +/// Represents a metadata annotation that specifies dacpac deployment options. +/// +/// deployment options +public record ConfigureDacDeployOptionsAnnotation(Action ConfigureDeploymentOptions) : IResourceAnnotation +{ +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs index 681bcfa5..9afb22e7 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacpacDeployer.cs @@ -8,12 +8,12 @@ namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects; /// internal class DacpacDeployer : IDacpacDeployer { - /// - public void Deploy(string dacpacPath, string targetConnectionString, string targetDatabaseName, ILogger deploymentLogger, CancellationToken cancellationToken) + /// + public void Deploy(string dacpacPath, DacDeployOptions options, string targetConnectionString, string targetDatabaseName, ILogger deploymentLogger, CancellationToken cancellationToken) { - var dacPackage = DacPackage.Load(dacpacPath, DacSchemaModelStorageType.Memory); + using var dacPackage = DacPackage.Load(dacpacPath, DacSchemaModelStorageType.Memory); var dacServices = new DacServices(targetConnectionString); dacServices.Message += (sender, args) => deploymentLogger.LogInformation(args.Message.ToString()); - dacServices.Deploy(dacPackage, targetDatabaseName, true, new DacDeployOptions(), cancellationToken); + dacServices.Deploy(dacPackage, targetDatabaseName, true, options, cancellationToken); } -} +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/IDacpacDeployer.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/IDacpacDeployer.cs index 4e120737..a9d9b924 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/IDacpacDeployer.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/IDacpacDeployer.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.Logging; +using Microsoft.SqlServer.Dac; namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects; @@ -12,9 +13,10 @@ internal interface IDacpacDeployer /// using the provided database name. /// /// Path to the .dacpac file to deploy. + /// Instance of that specifies properties that affect various aspects of the deployment. /// Connection string to the SQL Server. /// Name of the target database to deploy to. /// An to write the deployment log to. /// A that can be used to cancel the deployment operation. - void Deploy(string dacpacPath, string targetConnectionString, string targetDatabaseName, ILogger deploymentLogger, CancellationToken cancellationToken); + void Deploy(string dacpacPath, DacDeployOptions options, string targetConnectionString, string targetDatabaseName, ILogger deploymentLogger, CancellationToken cancellationToken); } diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/PublicAPI.Unshipped.txt b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/PublicAPI.Unshipped.txt index 074c6ad1..d5d6b090 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/PublicAPI.Unshipped.txt +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/PublicAPI.Unshipped.txt @@ -1,2 +1,6 @@ #nullable enable - +Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation +Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation.ConfigureDacDeployOptionsAnnotation(System.Action! ConfigureDeploymentOptions) -> void +Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation.ConfigureDeploymentOptions.get -> System.Action! +Aspire.Hosting.ApplicationModel.ConfigureDacDeployOptionsAnnotation.ConfigureDeploymentOptions.init -> void +static Aspire.Hosting.SqlProjectBuilderExtensions.WithConfigureDacDeployOptions(this Aspire.Hosting.ApplicationModel.IResourceBuilder! builder, System.Action! configureDeploymentOptions) -> Aspire.Hosting.ApplicationModel.IResourceBuilder! diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/README.md b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/README.md index 3e083cbf..21df7927 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/README.md +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/README.md @@ -45,5 +45,21 @@ builder.AddSqlProject("mysqlproj") .WithDacpac("path/to/mysqlproj.dacpac") .WithReference(sql); +builder.Build().Run(); +``` + +## Deployment options support +Define options that affect the behavior of package deployment. + +```csharp +var builder = DistributedApplication.CreateBuilder(args); + +var sql = builder.AddSqlServer("sql") + .AddDatabase("test"); + +builder.AddSqlProject("mysqlproj") + .WithConfigureDacDeployOptions(options => options.IncludeCompositeObjects = true) + .WithReference(sql); + builder.Build().Run(); ``` \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs index 475cd2d9..4ea2b74d 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs @@ -3,6 +3,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects; +using Microsoft.SqlServer.Dac; namespace Aspire.Hosting; @@ -72,6 +73,21 @@ public static IResourceBuilder WithDacpac(this IResourceBuil return builder.WithAnnotation(new DacpacMetadataAnnotation(dacpacPath)); } + /// + /// Adds a delegate annotation for configuring dacpac deployment options to the . + /// + /// An representing the SQL Server Database project. + /// The delegate for configuring dacpac deployment options + /// An that can be used to further customize the resource. + public static IResourceBuilder WithConfigureDacDeployOptions(this IResourceBuilder builder, Action configureDeploymentOptions) + { + ArgumentNullException.ThrowIfNull(builder, nameof(builder)); + ArgumentNullException.ThrowIfNull(configureDeploymentOptions); + + return builder + .WithAnnotation(new ConfigureDacDeployOptionsAnnotation(configureDeploymentOptions)); + } + /// /// Publishes the SQL Server Database project to the target . /// diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectPublishService.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectPublishService.cs index 98716a32..3d37d0c8 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectPublishService.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectPublishService.cs @@ -21,6 +21,8 @@ await resourceNotificationService.PublishUpdateAsync(sqlProject, return; } + var options = sqlProject.GetDacpacDeployOptions(); + var connectionString = await target.ConnectionStringExpression.GetValueAsync(cancellationToken); if (connectionString is null) { @@ -33,7 +35,7 @@ await resourceNotificationService.PublishUpdateAsync(sqlProject, await resourceNotificationService.PublishUpdateAsync(sqlProject, state => state with { State = new ResourceStateSnapshot("Publishing", KnownResourceStateStyles.Info) }); - deployer.Deploy(dacpacPath, connectionString, target.DatabaseName, logger, cancellationToken); + deployer.Deploy(dacpacPath, options, connectionString, target.DatabaseName, logger, cancellationToken); await resourceNotificationService.PublishUpdateAsync(sqlProject, state => state with { State = new ResourceStateSnapshot(KnownResourceStates.Finished, KnownResourceStateStyles.Success) }); diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs index 004f84cd..68391fde 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs @@ -1,4 +1,5 @@ using Microsoft.Build.Evaluation; +using Microsoft.SqlServer.Dac; namespace Aspire.Hosting.ApplicationModel; @@ -33,4 +34,16 @@ internal string GetDacpacPath() throw new InvalidOperationException($"Unable to locate SQL Server Database project package for resource {Name}."); } + + internal DacDeployOptions GetDacpacDeployOptions() + { + var options = new DacDeployOptions(); + + if (this.TryGetLastAnnotation(out var configureAnnotation)) + { + configureAnnotation.ConfigureDeploymentOptions(options); + } + + return options; + } } diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs index 559e3214..df7d0f56 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs @@ -1,4 +1,5 @@ using Aspire.Hosting; +using Microsoft.SqlServer.Dac; namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests; @@ -47,6 +48,53 @@ public void AddSqlProject_WithExplicitPath() Assert.True(File.Exists(dacpacPath)); } + [Fact] + public void AddSqlProject_WithoutDeploymentOptions() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + + appBuilder.AddSqlProject("MySqlProject"); + + // Act + using var app = appBuilder.Build(); + var appModel = app.Services.GetRequiredService(); + + // Assert + var sqlProjectResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("MySqlProject", sqlProjectResource.Name); + + Assert.False(sqlProjectResource.TryGetLastAnnotation(out ConfigureDacDeployOptionsAnnotation? _)); + + var options = sqlProjectResource.GetDacpacDeployOptions(); + Assert.NotNull(options); + Assert.Equivalent(new DacDeployOptions(), options); + } + + [Fact] + public void AddSqlProject_WithDeploymentOptions() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + Action configureAction = options => options.IncludeCompositeObjects = true; + + appBuilder.AddSqlProject("MySqlProject").WithConfigureDacDeployOptions(configureAction); + + // Act + using var app = appBuilder.Build(); + var appModel = app.Services.GetRequiredService(); + + // Assert + var sqlProjectResource = Assert.Single(appModel.Resources.OfType()); + Assert.Equal("MySqlProject", sqlProjectResource.Name); + + Assert.True(sqlProjectResource.TryGetLastAnnotation(out ConfigureDacDeployOptionsAnnotation? configureDacDeployOptionsAnnotation)); + Assert.Same(configureAction, configureDacDeployOptionsAnnotation.ConfigureDeploymentOptions); + + var options = sqlProjectResource.GetDacpacDeployOptions(); + Assert.True(options.IncludeCompositeObjects); + } + [Fact] public void PublishTo_AddsRequiredServices() {