One of the usual things in Software development is creating the database with all the tables,views,stored procedures and anything related . For Aspire 9.1 with Sql Ser,this involved a coordination between some shell scripts – see the code :
var sqlserver = builder.AddSqlServer("sqlserver",paramPass,1433)
.WithDbGate()
// Mount the init scripts directory into the container.
.WithBindMount("./sqlserverconfig","/usr/config")
// Mount the SQL scripts directory into the container so that the init scripts run.
.WithBindMount("../../Scripts/data/sqlserver","/docker-entrypoint-initdb.d")
// Run the custom entrypoint script on startup.
.WithEntrypoint("/usr/config/entrypoint.sh")
The scripts where in the mount binding. Also,some .gitattributes files was required with
* text=auto *.sh text eol=lf *.mod text eol=lf *.sum text eol=lf
to can execute the .sh entry point. An painfull orchestration just for create tables
For Aspire 9.2 this has been simplified a bit,because of Creation Script for database
var db = sqlserver.AddDatabase("MyDB")
.WithCreationScript("....")
However,this is has some flaws :
1 .Executing in the timeout of an sql
2,Executing just the last one
You can find the code at SqlServerBuilderExtensions.cs :
private static async Task CreateDatabaseAsync(SqlConnection sqlConnection,SqlServerDatabaseResource sqlDatabase,IServiceProvider serviceProvider,CancellationToken ct)
{
try
{
var scriptAnnotation = sqlDatabase.Annotations.OfType<SqlServerCreateDatabaseScriptAnnotation>().LastOrDefault();
if (scriptAnnotation?.Script == null)
{
var quotedDatabaseIdentifier = new SqlCommandBuilder().QuoteIdentifier(sqlDatabase.DatabaseName);
using var command = sqlConnection.CreateCommand();
command.CommandText = $"IF ( NOT EXISTS ( SELECT 1 FROM sys.databases WHERE name = @DatabaseName ) ) CREATE DATABASE {quotedDatabaseIdentifier};";
command.Parameters.Add(new SqlParameter("@DatabaseName",sqlDatabase.DatabaseName));
await command.ExecuteNonQueryAsync(ct).ConfigureAwait(false);
}
else
{
using var reader = new StringReader(scriptAnnotation.Script);
var batchBuilder = new StringBuilder();
while (reader.ReadLine() is { } line)
{
var matchGo = GoStatements().Match(line);
if (matchGo.Success)
{
// Execute the current batch
var count = matchGo.Groups["repeat"].Success ? int.Parse(matchGo.Groups["repeat"].Value,CultureInfo.InvariantCulture) : 1;
var batch = batchBuilder.ToString();
for (var i = 0; i < count; i++)
{
using var command = sqlConnection.CreateCommand();
command.CommandText = batch;
await command.ExecuteNonQueryAsync(ct).ConfigureAwait(false);
So I made my own extension,
[GeneratedRegex(@"^\s*GO(?<repeat>\s+\d{1,6})?(\s*\-{2,}.*)?\s*$",RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)]
internal static partial Regex GoStatements();
public static IResourceBuilder<SqlServerDatabaseResource> ExecuteSqlServerScripts(this IResourceBuilder<SqlServerDatabaseResource> db,params IEnumerable<string> sqlScripts)
{
var builder = db.ApplicationBuilder;
builder.Eventing.Subscribe<ResourceReadyEvent>(async (ev,ct) =>
{
if (ev.Resource is not SqlServerDatabaseResource dbRes) return;
if (db.Resource != dbRes) return;
var cn = await dbRes.ConnectionStringExpression.GetValueAsync(ct);
if (cn == null) return;
using var sqlConnection = new SqlConnection();
sqlConnection.ConnectionString = cn;
await sqlConnection.OpenAsync(ct);
foreach (var item in sqlScripts)
{
using var reader = new StringReader(item);
var batchBuilder = new StringBuilder();
while (reader.ReadLine() is { } line)
{
var matchGo = GoStatements().Match(line);
if (matchGo.Success)
{
// Execute the current batch
var count = matchGo.Groups["repeat"].Success ? int.Parse(matchGo.Groups["repeat"].Value,CultureInfo.InvariantCulture) : 1;
var batch = batchBuilder.ToString();
for (var i = 0; i < count; i++)
{
using var command = sqlConnection.CreateCommand();
command.CommandText = batch;
//TODO: modify the timeout
command.CommandTimeout = 120;
try
{
await command.ExecuteNonQueryAsync(ct).ConfigureAwait(false);
}
catch (Exception ex)
{
//TODO: log
Console.WriteLine($"!!!!Error in executing {batch} : {ex.Message}");
}
}
batchBuilder.Clear();
}
else
{
// Prevent batches with only whitespace
if (!string.IsNullOrWhiteSpace(line))
{
batchBuilder.AppendLine(line);
}
}
}
// Process the remaining batch lines
if (batchBuilder.Length > 0)
{
using var command = sqlConnection.CreateCommand();
var batch = batchBuilder.ToString();
command.CommandText = batch;
//TODO: modify the timeout
try
{
await command.ExecuteNonQueryAsync(ct).ConfigureAwait(false);
}
catch (Exception ex)
{
//TODO: log
Console.WriteLine($"!!!!Error in executing {batch} : {ex.Message}");
}
}
}
});
return db;
}
And can be executed like this:
var db = sqlserver.AddDatabase("MyDB")
.WithSqlPadViewerForDB(sqlserver)
.ExecuteSqlServerScripts(DBFiles.FilesToCreate)
Leave a Reply