I have just created a database and done my first migration (just a simple table add). Now I want to add some stored procedures which I have just added by writing the sql an
I will try to provide a different perspective because having SQL code within C# strings is not very appealing and one should expect to change such scripts within a tool that provides intellisense (e.g. SSMS).
The following solution is implemented within a ASP.NET Core 2.0 Web API project.
Maintain procedures in the development database using any convenient tool
Generate procedures scripts:
public class ProcedureItemMetadata
{
///
/// SQL server side object identifier
///
[Key]
public int ObjectId { get; set; }
///
/// schema name
///
public string SchemaName { get; set; }
///
/// procedure name
///
public string Name { get; set; }
///
/// procedure body
///
public string Definition { get; set; }
}
public string GetProceduresScript()
{
var query = Context.ProcedureItemMetadata.AsNoTracking().FromSql(@"
SELECT ao.object_id as ObjectId, SCHEMA_NAME(ao.schema_id) as SchemaName, ao.name, sm.definition
FROM sys.all_objects ao
JOIN sys.sql_modules sm ON sm.object_id = ao.object_id
WHERE ao.type = 'P'
and execute_as_principal_id IS NULL
order by 1;");
var list = query.ToList();
string text = string.Join($" {Base.Constants.General.ScriptGeneratorSeparator}\n", list.Select(p => p.Definition));
// replace create with create or alter
string replaced = Regex.Replace(text,
@"(?CREATE\s+PROCEDURE\s+)",
"CREATE OR ALTER PROCEDURE ",
RegexOptions.IgnoreCase);
return replaced;
}
This is a manual process, but allows to obtain procedures whenever their development is ready. Also, it can easily be extended to other types of objects (e.g. views).
Create a folder within solution to hold scripts to be run at application startup (e.g. _SQL)
Copy generated script within the folder (e.g. all_procedures.sql)
One advantage of storing scripts like this is that the IDE might automatically validate the syntax + highlight stuff etc.
Create "seed" code to automatically run when application starts
private static void EnsureSqlObjects(CustomContext context)
{
string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "_Sql");
foreach (var file in Directory.GetFiles(path, "*.sql"))
{
string fileText = File.ReadAllText(file);
// escaping { } for those rare cases when sql code contains {..}
// as ExecuteSqlCommand tries to replace them with params values
fileText = fileText.Replace("{", "{{");
fileText = fileText.Replace("}", "}}");
// splitting objects (cannot run more than one DDL in a command)
string[] ddlParts = fileText.Split(Base.Constants.General.ScriptGeneratorSeparator, StringSplitOptions.RemoveEmptyEntries);
foreach (string ddl in ddlParts)
{
context.Database.ExecuteSqlCommand(ddl);
}
}
}
This approach allows for any idempotent scripts that are not easily maintained through migrations to be managed.