Created
May 7, 2020 22:46
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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;"; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 => !(Char.IsLetterOrDigit(c) || c == '-'))) | |
{ | |
throw new Exception("DB name is invalid"); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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