The Beef.Database.Core
tool is a console application provided to automate the management of the SQL Server Database as part of the end-to-end development process.
This is tool automates three key elements:
- Migrations - being the upgrading of a database overtime using order-based migration scripts; the tool leverages the philosophy and NuGet packages of DbUp to enable.
- Schema - there are a number of database schema objects that can be managed outside of the above migrations, that are dropped and applied to the database using their native
Create
statement. - Data - there is data, for example Reference Data that needs to be applied to a database. This provides a simpler configuration than specifying the required SQL statements directly. This is also useful for setting up Master and Transaction data for the likes of testing scenarios.
As stated, DbUp is used enabling a database to be dropped, created and migrated. The migration is managed by tracking order-based migration scripts. It tracks which SQL scripts have been run already, and runs the change scripts that are needed to get the database up to date.
Over time there will be more than one script updating a single object, for example a Table
. In this case the first script operation will be a Create
, followed by subsequent Alter
operations. The scripts should be considered immutable, in that they cannot be changed once they have been applied; ongoing changes will need additional scripts.
The migration scripts must be marked as embedded resources, and reside under the Migrations
folder within the c# project. A naming convention should be used to ensure they are to be executed in the correct order; it is recommended that the name be prefixed by the date and time, following by a description of the purpose. For example: 20181218-081540-create-demo-person-table.sql
It is recommended that each script be enclosed by a transaction that can be rolled back in the case of error; otherwise, a script could be partially applied and will then need manual intervention to resolve.
There are some key schema objects that can be dropped and created overtime without causing side-effects. Equally, these objects can be code-generated reducing the effort to create and maintain over time. As such, these objects fall outside of the Migrations above.
The currently supported objects are (order specified implies order in which they are applied, and reverse when dropped to allow for dependencies):
The schema scripts must be marked as embedded resources, and reside under the Schema
folder within the c# project. Each script should only contain a single Create
statement. Each script will be parsed to determine type so that the appropriate order can be applied.
The Schema
folder is used to encourage the usage of database schemas. Therefore, directly under should be the schema name, for example dbo
or Ref
. Then sub-folders for the object types as per Azure Data Studio, for example Functions
, Stored Procedures
or Types\User-Defined Table Types
.
Code generation is also supported / enabled using the Beef Code-Gen capabilities. The tooling looks for the schema objects in the file system (as well as embedded resources) to allow for additions/changes during the code generation execution.
Data can be defined using YAML to enable simplified configuration that will be used to generate the required SQL statements to apply to the database.
The data specified follows a basic indenting/levelling rule to enable:
- Schema - specifies Schema name.
- Table - specifies the Table name within the Schema; this will be validated to ensure it exists within the database as the underlying table schema (columns) will be inferred. The underyling rows will be inserted by default, by prefixing with a
$
character a merge operation will be performed instead. - Rows - each row specifies the column name and the corresponding values (except for reference data described below). The tooling will parse each column value according to the underying SQL type.
Reference Data is treated as a special case; generally identified by being in the Ref
schema. The first column name and value pair are treated as the Code
and Text
columns. Also the IsActive
column will automatically be set to true
, and the SortOrder
column to the index (1-based) in which it is specified.
Where a column is a Reference Data reference the reference data code can be specified, with the identifier being determined at runtime (using a sub-query) as it is unlikely to be known at configuration time. The tooling determines this by the column name being suffixed by Id
and a corresponding table name in the Ref
schema; example GenderId
column and corresponding table Ref.Gender
.
Alternatively, a Reference Data reference could be the code itself, typically named XxxCode (e.g. GenderCode
). This has the advantage of decoupling the reference data references from the underlying identifier. Where data is persisted as JSON then the code is used; this would ensure consistency. The primary disadvantage is that the code absolutely becomes immutable and therefore not easily changed; for the most part this would not be an issue.
Example YAML configuration for merging reference data is as follows:
Ref:
- $Gender:
- M: Male
- F: Female
Example YAML configuration for inserting data (also inferring the GenderId
from the specified reference data code) is as follows:
Demo:
- Person:
- { FirstName: Wendy, LastName: Jones, Gender: F, Birthday: 1985-03-18 }
- { FirstName: Brian, LastName: Smith, Gender: M, Birthday: 1994-11-07 }
- { FirstName: Rachael, LastName: Browne, Gender: F, Birthday: 1972-06-28, Street: 25 Upoko Road, City: Wellington }
- { FirstName: Waylon, LastName: Smithers, Gender: M, Birthday: 1952-02-21 }
- WorkHistory:
- { PersonId: 2, Name: Telstra, StartDate: 2015-05-23, EndDate: 2016-04-06 }
- { PersonId: 2, Name: Optus, StartDate: 2016-04-16 }
To simplify the database management here are some further considerations that may make life easier over time; especially where you adopt the philosophy that the underlying busines logic (within the application APIs) is primarily responsible for the consistency of the data; and the data source (the database) is being largely used for storage and advanced query:
- Nullable everything - all columns (except) the primary key should be defined as nullable. The business logic should validate the request to ensure data is provided where mandatory. Makes changes to the database schema easier over time without this constraint.
- Minimise constraints - do not use database constraints unless absolutely necessary; only leverage where the database is the best and/or most efficient means to perform; i.e. uniqueness. The business logic should validate the request to ensure that any related data is provided, is valid and consistent.
- No cross-schema referencing - avoid referencing across
Schemas
where possible as this will impact the Migrations as part of this tooling; and we should not be using constraints as per prior point. Each schema is considered independent of others exceptdbo
orsec
(security where used). - Standardise column lengths - use a standard set of column lengths within the database and have the business logic manage the length constraint. As such the column length must be the same or greater that what is required.
- JSON for schema-less - where there is data that needs to be persisted, but rarely searched on, a schema-less approach should be considered such that a JSON object is persisted versus having to define columns. This can further simplify the database requirements where the data is hierarchical in nature. To enable the
ObjectToJsonConverter
should be used within the corresponding mapper (e.g.DatabasePropertyMapper
).
The Beef.Database.Core
can be executed as a console application directly; however, the experience has been optimised so that a new console application can reference and inherit the capabilities. Then simply add the Data
, Migrations
and Schema
folders and embed the required resources.
See the sample Beef.Demo.Database
as an example.
The following commands are automatically enabled for the console application:
Command | Description |
---|---|
Drop |
Drop the existing database (where it alredy exists). |
Create |
Create the database (where it does not already exist). |
Migrate |
Migrate the database using the Migrations scripts (those that have not already been executed). |
CodeGen |
Generates database Schema objects via code generation. |
Schema |
Drops and creates the known database Schema objects. |
Reset |
Resets the database by deleting all existing data. |
Data |
Inserts or merges Data from embedded YAML files. |
The remainder are common combinations of the above:
Command | Description |
---|---|
All |
Performs all commands as follows; Create , Migrate , CodeGen , Schema and Data . |
DropAndAll |
Performs Drop and All . |
ResetAndAll |
Performs Reset and All . |
Database |
Performs only the database commands as follows: Create , Migrate , Schema and Data . |
DropAndDatabase |
Performs Drop and Database . |
ResetAndDatabase |
Performs Reset and Database . |
There are multiple scripting options (create new script file in the Migrate
folder:
Command | Description |
---|---|
ScriptNew |
Creates a new (skeleton) script file using the defined naming convention. |
ScriptNew -create Schema.Table |
Creates a new table create script file for the named schema and table. |
ScriptNew -alter Schema.Table |
Creates a new table create script file for the named schema and table. |
The Program.cs
for the new console application should be updated similar to the following. The connection string is provided as the default used at runtime. An environment variable {Company}_{AppName}_ConnectionString
can be updated to override (any .
characters will be replaced with _
); or alternatively use the command line option -cs "connection-string-info"
.
public class Program
{
static int Main(string[] args)
{
return DatabaseConsoleWrapper.Create("Data Source=.;Initial atalog=Beef.Test;Integrated Security=True", "Beef", "Demo").Run(args);
}
}
To automatically added artefacts as embedded resources make the following change to your .csproj
file:
<ItemGroup>
<EmbeddedResource Include="Schema\**\*" />
<EmbeddedResource Include="Migrations\**\*" />
<EmbeddedResource Include="Data\**\*" />
</ItemGroup>
To run the console application, simply specify the required command; e.g:
dotnet run dropandall
dotnet run all
dotnet run database -cs "Data Source=.;Initial atalog=Beef.Test;Integrated Security=True"
dotnet run scriptnew -create Ref.Eyecolor