There's three C# MongoDB drivers, and I've been using NoRM (for yet another weak reason: Rob Conery is an active contributor). NoRM is easy to install and somewhat well documented (the tutorial screencast is great). Its LINQ-to-Mongo implementation is great when it works, but you must be familiar with MongoDB in order to avoid writing perfectly good-looking LINQ queries that will fail miserably at runtime (e.g. MongoDB does not natively support OrderBy or Distinct).
Anyways. I recently ran into a situation where I really needed to access MongoDB's Clone command during runtime. I was surprised to discover it wasn't implemented yet. No worries - NoRM has an extensible architecture.
We'll implement the native Clone command. The regular MongoDB syntax is
db.copyDatabase(<from_dbname>, <to_dbname>);
The "command" syntax, which is runnable from any driver (and is the one we want), is
db.runCommand( { copydb : 1,
fromdb : <from_dbname>,
todb : <to_dbname>,
fromhost : <from_hostname> } );
First, we add a new method to the interface:
public interface IMongoAdmin : IDisposable
{
bool CloneDatabase(string sourceDatabase,
string destinationDatabase,
string host);
}
Next we add an empty implementation so the compiler won't complain:
public class MongoAdmin : IMongoAdmin
{
///
/// Clones the database.
///
///A boolean that indicates whether the cloning operation was successful or not.
///The source database.
///The destination database.
///The destination database host.
public bool CloneDatabase(string sourceDatabase,
string destinationDatabase,
string host = "")
{
return false;
}
}
Now we can write our unit test:
[Fact]
public void ClonesDatabase()
{
// arrange: create sourceDb with a fake object
const string sourceDb = "sourceDb";
string sourceDbConnectionString = TestHelper.ConnectionString(null, sourceDb, null, null);
var fakeObject = new FakeObject();
using (var mongo = Mongo.Create(sourceDbConnectionString))
{
mongo.GetCollection<FakeObject>().Insert(fakeObject);
}
// act: create destinationDb as clone of sourceDb
const string destinationDb = "destinationDb";
string destinationDbConnectionString = TestHelper.ConnectionString(null, destinationDb, null, null);
using (var admin = new MongoAdmin(TestHelper.ConnectionString(null, "admin", null, null)))
{
admin.CloneDatabase(sourceDb, destinationDb);
}
// assert: verify that destinationDb exists and contains fake object
using (var mongo = Mongo.Create(destinationDbConnectionString))
{
Assert.Equal(fakeObject.Id, mongo.GetCollection<FakeObject>().AsQueryable().Single().Id);
}
// cleanup: drop both databases
using (var admin = new MongoAdmin(sourceDbConnectionString))
{
admin.DropDatabase();
}
using (var admin = new MongoAdmin(destinationDbConnectionString))
{
admin.DropDatabase();
}
}
Our unit test fails. Great. Now let's fill in the method body:
public class MongoAdmin : IMongoAdmin
{
public bool CloneDatabase(string sourceDatabase,
string destinationDatabase,
string host = "")
{
AssertConnectedToAdmin();
return Database.GetCollection<BaseStatusMessage>("$cmd")
.FindOne(
new CloneDatabaseRequest {
SourceDatabaseName = sourceDatabase,
DestinationDatabaseName = destinationDatabase,
Host = host
}
)
.WasSuccessful;
}
}
Notice lines 8-10. We're telling NoRM to send a request to MongoDB and expect a message back. In other words, we're stuffing our clone command in an envelope (CloneDatabaseRequest) and sending it to MongoDB, hoping to get the desired response (BaseStatusMessage). Most of the NoRM commands follow this pattern. It makes it quite easy to add new functionality to NoRM - pretty clever!
CloneDatabaseRequest has several properties that map directly to the native MongoDB clone command. In other words, it takes care of assigning our C# parameters (sourceDatabase, destinationDatabase, host) to the MongoDB parameters (fromdb, todb, fromhost):
/// <summary>
/// The clone database request.
/// </summary>
internal class CloneDatabaseRequest : ISystemQuery
{
/// <summary>
/// Initializes the <see cref="CloneDatabaseRequest"/> class.
/// </summary>
static CloneDatabaseRequest()
{
MongoConfiguration.Initialize(c => c.For<CloneDatabaseRequest>(a =>
{
a.ForProperty(auth => auth.CloneDatabase).UseAlias("copydb");
a.ForProperty(auth => auth.SourceDatabaseName).UseAlias("fromdb");
a.ForProperty(auth => auth.DestinationDatabaseName).UseAlias("todb");
a.ForProperty(auth => auth.Host).UseAlias("fromhost");
})
);
}
public int CloneDatabase
{
get { return 1; }
}
public string SourceDatabaseName { get; set; }
public string DestinationDatabaseName { get; set; }
private string _host = "";
public string Host
{
get { return _host; }
set { _host = value; }
}
}
That's it - done! Here's an example of cloning in action - I'm backing up the dev database and dropping it during a SpecFlow initialization process:
[BeforeFeature]
public static void BeforeFeature()
{
// clear test
using (var admin = new MongoAdmin(ApplicationSettings.GetConnectionString(Db.Test)))
{
admin.DropDatabase();
}
// copy dev to test
using (var admin = new MongoAdmin(ApplicationSettings.GetConnectionString(Db.Admin)))
{
admin.CloneDatabase(ApplicationSettings.Dev, ApplicationSettings.Test);
}
// clear dev
using (var admin = new MongoAdmin(ApplicationSettings.GetConnectionString(Db.Dev)))
{
admin.DropDatabase();
}
}
You can get the above bits here.