There isn't a built in mechanism for this that I am aware of. But you can role your own:
Create a separate model assembly:
If you find yourself changing your model a lot then I would recommend that you create your model as a separate assembly. So create a new library project in your solution and move your model there. Then reference the project in your main project. This is good organisation practise anyway.
Then in your Properties/AssemblyInfo.cs
(of the model) ensure that AssemblyVersion
is set with wildcard build number and remove the [assembly: AssemblyFileVersion ...]
if it exists.
[assembly: AssemblyVersion("1.0.*")]
So my model: class looks like this:
using System;
using ServiceStack.Model;
using ServiceStack.DataAnnotations;
namespace Blog
{
public static class Model
{
public class Article : IHasId<int>
{
[AutoIncrement, PrimaryKey]
public int Id { get; set; }
...
Notice that I use an outer static class Model
. This makes all my tables easy to reference in my project.
Create a method to detect changes to the model assembly:
Now that we have an assembly thats version number will automatically increment when we make new builds of it, we need to be able to detect changes to the assembly in our application so we can recreate the tables.
The code below does the following:
Determines the type of our Model assembly. Because it can then use reflection to determine the version number of the current assembly.
Check the application configuration settings for the last created database model version.
If it doesn't find the configuration setting or the version numbers don't match a database connection is resolved.
The tables with the Model are then inferred from the assembly. The advantage of this is we can add more tables to our Model assembly and never have to change the drop/create code.
The database drops and creates the tables.
The new assembly version number is saved.
If you restart your application without altering your model, the database will persist, however make a change and restart and the database with be recreated.
public static void Init(Funq.Container container)
{
...
CheckDatabaseModel(typeof(Model));
}
public void CheckDatabaseModel(Type modelType)
{
// Get the referenced model version
string modelVersion = Assembly.GetAssembly(modelType).GetName().Version.ToString();
// Determine the last model version number from the configuration
var config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
var lastModelVersion = config.AppSettings.Settings["DatabaseModelVersion"];
// Determine if the model has changed
if(lastModelVersion == null || lastModelVersion.Value != modelVersion)
{
Console.WriteLine("Model has changed");
using(var db = Resolve<IDbConnectionFactory>().OpenDbConnection())
{
// Drop and recreate the tables
// Determine the tables from the model assembly
var tables = modelType.GetNestedTypes();
db.DropAndCreateTables(tables);
// Repopulate database with initial data.
// Save the model version to settings
if(lastModelVersion == null)
config.AppSettings.Settings.Add("DatabaseModelVersion", modelVersion);
else
config.AppSettings.Settings["DatabaseModelVersion"].Value = modelVersion;
config.Save(ConfigurationSaveMode.Modified);
}
} else {
// The model numbers matched
Console.WriteLine("Model is current");
}
}
Dealing with table creation order
Your database will probably have foreign key constraints and you will then find you need to create tables in a certain order, or the database won't be happy.
When you manually created the table array for your db.DropAndCreateTables
before you would specify the creation order to satisfy any constraints. But because we are using modelTypes.GetNestedTypes()
the order is no longer in our control. There are a couple of ways to tackle this problem.
1: Disable Key Check Constraints (Not Recommended)
The most basic, would be instruct our database to ignore constraints, while we do the table creation. In MySQL the code would be:
db.ExecuteSql("SET foreign_key_checks = 0;");
db.DropAndCreateTables(tables);
db.ExecuteSql("SET foreign_key_checks = 1;");
The code required in MSSQL or other databases will vary, and in some may not be possible. But that is ultimately a dangerous way to do things. The constraints are there for a reason, after all.
2: Define the Table Creation order in our Model (Recommended):
We can create a simple attribute that we decorate our tables with that tells our database setup code what order to do things in. The advantage of this is that we don't have to turn of constraints, and it's clear to maintainers the order things will happen in.
The Attribute:
public class TableCreationOrderAttribute : Attribute
{
public int Order { get; private set; }
public TableCreationOrderAttribute(int order)
{
Order = order;
}
}
Decorating the Model:
public static class Model
{
[TableCreationOrder(3)]
public class Article : IHasId<int>
{
[AutoIncrement, PrimaryKey]
public int Id { get; set; }
...
So now we need to tell database setup code how to use this order to create the tables correctly. Replace this line db.DropAndCreateTables(tables);
with this:
var orderedTables = new Dictionary<int, Type>();
var unorderedTables = new List<Type>(); // Tables without the attribute will be created last, but in no specific order
foreach(var table in tables)
{
var order = Attribute.GetCustomAttribute(table, typeof(TableCreationOrderAttribute)) as TableCreationOrderAttribute;
if(order != null)
orderedTables.Add(order.Order, table);
else
unorderedTables.Add(table);
}
foreach(var table in orderedTables.OrderBy(t=>t.Key))
db.DropAndCreateTable(table.Value);
foreach(var table in unorderedTables)
db.DropAndCreateTable(table);
Summary
It may seem a lot but it's not really, the CheckDatabaseModel
method can be shrunk down to less than 35 lines of code. It's generic, so you can add it to a utilities library and reuse it again using a single call in other projects. You will never have to worry about manually causing triggering the database to refresh.
Includes a simplified step by step guide, as this answer includes a lot of additional explaination.
Is there a way to get a list all tables within the Database by using ServiceStack's version of ORMLite?
- Built into ORMLite directly: No.
- You can use
db.ExecSql
to query the database for this information. Each database has a different command to do this. It would certainly be possible using raw SQL this way.
Hope this helps.