diff --git a/src/Orchard.Tests/Data/Migrations/DatabaseMigrationManagerTests.cs b/src/Orchard.Tests/Data/Migrations/DatabaseMigrationManagerTests.cs
new file mode 100644
index 000000000..25c7039dd
--- /dev/null
+++ b/src/Orchard.Tests/Data/Migrations/DatabaseMigrationManagerTests.cs
@@ -0,0 +1,136 @@
+using System;
+using System.Data.SqlClient;
+using System.IO;
+using System.Threading;
+using NUnit.Framework;
+using Orchard.Data.Migrations;
+using Orchard.Environment;
+using Orchard.Tests.Records;
+
+namespace Orchard.Tests.Data.Migrations {
+ [TestFixture]
+ public class DatabaseMigrationManagerTests {
+ private string _tempDataFolder;
+
+ [SetUp]
+ public void Init() {
+ var tempFilePath = Path.GetTempFileName();
+ File.Delete(tempFilePath);
+ Directory.CreateDirectory(tempFilePath);
+ _tempDataFolder = tempFilePath;
+ }
+
+ [TearDown]
+ public void Term() {
+ try { Directory.Delete(_tempDataFolder, true); }
+ catch (IOException) { }
+ }
+
+ private static void CreateSqlServerDatabase(string databasePath) {
+ var databaseName = Path.GetFileNameWithoutExtension(databasePath);
+ using (var connection = new SqlConnection(
+ "Data Source=.\\SQLEXPRESS;Initial Catalog=tempdb;Integrated Security=true;User Instance=True;")) {
+ connection.Open();
+ using (var command = connection.CreateCommand()) {
+ command.CommandText =
+ "CREATE DATABASE " + databaseName +
+ " ON PRIMARY (NAME=" + databaseName +
+ ", FILENAME='" + databasePath.Replace("'", "''") + "')";
+ command.ExecuteNonQuery();
+
+ command.CommandText =
+ "EXEC sp_detach_db '" + databaseName + "', 'true'";
+ command.ExecuteNonQuery();
+ }
+ }
+ }
+
+ [Test]
+ public void MigrationManagerShouldCreateEmptySQLiteDatabaseAtGivenLocation() {
+ var manager = (IDatabaseMigrationManager)new DatabaseMigrationManager();
+ var coordinator = manager.CreateCoordinator("SQLite", _tempDataFolder, "");
+
+ coordinator.CreateDatabase();
+
+ Assert.That(File.Exists(Path.Combine(_tempDataFolder, "Orchard.db")), Is.True);
+ }
+
+ [Test, ExpectedException(typeof(NotImplementedException))]
+ public void MigrationManagerShouldNotImplementTheCreationOfSqlServer() {
+ var manager = (IDatabaseMigrationManager)new DatabaseMigrationManager();
+ var coordinator = manager.CreateCoordinator("SqlServer", _tempDataFolder, "");
+
+ coordinator.CreateDatabase();
+
+ }
+
+ [Test]
+ public void CanConnectShouldBeFalseWhenSqlServerIsInvalid() {
+ var manager = (IDatabaseMigrationManager)new DatabaseMigrationManager();
+ var coordinator = manager.CreateCoordinator("SqlServer", _tempDataFolder, "Data Source=.\\SQLEXPRESS;Initial Catalog=Hello");
+ Assert.That(coordinator.CanConnect(), Is.False);
+ }
+
+ [Test]
+ public void CanConnectShouldBeTrueWhenValidSqlServerMdfIsTargetted() {
+ var databasePath = Path.Combine(_tempDataFolder, "Orchard.mdf");
+ CreateSqlServerDatabase(databasePath);
+
+ var manager = (IDatabaseMigrationManager)new DatabaseMigrationManager();
+ var coordinator = manager.CreateCoordinator("SqlServer", _tempDataFolder, "Data Source=.\\SQLEXPRESS;AttachDbFileName=" + databasePath + ";Integrated Security=True;User Instance=True;");
+ Assert.That(coordinator.CanConnect(), Is.True);
+ }
+
+ [Test]
+ public void SQLiteSchemaShouldBeGeneratedAndUsable() {
+ var manager = (IDatabaseMigrationManager) new DatabaseMigrationManager();
+ var coordinator = manager.CreateCoordinator("SQLite", _tempDataFolder, "");
+
+ var recordDescriptors = new[] {
+ new RecordDescriptor {Prefix = "Hello", Type = typeof (Foo)}
+ };
+
+ coordinator.UpdateSchema(recordDescriptors);
+
+ var sessionFactory = coordinator.BuildSessionFactory(recordDescriptors);
+
+ var session = sessionFactory.OpenSession();
+ var foo = new Foo {Name = "hi there"};
+ session.Save(foo);
+ session.Flush();
+ session.Close();
+
+ Assert.That(foo, Is.Not.EqualTo(0));
+
+ sessionFactory.Close();
+
+ }
+
+ [Test]
+ public void SqlServerSchemaShouldBeGeneratedAndUsable() {
+ var databasePath = Path.Combine(_tempDataFolder, "Orchard.mdf");
+ CreateSqlServerDatabase(databasePath);
+
+ var manager = (IDatabaseMigrationManager)new DatabaseMigrationManager();
+ var coordinator = manager.CreateCoordinator("SqlServer", _tempDataFolder, "Data Source=.\\SQLEXPRESS;AttachDbFileName=" + databasePath + ";Integrated Security=True;User Instance=True;");
+
+ var recordDescriptors = new[] {
+ new RecordDescriptor {Prefix = "Hello", Type = typeof (Foo)}
+ };
+
+ coordinator.UpdateSchema(recordDescriptors);
+
+ var sessionFactory = coordinator.BuildSessionFactory(recordDescriptors);
+
+ var session = sessionFactory.OpenSession();
+ var foo = new Foo { Name = "hi there" };
+ session.Save(foo);
+ session.Flush();
+ session.Close();
+
+ Assert.That(foo, Is.Not.EqualTo(0));
+
+ sessionFactory.Close();
+ }
+ }
+}
diff --git a/src/Orchard.Tests/Orchard.Tests.csproj b/src/Orchard.Tests/Orchard.Tests.csproj
index bb5c16483..fddacf379 100644
--- a/src/Orchard.Tests/Orchard.Tests.csproj
+++ b/src/Orchard.Tests/Orchard.Tests.csproj
@@ -135,6 +135,7 @@
Code
+
diff --git a/src/Orchard/Data/Migrations/DatabaseCoordinatorBase.cs b/src/Orchard/Data/Migrations/DatabaseCoordinatorBase.cs
new file mode 100644
index 000000000..0d1d3bf66
--- /dev/null
+++ b/src/Orchard/Data/Migrations/DatabaseCoordinatorBase.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using FluentNHibernate.Automapping;
+using FluentNHibernate.Automapping.Alterations;
+using FluentNHibernate.Cfg;
+using FluentNHibernate.Cfg.Db;
+using FluentNHibernate.Conventions.Helpers;
+using NHibernate;
+using NHibernate.Tool.hbm2ddl;
+using Orchard.ContentManagement.Records;
+using Orchard.Data.Conventions;
+using Orchard.Environment;
+
+namespace Orchard.Data.Migrations {
+ public abstract class DatabaseCoordinatorBase : IDatabaseCoordinator {
+ protected abstract IPersistenceConfigurer GetPersistenceConfigurer();
+
+ public virtual bool CanConnect() {
+ try {
+ var sessionFactory = Fluently.Configure()
+ .Database(GetPersistenceConfigurer())
+ .BuildSessionFactory();
+ try {
+ // attempting to open a session validates a connection can be made
+ var session = sessionFactory.OpenSession();
+ session.Close();
+ }
+ finally {
+ sessionFactory.Close();
+ }
+ return true;
+ }
+ catch {
+ return false;
+ }
+ }
+
+ public virtual void CreateDatabase() {
+ // creating a session factory appears to be sufficient for causing a database file to be created for inplace providers
+ var sessionFactory = Fluently.Configure()
+ .Database(GetPersistenceConfigurer())
+ .BuildSessionFactory();
+ sessionFactory.Close();
+ }
+
+ public void UpdateSchema(IEnumerable recordDescriptors) {
+ var configuration = Fluently.Configure()
+ .Database(GetPersistenceConfigurer())
+ .Mappings(m => m.AutoMappings.Add(CreatePersistenceModel(recordDescriptors)))
+ .BuildConfiguration();
+
+ var updater = new SchemaUpdate(configuration);
+ updater.Execute(true /*script*/, true /*doUpdate*/);
+ }
+
+ public ISessionFactory BuildSessionFactory(IEnumerable recordDescriptors) {
+ return Fluently.Configure()
+ .Database(GetPersistenceConfigurer())
+ .Mappings(m => m.AutoMappings.Add(CreatePersistenceModel(recordDescriptors)))
+ .BuildSessionFactory();
+ }
+
+ static AutoPersistenceModel CreatePersistenceModel(IEnumerable recordDescriptors) {
+ return AutoMap.Source(new TypeSource(recordDescriptors))
+ // Ensure that namespaces of types are never auto-imported, so that
+ // identical type names from different namespaces can be mapped without ambiguity
+ .Conventions.Setup(x => x.Add(AutoImport.Never()))
+ .Conventions.Add(new RecordTableNameConvention(recordDescriptors))
+ .Alterations(alt => {
+ foreach (var recordAssembly in recordDescriptors.Select(x => x.Type.Assembly).Distinct()) {
+ alt.Add(new AutoMappingOverrideAlteration(recordAssembly));
+ }
+ alt.AddFromAssemblyOf();
+ alt.Add(new ContentItemAlteration(recordDescriptors));
+ })
+ .Conventions.AddFromAssemblyOf();
+ }
+
+ class TypeSource : ITypeSource {
+ private readonly IEnumerable _recordDescriptors;
+
+ public TypeSource(IEnumerable recordDescriptors) { _recordDescriptors = recordDescriptors; }
+
+ public IEnumerable GetTypes() { return _recordDescriptors.Select(descriptor => descriptor.Type); }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Data/Migrations/DatabaseMigrationManager.cs b/src/Orchard/Data/Migrations/DatabaseMigrationManager.cs
new file mode 100644
index 000000000..bde875b95
--- /dev/null
+++ b/src/Orchard/Data/Migrations/DatabaseMigrationManager.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Orchard.Data.Migrations {
+ public class DatabaseMigrationManager : IDatabaseMigrationManager {
+ public IDatabaseCoordinator CreateCoordinator(string provider, string dataFolder, string connectionString) {
+ if (string.Equals(provider, "SQLite", StringComparison.InvariantCultureIgnoreCase))
+ return new SQLiteDatabaseCoordinator(dataFolder, connectionString);
+ return new SqlServerDatabaseCoordinator(dataFolder, connectionString);
+ }
+ }
+}
diff --git a/src/Orchard/Data/Migrations/IDatabaseCoordinator.cs b/src/Orchard/Data/Migrations/IDatabaseCoordinator.cs
new file mode 100644
index 000000000..38239f75f
--- /dev/null
+++ b/src/Orchard/Data/Migrations/IDatabaseCoordinator.cs
@@ -0,0 +1,19 @@
+using System.Collections.Generic;
+using NHibernate;
+using Orchard.Environment;
+
+namespace Orchard.Data.Migrations {
+ public interface IDatabaseCoordinator {
+ bool CanConnect();
+ void CreateDatabase();
+
+ ///
+ /// Should only be called in a development or evaluation environment. Automatic schema migration
+ /// not a really safe practice on production data sources.
+ ///
+ /// Set of known records to be applied
+ void UpdateSchema(IEnumerable recordDescriptors);
+
+ ISessionFactory BuildSessionFactory(IEnumerable recordDescriptors);
+ }
+}
diff --git a/src/Orchard/Data/Migrations/IDatabaseMigrationManager.cs b/src/Orchard/Data/Migrations/IDatabaseMigrationManager.cs
new file mode 100644
index 000000000..921d7e1fb
--- /dev/null
+++ b/src/Orchard/Data/Migrations/IDatabaseMigrationManager.cs
@@ -0,0 +1,5 @@
+namespace Orchard.Data.Migrations {
+ public interface IDatabaseMigrationManager {
+ IDatabaseCoordinator CreateCoordinator(string provider, string dataFolder, string connectionString);
+ }
+}
diff --git a/src/Orchard/Data/Migrations/SQLiteDatabaseCoordinator.cs b/src/Orchard/Data/Migrations/SQLiteDatabaseCoordinator.cs
new file mode 100644
index 000000000..2c90f231c
--- /dev/null
+++ b/src/Orchard/Data/Migrations/SQLiteDatabaseCoordinator.cs
@@ -0,0 +1,26 @@
+using System.IO;
+using FluentNHibernate.Cfg.Db;
+
+namespace Orchard.Data.Migrations {
+ public class SQLiteDatabaseCoordinator : DatabaseCoordinatorBase {
+ private readonly string _dataFolder;
+ private readonly string _connectionString;
+
+ public SQLiteDatabaseCoordinator(string dataFolder, string connectionString) {
+ _dataFolder = dataFolder;
+ _connectionString = connectionString;
+ }
+
+ protected override IPersistenceConfigurer GetPersistenceConfigurer() {
+ var persistence = SQLiteConfiguration.Standard;
+ if (string.IsNullOrEmpty(_connectionString)) {
+ persistence = persistence.UsingFile(Path.Combine(_dataFolder, "Orchard.db"));
+ }
+ else {
+ persistence = persistence.ConnectionString(_connectionString);
+ }
+ return persistence;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Data/Migrations/SqlServerDatabaseCoordinator.cs b/src/Orchard/Data/Migrations/SqlServerDatabaseCoordinator.cs
new file mode 100644
index 000000000..0df8cb0bd
--- /dev/null
+++ b/src/Orchard/Data/Migrations/SqlServerDatabaseCoordinator.cs
@@ -0,0 +1,26 @@
+using System;
+using FluentNHibernate.Cfg.Db;
+
+namespace Orchard.Data.Migrations {
+ public class SqlServerDatabaseCoordinator : DatabaseCoordinatorBase {
+ private readonly string _dataFolder;
+ private readonly string _connectionString;
+
+ public SqlServerDatabaseCoordinator(string dataFolder, string connectionString) {
+ _dataFolder = dataFolder;
+ _connectionString = connectionString;
+ }
+
+
+ protected override IPersistenceConfigurer GetPersistenceConfigurer() {
+ var persistence = MsSqlConfiguration.MsSql2008;
+ if (string.IsNullOrEmpty(_connectionString)) {
+ throw new NotImplementedException();
+ }
+ else {
+ persistence = persistence.ConnectionString(_connectionString);
+ }
+ return persistence;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Orchard/Orchard.csproj b/src/Orchard/Orchard.csproj
index 5a29de59b..f5d0b0b99 100644
--- a/src/Orchard/Orchard.csproj
+++ b/src/Orchard/Orchard.csproj
@@ -133,6 +133,12 @@
+
+
+
+
+
+