Skip to content

Instantly share code, notes, and snippets.

@darkiri
Created April 30, 2013 06:44
Show Gist options
  • Save darkiri/5486994 to your computer and use it in GitHub Desktop.
Save darkiri/5486994 to your computer and use it in GitHub Desktop.
Some ideas for MongoDB schema migrations using ISupportInitialize (based on https://github.com/darkiri/mongo-csharp-migrations)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
namespace MongoDB.Migrations
{
public class MigratableObject : ISupportInitialize
{
private const string VERSION_ELEMENT_NAME = "_v";
private const string VERSION_ZERO = "0.0.0.0";
private readonly IVersionDetectionStrategy _versionDetectionStrategy;
private readonly Dictionary<Version, Action<object, IDictionary<string, object>>> _migrations;
public IDictionary<string, object> ExtraElements;
public MigratableObject(IVersionDetectionStrategy versionDetectionStrategy,
Dictionary<Version, Action<object, IDictionary<string, object>>> migrations)
{
ExtraElements = new Dictionary<string, object>
{
{VERSION_ELEMENT_NAME, versionDetectionStrategy.GetCurrentVersion()}
};
_versionDetectionStrategy = versionDetectionStrategy;
_migrations = migrations;
}
public void BeginInit()
{
// _v was added in constructor, need to remove it for deserialization
if (null != ExtraElements) ExtraElements.Clear();
}
public void EndInit()
{
string objectVersion;
if (null != ExtraElements && ExtraElements.ContainsKey(VERSION_ELEMENT_NAME))
{
objectVersion = (string) ExtraElements[VERSION_ELEMENT_NAME];
ExtraElements.Remove(VERSION_ELEMENT_NAME);
}
else objectVersion = VERSION_ZERO;
RunUpgrades(new Version(objectVersion), this, ExtraElements);
}
private void RunUpgrades(Version objectVersion, object obj, IDictionary<string, object> extraElements)
{
foreach (var migratableVesion in _migrations.Keys)
{
try
{
if (objectVersion < migratableVesion &&
migratableVesion <= _versionDetectionStrategy.GetCurrentVersion())
{
var upgrade = _migrations[migratableVesion];
upgrade(obj, extraElements);
}
}
catch (Exception e)
{
throw new MigrationException(obj.GetType(), migratableVesion, e);
}
}
}
}
public static class SomewhereInTheInfrastructure
{
private static readonly Dictionary<Type, Dictionary<Version, Action<object, IDictionary<string, object>>>>
_migrationForAllTypes =
new Dictionary<Type, Dictionary<Version, Action<object, IDictionary<string, object>>>>();
public static void ExtractMigrations(Type[] migratableTypes)
{
foreach (var type in migratableTypes)
{
_migrationForAllTypes[type] = ExtractMigrations(type);
}
}
public static Dictionary<Version, Action<object, IDictionary<string, object>>> GetMigrations(Type type)
{
return _migrationForAllTypes.ContainsKey(type)
? _migrationForAllTypes[type]
: new Dictionary<Version, Action<object, IDictionary<string, object>>>();
}
private static Dictionary<Version, Action<object, IDictionary<string, object>>> ExtractMigrations(Type classType)
{
var migrationTypes = classType
.GetCustomAttributes(typeof (MigrationAttribute), false)
.Cast<MigrationAttribute>()
.Select(a => a.MigrationType)
.ToArray();
var migrationInterface = typeof (IMigration<>);
migrationInterface = migrationInterface.MakeGenericType(new[] {classType});
if (migrationTypes.Any(t => t.GetInterfaces().All(i => i != migrationInterface)))
throw new ArgumentException("One of migration types is not a subclass of " + migrationInterface.Name);
var migrations = migrationTypes
.Select(Activator.CreateInstance)
.Select(m => new {To = ExtractVersion(m), Upgrade = BuildLambda(m, classType)})
.OrderBy(m => m.To);
var duplicate = migrations
.GroupBy(m => m.To)
.FirstOrDefault(g => g.Count() > 1);
if (duplicate != null)
throw new MigrationException(classType, duplicate.First().To);
return migrations.ToDictionary(m => m.To, m => m.Upgrade);
}
private static Version ExtractVersion(object migration)
{
return (Version) migration.GetType().GetProperty("To").GetValue(migration);
}
private static Action<object, IDictionary<string, object>> BuildLambda(object migration, Type objectType)
{
var migrationMethod = migration
.GetType()
.GetMethod("Upgrade", new[] {objectType, typeof (IDictionary<string, object>)});
var objParameter = Expression.Parameter(typeof (object), "obj");
var extraElementsParameter = Expression.Parameter(typeof (IDictionary<string, object>), "extraElements");
var typedParameter = Expression.Convert(objParameter, objectType);
var migrationCall = Expression.Call(
Expression.Constant(migration),
migrationMethod,
new Expression[] {typedParameter, extraElementsParameter});
return
Expression.Lambda<Action<object, IDictionary<string, object>>>(migrationCall,
objParameter,
extraElementsParameter).Compile();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment