Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5ad0d72
first pass at #8876
yorek Apr 19, 2025
c4d6734
introduced WithSku to manage database SKU
yorek Apr 21, 2025
d2257c1
Improved sku comparison code
yorek Apr 21, 2025
ff1fa57
changes as per comments
yorek Apr 21, 2025
4202efa
formatting improvements
yorek Apr 21, 2025
d090962
Update src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs
yorek Apr 22, 2025
9acc9ad
get is now public, added test
yorek Apr 22, 2025
177b074
Merge branch 'azuresqldb-8876' of https://github.com/yorek/aspire int…
yorek Apr 22, 2025
a6de371
Update src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs
yorek Apr 23, 2025
73d5043
Move private member up in the class
sebastienros Apr 24, 2025
e0d27f7
Update src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs
yorek Apr 26, 2025
d63a90c
Update src/Aspire.Hosting.Azure.Sql/README.md
yorek Apr 26, 2025
56acbd7
Update src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs
yorek Apr 26, 2025
93847b8
Update src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs
yorek Apr 26, 2025
d5b3c65
Update src/Aspire.Hosting.Azure.Sql/README.md
yorek Apr 26, 2025
a0119a3
Fix missing xml tag
sebastienros Apr 29, 2025
ee95da2
Merge branch 'dotnet:main' into azuresqldb-8876
yorek May 7, 2025
9ff69c1
removed WithSku and added WithDefaultAzureSku
yorek May 7, 2025
d99070d
Updated readme
yorek May 7, 2025
76e5648
Made UseDefaultAzureSku all internal
yorek May 7, 2025
89ecbfe
Fix AzureResourceOptionsCanBeConfigured test
sebastienros May 9, 2025
a891929
Fix AddContainerAppEnvironmentWorksWithSqlServer test
sebastienros May 9, 2025
de52a70
Merge remote-tracking branch 'origin/main' into azuresqldb-8876
sebastienros May 9, 2025
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
16 changes: 16 additions & 0 deletions src/Aspire.Hosting.Azure.Sql/AzureSqlDatabaseResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ namespace Aspire.Hosting.Azure;
public class AzureSqlDatabaseResource(string name, string databaseName, AzureSqlServerResource parent)
: Resource(name), IResourceWithParent<AzureSqlServerResource>, IResourceWithConnectionString
{
/// <summary>
/// Free Azure SQL database offer
/// </summary>
public const string FREE_SKU = "Free";

private string _skuName = FREE_SKU;

/// <summary>
/// Gets the parent Azure SQL Database (server) resource.
/// </summary>
Expand All @@ -32,6 +39,15 @@ public class AzureSqlDatabaseResource(string name, string databaseName, AzureSql
/// </summary>
public string DatabaseName { get; } = ThrowIfNullOrEmpty(databaseName);

/// <summary>
/// Gets or Sets the database SKU name
/// </summary>
internal string SkuName
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be public

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made the get public and kept the set internal, as we want to make sure developers use the WithSku method to set the sku, right?

{
get { return _skuName; }
set { ThrowIfNullOrEmpty(value); _skuName = value; }
}

/// <summary>
/// Gets the inner SqlServerDatabaseResource resource.
///
Expand Down
92 changes: 75 additions & 17 deletions src/Aspire.Hosting.Azure.Sql/AzureSqlExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ namespace Aspire.Hosting;
/// Provides extension methods for adding the Azure SQL resources to the application model.
/// </summary>
public static class AzureSqlExtensions
{
{
[Obsolete]
private static IResourceBuilder<SqlServerServerResource> PublishAsAzureSqlDatabase(this IResourceBuilder<SqlServerServerResource> builder, bool useProvisioner)
{
Expand Down Expand Up @@ -97,8 +97,9 @@ public static IResourceBuilder<AzureSqlServerResource> AddAzureSqlServer(this ID
/// <param name="builder">The builder for the Azure SQL resource.</param>
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
/// <param name="databaseName">The name of the database. If not provided, this defaults to the same value as <paramref name="name"/>.</param>
/// <param name="skuName">SKU of the database. If not provided, this defaults to the free database tier.<paramref name="name"/>.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<AzureSqlDatabaseResource> AddDatabase(this IResourceBuilder<AzureSqlServerResource> builder, [ResourceName] string name, string? databaseName = null)
public static IResourceBuilder<AzureSqlDatabaseResource> AddDatabase(this IResourceBuilder<AzureSqlServerResource> builder, [ResourceName] string name, string? databaseName = null, string skuName = AzureSqlDatabaseResource.FREE_SKU)
{
ArgumentNullException.ThrowIfNull(builder);
ArgumentException.ThrowIfNullOrEmpty(name);
Expand All @@ -108,8 +109,9 @@ public static IResourceBuilder<AzureSqlDatabaseResource> AddDatabase(this IResou

var azureResource = builder.Resource;
var azureSqlDatabase = new AzureSqlDatabaseResource(name, databaseName, azureResource);
azureSqlDatabase.SkuName = skuName;

builder.Resource.AddDatabase(name, databaseName);
builder.Resource.AddDatabase(azureSqlDatabase);

if (azureResource.InnerResource is null)
{
Expand All @@ -127,6 +129,18 @@ public static IResourceBuilder<AzureSqlDatabaseResource> AddDatabase(this IResou
}
}

/// <summary>
/// Configures the Azure SQL Database to be deployed with the specified SKU
/// </summary>
/// <param name="builder">The builder for the Azure SQL resource.</param>
/// <param name="skuName">SKU of the database. If not provided, this defaults to the free database tier.</param>
/// <returns>A reference to the <see cref="IResourceBuilder{T}"/>.</returns>
public static IResourceBuilder<AzureSqlDatabaseResource> WithSku(this IResourceBuilder<AzureSqlDatabaseResource> builder, string skuName)
{
builder.Resource.SkuName = skuName;
return builder;
}

/// <summary>
/// Configures an Azure SQL Database (server) resource to run locally in a container.
/// </summary>
Expand Down Expand Up @@ -176,7 +190,7 @@ public static IResourceBuilder<AzureSqlServerResource> RunAsContainer(this IReso
throw new InvalidOperationException($"Could not find a {nameof(AzureSqlDatabaseResource)} with name {database.Key}.");
}

var innerDb = sqlContainer.AddDatabase(database.Key, database.Value);
var innerDb = sqlContainer.AddDatabase(database.Key, database.Value.DatabaseName);
existingDb.SetInnerResource(innerDb.Resource);
}

Expand All @@ -195,9 +209,63 @@ private static void RemoveAzureResources(IDistributedApplicationBuilder appBuild
}

private static void CreateSqlServer(
AzureResourceInfrastructure infrastructure,
AzureResourceInfrastructure infrastructure,
IDistributedApplicationBuilder distributedApplicationBuilder,
IReadOnlyDictionary<string, string> databases)
{
var sqlServer = CreateSqlServerResourceOnly(infrastructure, distributedApplicationBuilder);

foreach (var database in databases)
{
var bicepIdentifier = Infrastructure.NormalizeBicepIdentifier(database.Key);
var databaseName = database.Value;
var sqlDatabase = new SqlDatabase(bicepIdentifier)
{
Parent = sqlServer,
Name = databaseName,
};

sqlDatabase.Sku = new SqlSku() { Name = "GP_S_Gen5_2" };
sqlDatabase.UseFreeLimit = true;
sqlDatabase.FreeLimitExhaustionBehavior = FreeLimitExhaustionBehavior.AutoPause;

infrastructure.Add(sqlDatabase);
}
}

private static void CreateSqlServer(
AzureResourceInfrastructure infrastructure,
IDistributedApplicationBuilder distributedApplicationBuilder,
IReadOnlyDictionary<string, AzureSqlDatabaseResource> databases)
{
var sqlServer = CreateSqlServerResourceOnly(infrastructure, distributedApplicationBuilder);

foreach (var database in databases)
{
var bicepIdentifier = Infrastructure.NormalizeBicepIdentifier(database.Key);
var databaseName = database.Value.DatabaseName;
var sqlDatabase = new SqlDatabase(bicepIdentifier)
{
Parent = sqlServer,
Name = databaseName,
};

if (database.Value.SkuName == AzureSqlDatabaseResource.FREE_SKU)
{
sqlDatabase.Sku = new SqlSku() { Name = "GP_S_Gen5_2" };
sqlDatabase.UseFreeLimit = true;
sqlDatabase.FreeLimitExhaustionBehavior = FreeLimitExhaustionBehavior.AutoPause;
} else
{
sqlDatabase.Sku = new SqlSku() { Name = database.Value.SkuName };
}

infrastructure.Add(sqlDatabase);
}
}

private static SqlServer CreateSqlServerResourceOnly(AzureResourceInfrastructure infrastructure,
IDistributedApplicationBuilder distributedApplicationBuilder)
{
var azureResource = (AzureSqlServerResource)infrastructure.AspireResource;

Expand Down Expand Up @@ -263,22 +331,12 @@ private static void CreateSqlServer(
});
}

foreach (var databaseNames in databases)
{
var bicepIdentifier = Infrastructure.NormalizeBicepIdentifier(databaseNames.Key);
var databaseName = databaseNames.Value;
var sqlDatabase = new SqlDatabase(bicepIdentifier)
{
Parent = sqlServer,
Name = databaseName
};
infrastructure.Add(sqlDatabase);
}

infrastructure.Add(new ProvisioningOutput("sqlServerFqdn", typeof(string)) { Value = sqlServer.FullyQualifiedDomainName });

// We need to output name to externalize role assignments.
infrastructure.Add(new ProvisioningOutput("name", typeof(string)) { Value = sqlServer.Name });

return sqlServer;
}

internal static SqlServerAzureADAdministrator AddActiveDirectoryAdministrator(AzureResourceInfrastructure infra, SqlServer sqlServer, BicepValue<Guid> principalId, BicepValue<string> principalName)
Expand Down
8 changes: 4 additions & 4 deletions src/Aspire.Hosting.Azure.Sql/AzureSqlServerResource.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Aspire.Hosting.Azure;
/// </summary>
public class AzureSqlServerResource : AzureProvisioningResource, IResourceWithConnectionString
{
private readonly Dictionary<string, string> _databases = new Dictionary<string, string>(StringComparers.ResourceName);
private readonly Dictionary<string, AzureSqlDatabaseResource> _databases = new Dictionary<string, AzureSqlDatabaseResource>(StringComparers.ResourceName);
private readonly bool _createdWithInnerResource;

/// <summary>
Expand Down Expand Up @@ -77,11 +77,11 @@ public ReferenceExpression ConnectionStringExpression
/// <summary>
/// A dictionary where the key is the resource name and the value is the database name.
/// </summary>
public IReadOnlyDictionary<string, string> Databases => _databases;
public IReadOnlyDictionary<string, AzureSqlDatabaseResource> Databases => _databases;

internal void AddDatabase(string name, string databaseName)
internal void AddDatabase(AzureSqlDatabaseResource db)
{
_databases.TryAdd(name, databaseName);
_databases.TryAdd(db.Name, db);
}

internal void SetInnerResource(SqlServerServerResource innerResource)
Expand Down
20 changes: 19 additions & 1 deletion src/Aspire.Hosting.Azure.Sql/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Aspire.Hosting.Azure.Sql library

Provides extension methods and resource definitions for a .NET Aspire AppHost to configure Azure SQL Server.
Provides extension methods and resource definitions for a .NET Aspire AppHost to configure Azure SQL DB.

## Getting started

Expand Down Expand Up @@ -54,6 +54,24 @@ The `WithReference` method configures a connection in the `MyService` project na
builder.AddSqlServerClient("sqldata");
```

## Azure SQL DB defaults

Unless otherwise specified, the Azure SQL DB created will be a 2vCores General Purpose Serverless database (GP_S_Gen5_2) with the free offer enabled.

Read more about the free offer here: [Deploy Azure SQL Database for free](https://learn.microsoft.com/en-us/azure/azure-sql/database/free-offer?view=azuresql)

The free offer is configured so that when the maximum usage limit is reached, the database is stopped to avoid incurring in unexpected costs.

If you want don't want to use the free offer and instead deploy the database with the service level of your choice, specify the SKU name when adding the database resource:

```csharp
var sql = builder.AddAzureSqlServer("sql")
.AddDatabase("db", "my-db-name").WithSku("HS_Gen5_2");

var myService = builder.AddProject<Projects.MyService>()
.WithReference(sql);
```

## Additional documentation

* https://learn.microsoft.com/dotnet/framework/data/adonet/sql/
Expand Down
Loading
Loading