Skip to content

Instantly share code, notes, and snippets.

@zola-25
Created May 7, 2020 22:46
Show Gist options
  • Save zola-25/2a006d269efa309d312655f1256fb2a5 to your computer and use it in GitHub Desktop.
Save zola-25/2a006d269efa309d312655f1256fb2a5 to your computer and use it in GitHub Desktop.
Stop EF Core creating expensive Azure SQL Database configurations on start up & auto connection string builder based on Git branch
using Enis.Domain.Abstractions.StartupServices;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
public interface IConnectionStringBuilder
{
string GetApplicationConnectionString();
}
public class ConnectionStringBuilder : IConnectionStringBuilder
{
private readonly IConfiguration _configuration;
private readonly IHostingEnvironment _hostingEnvironment;
private readonly IGitBranchFinder _gitBranchFinder;
public ConnectionStringBuilder(IConfiguration configuration, IHostingEnvironment hostingEnvironment, IGitBranchFinder gitBranchFinder)
{
_configuration = configuration;
_hostingEnvironment = hostingEnvironment;
_gitBranchFinder = gitBranchFinder;
}
public string GetApplicationConnectionString()
{
string connString = _configuration.GetConnectionString("YOUR_CONNECTION_STRING_APPSETTING"); ;
// If no connection string set in Environment Variables or app setttings, and running locally, guess with branch:
if (connString == null && _hostingEnvironment.IsEnvironment("Local"))
{
connString = GetDevelopmentDatabaseConnectionString();
}
return connString;
}
private string GetDevelopmentDatabaseConnectionString()
{
string branchNameLastPart = _gitBranchFinder.GetBranchNameLastPart();
string password = _configuration.GetValue<string>("YOUR_DEV_SQL_SERVER_PASSWORD")
return
$"Server=tcp:[*your Azure SQL Server*].database.windows.net,1433;Initial Catalog=[*your dev DB prefix convention*]-{branchNameLastPart};Persist Security Info=False;User ID=[*your-user-id*];Password={password};MultipleActiveResultSets=False;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;";
}
}
using System.Data.SqlClient;
using System.Linq;
public interface IDevelopmentDatabaseCreator
{
void CreateDevelopmentDatabase(string connectionString);
}
public class DevelopmentDatabaseCreator : IDevelopmentDatabaseCreator
{
public void CreateDevelopmentDatabase(string connectionString)
{
var builder = new SqlConnectionStringBuilder(connectionString);
string dbToCreate = builder.InitialCatalog;
string masterDbConnectionString = connectionString.Replace(dbToCreate, "master"); // Ideally would use the SqlConnectionStringBuilder to just replace the db name with master, but when the ConnectionString property is called on the builder it always returns a connection string with a DataSource=... property instead of Server=..., which for some reason doesn't work on Azure
using (var conn = new SqlConnection(masterDbConnectionString))
{
conn.Open();
using (var command = new SqlCommand())
{
command.Connection = conn;
CheckDbName(dbToCreate); // You can't use parameters when using SQL DDL like CREATE DATABASE, so have to validate SQL manually
command.CommandTimeout = 60;
command.CommandText = $@"
IF NOT EXISTS (SELECT * FROM sys.databases WHERE name = '{dbToCreate}')
BEGIN
CREATE DATABASE [{dbToCreate}] (
MAXSIZE=2 GB,
EDITION='Standard',
SERVICE_OBJECTIVE='S0')
END";
command.ExecuteNonQuery();
}
}
}
private void CheckDbName(string dbName)
{
if (dbName.Any(c =&gt; !(Char.IsLetterOrDigit(c) || c == '-')))
{
throw new Exception("DB name is invalid");
}
}
}
using System;
using System.Diagnostics;
public interface IGitBranchFinder
{
string GetFullBranchName();
string GetBranchNameLastPart();
}
public class GitBranchFinder : IGitBranchFinder
{
public string GetFullBranchName()
{
ProcessStartInfo startInfo = new ProcessStartInfo("git.exe");
startInfo.UseShellExecute = false;
startInfo.WorkingDirectory = Environment.CurrentDirectory;
startInfo.RedirectStandardInput = true;
startInfo.RedirectStandardOutput = true;
startInfo.Arguments = "rev-parse --abbrev-ref HEAD";
using (var process = new Process())
{
process.StartInfo = startInfo;
process.Start();
string branchname = process.StandardOutput.ReadLine();
return branchname;
}
}
public string GetBranchNameLastPart()
{
string fullBranchName = GetFullBranchName();
return fullBranchName.Contains("/") ? fullBranchName.Remove(0, fullBranchName.LastIndexOf('/') + 1) : fullBranchName;
}
}
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment appEnv)
{
_configuration = configuration;
_currentEnvironment = appEnv;
}
public IConfiguration _configuration;
private IHostingEnvironment _currentEnvironment { get; set; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
//Startup services
services.AddSingleton<IHostingEnvironment>(_currentEnvironment);
services.AddTransient<IConnectionStringBuilder, ConnectionStringBuilder>();
services.AddTransient<IGitBranchFinder, GitBranchFinder>();
services.AddTransient<IDevelopmentDatabaseCreator, DevelopmentDatabaseCreator>();
var sp = services.BuildServiceProvider();
var connectionStringBuilder = sp.GetService<IConnectionStringBuilder>();
var connString = connectionStringBuilder.GetApplicationConnectionString();
services.AddDbContext<AppDbContext>(o =>
o.UseSqlServer(
connString
));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, AppDbContext dbContext, IDevelopmentDatabaseCreator devDatabaseCreator, IConnectionStringBuilder connectionStringBuilder)
{
// ...after the rest of your app configuration:
if(_currentEnvironment.IsDevelopment() || _currentEnvironment.IsEnvironment("Local"))
{
var connString = connectionStringBuilder.GetApplicationConnectionString();
devDatabaseCreator.CreateDevelopmentDatabase(connString);
}
dbContext.Database.Migrate();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment