Created
April 30, 2013 06:44
-
-
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)
This file contains 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.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