From 987afeed4ba3b12000d5accab5c188b3745994ba Mon Sep 17 00:00:00 2001 From: Erik Ejlskov Jensen Date: Sun, 15 Jun 2025 12:28:20 +0200 Subject: [PATCH 1/2] Add support for DacDeployOtion from a publish profile fixes #694 --- .../SdkProject/Database.publish.xml | 10 ++++ .../DacDeployOptionsAnnotation.cs | 9 ++++ .../SqlPackageResource.cs | 13 +++++ .../SqlProjectBuilderExtensions.cs | 28 +++++++++++ .../SqlProjectResource.cs | 15 +++++- .../AddSqlPackageTests.cs | 26 ++++++++++ .../AddSqlProjectTests.cs | 49 +++++++++++++++++++ ...e.Hosting.SqlDatabaseProjects.Tests.csproj | 6 +++ .../Database.publish.xml | 9 ++++ 9 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 examples/sql-database-projects/SdkProject/Database.publish.xml create mode 100644 src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacDeployOptionsAnnotation.cs create mode 100644 tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/Database.publish.xml diff --git a/examples/sql-database-projects/SdkProject/Database.publish.xml b/examples/sql-database-projects/SdkProject/Database.publish.xml new file mode 100644 index 00000000..ae6f0f18 --- /dev/null +++ b/examples/sql-database-projects/SdkProject/Database.publish.xml @@ -0,0 +1,10 @@ + + + + False + Database + Database.sql + True + 1 + + \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacDeployOptionsAnnotation.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacDeployOptionsAnnotation.cs new file mode 100644 index 00000000..20970aac --- /dev/null +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/DacDeployOptionsAnnotation.cs @@ -0,0 +1,9 @@ +namespace Aspire.Hosting.ApplicationModel; + +/// +/// Represents a metadata annotation that specifies dacpac deployment options. +/// +/// path to deployment options xml file +public record DacDeployOptionsAnnotation(string OptionsPath) : IResourceAnnotation +{ +} \ No newline at end of file diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlPackageResource.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlPackageResource.cs index 7a1c6ad2..d7869cff 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlPackageResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlPackageResource.cs @@ -37,6 +37,19 @@ DacDeployOptions IResourceWithDacpac.GetDacpacDeployOptions() { var options = new DacDeployOptions(); + if (this.TryGetLastAnnotation(out var optionsAnnotation)) + { + var profile = DacProfile.Load(optionsAnnotation.OptionsPath); + + if (profile == null) + { + throw new InvalidOperationException($"Unable to load DacProfile from path {optionsAnnotation.OptionsPath} for resource {Name}."); + } + + options = profile.DeployOptions; + return options; + } + if (this.TryGetLastAnnotation(out var configureAnnotation)) { configureAnnotation.ConfigureDeploymentOptions(options); diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs index 8d145425..7d36bff2 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectBuilderExtensions.cs @@ -142,6 +142,34 @@ internal static IResourceBuilder InternalWithConfigureDacDeployOption .WithAnnotation(new ConfigureDacDeployOptionsAnnotation(configureDeploymentOptions)); } + /// + /// Adds a path to a publish profile for configuring dacpac deployment options to the . + /// + /// An representing the SQL Server Database project. + /// Path to the publish profile xml file + /// An that can be used to further customize the resource. + public static IResourceBuilder WithDacDeployOptions(this IResourceBuilder builder, string optionsPath) + => InternalWithDacDeployOptions(builder, optionsPath); + + /// + /// Adds a path to a publish profile for configuring dacpac deployment options to the . + /// + /// An representing the SQL Server Database project. + /// Path to the publish profile xml file + /// An that can be used to further customize the resource. + public static IResourceBuilder> WithDacDeployOptions(this IResourceBuilder> builder, string optionsPath) + where TPackage : IPackageMetadata => InternalWithDacDeployOptions(builder, optionsPath); + + internal static IResourceBuilder InternalWithDacDeployOptions(this IResourceBuilder builder, string optionsPath) + where TResource : IResourceWithDacpac + { + ArgumentNullException.ThrowIfNull(builder, nameof(builder)); + ArgumentNullException.ThrowIfNull(optionsPath); + + return builder + .WithAnnotation(new DacDeployOptionsAnnotation(optionsPath)); + } + /// /// Publishes the SQL Server Database project to the target . /// diff --git a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs index 61d80a63..05e54ce0 100644 --- a/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs +++ b/src/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects/SqlProjectResource.cs @@ -23,7 +23,7 @@ string IResourceWithDacpac.GetDacpacPath() var project = projectCollection.LoadProject(projectPath); - // .sqlprojx has a SqlTargetPath property, so try that first + // Microsoft.Build.Sql .sqlproj has a SqlTargetPath property, so try that first var targetPath = project.GetPropertyValue("SqlTargetPath"); if (string.IsNullOrWhiteSpace(targetPath)) { @@ -45,6 +45,19 @@ DacDeployOptions IResourceWithDacpac.GetDacpacDeployOptions() { var options = new DacDeployOptions(); + if (this.TryGetLastAnnotation(out var optionsAnnotation)) + { + var profile = DacProfile.Load(optionsAnnotation.OptionsPath); + + if (profile == null) + { + throw new InvalidOperationException($"Unable to load DacProfile from path {optionsAnnotation.OptionsPath} for resource {Name}."); + } + + options = profile.DeployOptions; + return options; + } + if (this.TryGetLastAnnotation(out var configureAnnotation)) { configureAnnotation.ConfigureDeploymentOptions(options); diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlPackageTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlPackageTests.cs index 62faf703..d957dad8 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlPackageTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlPackageTests.cs @@ -1,5 +1,6 @@ using Aspire.Hosting; using Microsoft.SqlServer.Dac; +using System.Configuration; namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests; @@ -91,4 +92,29 @@ public void AddSqlPackage_WithDeploymentOptions() var options = ((IResourceWithDacpac)sqlProjectResource).GetDacpacDeployOptions(); Assert.True(options.IncludeCompositeObjects); } + + [Fact] + public void AddSqlPackage_WithDeploymentOptions_FromFile() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + + var optionsPath = "Database.publish.xml"; + + appBuilder.AddSqlPackage("chinook").WithDacDeployOptions(optionsPath); + + // Act + using var app = appBuilder.Build(); + var appModel = app.Services.GetRequiredService(); + + // Assert + var sqlProjectResource = Assert.Single(appModel.Resources.OfType>()); + Assert.Equal("chinook", sqlProjectResource.Name); + + Assert.True(sqlProjectResource.TryGetLastAnnotation(out DacDeployOptionsAnnotation? dacDeployOptionsAnnotation)); + Assert.Equal(optionsPath, dacDeployOptionsAnnotation.OptionsPath); + + var options = ((IResourceWithDacpac)sqlProjectResource).GetDacpacDeployOptions(); + Assert.False(options.BlockOnPossibleDataLoss); + } } \ No newline at end of file diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs index ea4e0351..4cb10544 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlProjectTests.cs @@ -94,6 +94,55 @@ public void AddSqlProject_WithDeploymentOptions() Assert.True(options.IncludeCompositeObjects); } + [Fact] + public void AddSqlProject_WithDeploymentOptions_FromFile_NonExisting() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + + var optionsPath = "/folder/project.publish.xml"; + + appBuilder.AddSqlProject("MySqlProject").WithDacDeployOptions(optionsPath); + + // 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 DacDeployOptionsAnnotation? dacDeployOptionsAnnotation)); + Assert.Equal(optionsPath, dacDeployOptionsAnnotation.OptionsPath); + + Assert.Throws(() => ((IResourceWithDacpac)sqlProjectResource).GetDacpacDeployOptions()); + } + + [Fact] + public void AddSqlProject_WithDeploymentOptions_FromFile() + { + // Arrange + var appBuilder = DistributedApplication.CreateBuilder(); + + var optionsPath = "Database.publish.xml"; + + appBuilder.AddSqlProject("MySqlProject").WithDacDeployOptions(optionsPath); + + // 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 DacDeployOptionsAnnotation? dacDeployOptionsAnnotation)); + Assert.Equal(optionsPath, dacDeployOptionsAnnotation.OptionsPath); + + var options = ((IResourceWithDacpac)sqlProjectResource).GetDacpacDeployOptions(); + Assert.False(options.BlockOnPossibleDataLoss); + } + [Fact] public void WithReference_AddsRequiredServices() { diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests.csproj b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests.csproj index 32fdcb67..02800ae5 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests.csproj +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests.csproj @@ -10,4 +10,10 @@ + + + Always + + + diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/Database.publish.xml b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/Database.publish.xml new file mode 100644 index 00000000..d20ad984 --- /dev/null +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/Database.publish.xml @@ -0,0 +1,9 @@ + + + + Database + Database.sql + False + 1 + + \ No newline at end of file From 462258d9defae9bb2cd2457eb75958fdd4a4fd85 Mon Sep 17 00:00:00 2001 From: Erik Ejlskov Jensen Date: Mon, 16 Jun 2025 10:26:20 +0200 Subject: [PATCH 2/2] fix using --- .../AddSqlPackageTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlPackageTests.cs b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlPackageTests.cs index d957dad8..6d12ab02 100644 --- a/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlPackageTests.cs +++ b/tests/CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests/AddSqlPackageTests.cs @@ -1,6 +1,5 @@ using Aspire.Hosting; using Microsoft.SqlServer.Dac; -using System.Configuration; namespace CommunityToolkit.Aspire.Hosting.SqlDatabaseProjects.Tests;