PM > Install-Package RavenMigrations
Raven Migrations is a migration framework for RavenDB to help with common tasks you might have to do over time to your database. The framework API is heavily influenced by Fluent Migrator.
We believe any changes to your domain should be visible in your code and reflected as such. Changing things "on the fly", can lead to issues, where as migrations can be tested and throughly vetted before being exposed into your production environment. With RavenDB testing migrations is super simple since RavenDB supports in memory databases (our test suite is in memory).
This is important, once a migration is in your production environment, NEVER EVER modify it in your code. Treat a migration like a historical record of changes.
Every migration has several elements you need to be aware of. Additionally, there are over arching concepts that will help you structure your project to take advantage of this library.
A migration looks like the following:
// #1
[Migration(1)]
public class First_Migration : Migration // #2
{
// #3
public override void Up()
{
using (var session = DocumentStore.OpenSession())
{
session.Store(new TestDocument { Name = "Khalid Abuhakmeh" });
session.SaveChanges();
}
}
// #4
public override void Down()
{
DocumentStore.DatabaseCommands.DeleteByIndex(new TestDocumentIndex().IndexName, new IndexQuery());
}
}
Each important part of the migration is numbered:
- Every migration has to be decorated with the MigrationAttribute, and needs to be seeded it with a *long value. We recommend you seed it with a DateTime stamp of yyyyMMddHHmmss ex. 20131031083545. This helps keeps teams for guessing and conflicting on the next migration number.
- Every migration needs to implement from the base class of Migration. This gives you access to base functionality and the ability to implement Up and Down.
- Up is the method that occurs when a migration is executed. As you see above, we are adding a document.
- Down is the method that occurs when a migration is rolledback. This is not always possible, but if it is, then it most likely will be the reverse of Up.
In every migration you have access to the document store, so you are able to do anything you need to your storage engine. This document store is the same as the one your application will use.
Raven Migrations comes with a migration runner. It scans all provided assemblies for any classes implementing the Migration base class and then orders them according to their migration value.
After each migration is executed, a document of type MigrationDocument is inserted into your database, to insure the next time the runner is executed that migration is not executed again. When a migration is rolled back the document is removed.
You can modify the runner options by declaring a MigrationOptions instance and passing it to the runner.
public class MigrationOptions
{
public MigrationOptions()
{
Direction = Directions.Up;
Assemblies = new List<Assembly>();
Profiles = new List<string>();
MigrationResolver = new DefaultMigrationResolver();
Assemblies = new List<Assembly>();
ToVersion = 0;
Logger = new ConsoleLogger();
}
public Directions Direction { get; set; }
public IList<Assembly> Assemblies { get; set; }
public IList<string> Profiles { get; set; }
public IMigrationResolver MigrationResolver { get; set; }
public long ToVersion { get; set; }
public ILogger Logger { get; set; }
}
We understand there are times when you want to run specific migrations in certain environments, so Raven Migrations supports profiles. For instance, some migrations might only run during development, by decorating your migration with the profile of "development" and setting the options to include the profile will execute that migration.
[Migration(3, "development")]
public class Development_Migration : Migration
{
public override void Up()
{
using (var session = DocumentStore.OpenSession())
{
session.Store(new { Id = "development-1" });
session.SaveChanges();
}
}
}
Runner.Run(store, new MigrationOptions { Profiles = new[] { "development" } });
You can also specify that a particular profile belongs in more than one profile by setting multiple profile names in the attribute.
[Migration(3, "development", "demo")]
This migration would run if either (or both) the development and demo profiles were specified in the MigrationOptions.
Raven Migrations lets you migrate at the RavenJObject level, giving full access to the document and metadata. This closely follows Ayende's approach porting the MVC Music Store.
Alter.Collection
works on a collection and gives access to the document and metadata:
Alter.Collection("People", (doc, metadata) => { ... });
Batching changes is taken care of with the default batch size being 128. You can change the batch size if needed:
public void Collection(string tag, Action<RavenJObject, RavenJObject> action, int pageSize = 128)
Let's say you start using a single property:
public class Person
{
public string Id { get; set; }
public string Name { get; set; }
}
But then want to change using two properties:
public class Person
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
You now need to migrating your documents or you will lose data when you load your new Person
. The following migration uses Alter.Collection
to split out the first and last names:
[Migration(1)]
public class PersonNameMigration : Migration
{
public override void Down()
{
Alter.Collection("People", MigratePerson2ToPerson1);
}
public override void Up()
{
Alter.Collection("People", MigratePerson1ToPerson2);
}
private void MigratePerson2ToPerson1(RavenJObject doc, RavenJObject metadata)
{
var first = doc.Value<string>("FirstName");
var last = doc.Value<string>("LastName");
doc["Name"] = first + " " + last;
doc.Remove("FirstName");
doc.Remove("LastName");
}
private void MigratePerson1ToPerson2(RavenJObject doc, RavenJObject metadata)
{
var name = doc.Value<string>("Name");
if (!string.IsNullOrEmpty(name))
{
doc["FirstName"] = name.Split(' ')[0];
doc["LastName"] = name.Split(' ')[1];
}
doc.Remove("Name");
}
}
Alter.CollectionWithAdditionalCommands
works just like Alter.Collection
except the function you pass to it
must return an IEnumerable<ICommandData>
. These are additional RavenDb commands that will be applied in the same transaction
as the document of the collection you are modifying. So, if anything goes wrong, you can be sure that no documents of the
collection were changed without also doing the corresponding "additional" changes. An example scenario: you want to migrate a field
from one document to a different document. There are two things that need to happen: 1. remove the old field, 2. set the new field
on the other document. With this helper method, you can batch both of those commands in the same transaction, so they'll both either
pass or fail together. An example of an additional command:
new PutCommand {
Document = new RavenJObject(),
Key = "foobar/1",
Metadata = new RavenJObject()
}
Let's say that you refactor and move Person
to another assembly. So that RavenDB will load the data into the new class, you will need to adjust the metadata in the collection for the new CLR type.
[Migration(2)]
public class MovePersonMigration : Migration
{
public override void Up()
{
Alter.Collection("People",
(doc, metadata) =>
{
metadata[Constants.RavenClrType] = "MyProject.Person, MyProject";
});
}
public override void Down()
{
Alter.Collection("People",
(doc, metadata) =>
{
metadata[Constants.RavenClrType] = "MyProject.Domain.Person, Domain";
});
}
}
If the long
version number does not fit your versioning scheme, a custom
attribute can inherit from MigrationAttribute
.
Example implementation for semantic versions:
public class MigrationVersionAttribute : MigrationAttribute
{
public MigrationVersionAttribute(int major, int minor, int patch, int migration, params string [] profiles)
:base(CreateVersionNumber(major, minor, patch, migration), profiles)
{
}
private static long CreateVersionNumber(int major, int minor, int patch, int migration)
{
return major*100000000000L + minor*10000000L + patch*1000L + migration;
}
}
}
Example usage in a migration:
[MigrationVersion(6, 9, 11, 1)]
public class CustomVersionMigration : Migration
{
public override void Up() { /* ... */ }
}
Specify a logger when configuring the runner.
By default the ConsoleLogger
is used.
Implement the RavenMigrations.ILogger
interface to create your own logger:
void WriteInformation(string format, params object[] args);
void WriteError(string format, params object[] args);
void WriteWarning(string format, params object[] args);
We suggest you run the migrations at the start of your application to ensure that any new changes you have made apply to your application before you application starts. If you do not want to do it here, you can choose to do it out of band using a seperate application.
We recommend you create a folder called Migrations, then name your files according to the migration long value. For example
\Migrations
- 20131010120000_FirstMigration.cs
- 20131010120001_SecondMigration.cs
- 20131110120001_ThirdMigration.cs
- etc....
The advantage to this approach, is that your IDE will order the migrations alpha-numerically allowing you to easily find the first and last migration.
- If you use a domain model in your migration, be prepared for that migration to break if properties are removed critical to the migration. There are ways to be safe about breaking migrations. One approach is to use RavenJObject instead of your domain types.
Contributions of any size are always welcome! Please read our Code of Conduct and Contribution Guide and then jump in!
Thanks goes to Sean Kearon who helped dog food this migration framework and contribute to it.
This project strives to adhere to the semver guidelines. See the contributing and maintaining guides for more on this.