mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-01-22 21:02:08 +08:00
Adding IDatabaseMigrationManager and IDatabaseCoordinator implementations
Starting point for controlled creation and upgrade of databases and schema Only automatic schema population currently supported SqlServer and SQLite providers implemented --HG-- branch : dev
This commit is contained in:
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -135,6 +135,7 @@
|
|||||||
<SubType>Code</SubType>
|
<SubType>Code</SubType>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="DataUtility.cs" />
|
<Compile Include="DataUtility.cs" />
|
||||||
|
<Compile Include="Data\Migrations\DatabaseMigrationManagerTests.cs" />
|
||||||
<Compile Include="Data\RepositoryTests.cs" />
|
<Compile Include="Data\RepositoryTests.cs" />
|
||||||
<Compile Include="Data\StubLocator.cs" />
|
<Compile Include="Data\StubLocator.cs" />
|
||||||
<Compile Include="Environment\DefaultCompositionStrategyTests.cs" />
|
<Compile Include="Environment\DefaultCompositionStrategyTests.cs" />
|
||||||
|
|||||||
88
src/Orchard/Data/Migrations/DatabaseCoordinatorBase.cs
Normal file
88
src/Orchard/Data/Migrations/DatabaseCoordinatorBase.cs
Normal file
@@ -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<RecordDescriptor> 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<RecordDescriptor> recordDescriptors) {
|
||||||
|
return Fluently.Configure()
|
||||||
|
.Database(GetPersistenceConfigurer())
|
||||||
|
.Mappings(m => m.AutoMappings.Add(CreatePersistenceModel(recordDescriptors)))
|
||||||
|
.BuildSessionFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
static AutoPersistenceModel CreatePersistenceModel(IEnumerable<RecordDescriptor> 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<DataModule>();
|
||||||
|
alt.Add(new ContentItemAlteration(recordDescriptors));
|
||||||
|
})
|
||||||
|
.Conventions.AddFromAssemblyOf<DataModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class TypeSource : ITypeSource {
|
||||||
|
private readonly IEnumerable<RecordDescriptor> _recordDescriptors;
|
||||||
|
|
||||||
|
public TypeSource(IEnumerable<RecordDescriptor> recordDescriptors) { _recordDescriptors = recordDescriptors; }
|
||||||
|
|
||||||
|
public IEnumerable<Type> GetTypes() { return _recordDescriptors.Select(descriptor => descriptor.Type); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/Orchard/Data/Migrations/DatabaseMigrationManager.cs
Normal file
11
src/Orchard/Data/Migrations/DatabaseMigrationManager.cs
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
src/Orchard/Data/Migrations/IDatabaseCoordinator.cs
Normal file
19
src/Orchard/Data/Migrations/IDatabaseCoordinator.cs
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using NHibernate;
|
||||||
|
using Orchard.Environment;
|
||||||
|
|
||||||
|
namespace Orchard.Data.Migrations {
|
||||||
|
public interface IDatabaseCoordinator {
|
||||||
|
bool CanConnect();
|
||||||
|
void CreateDatabase();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should only be called in a development or evaluation environment. Automatic schema migration
|
||||||
|
/// not a really safe practice on production data sources.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="recordDescriptors">Set of known records to be applied</param>
|
||||||
|
void UpdateSchema(IEnumerable<RecordDescriptor> recordDescriptors);
|
||||||
|
|
||||||
|
ISessionFactory BuildSessionFactory(IEnumerable<RecordDescriptor> recordDescriptors);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/Orchard/Data/Migrations/IDatabaseMigrationManager.cs
Normal file
5
src/Orchard/Data/Migrations/IDatabaseMigrationManager.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace Orchard.Data.Migrations {
|
||||||
|
public interface IDatabaseMigrationManager {
|
||||||
|
IDatabaseCoordinator CreateCoordinator(string provider, string dataFolder, string connectionString);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Orchard/Data/Migrations/SQLiteDatabaseCoordinator.cs
Normal file
26
src/Orchard/Data/Migrations/SQLiteDatabaseCoordinator.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Orchard/Data/Migrations/SqlServerDatabaseCoordinator.cs
Normal file
26
src/Orchard/Data/Migrations/SqlServerDatabaseCoordinator.cs
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -133,6 +133,12 @@
|
|||||||
<Compile Include="ContentManagement\Handlers\RemoveContentContext.cs" />
|
<Compile Include="ContentManagement\Handlers\RemoveContentContext.cs" />
|
||||||
<Compile Include="ContentManagement\Handlers\VersionContentContext.cs" />
|
<Compile Include="ContentManagement\Handlers\VersionContentContext.cs" />
|
||||||
<Compile Include="Data\Conventions\RecordTableNameConvention.cs" />
|
<Compile Include="Data\Conventions\RecordTableNameConvention.cs" />
|
||||||
|
<Compile Include="Data\Migrations\DatabaseCoordinatorBase.cs" />
|
||||||
|
<Compile Include="Data\Migrations\DatabaseMigrationManager.cs" />
|
||||||
|
<Compile Include="Data\Migrations\IDatabaseCoordinator.cs" />
|
||||||
|
<Compile Include="Data\Migrations\IDatabaseMigrationManager.cs" />
|
||||||
|
<Compile Include="Data\Migrations\SQLiteDatabaseCoordinator.cs" />
|
||||||
|
<Compile Include="Data\Migrations\SqlServerDatabaseCoordinator.cs" />
|
||||||
<Compile Include="Environment\Configuration\ShellSettingsLoader.cs" />
|
<Compile Include="Environment\Configuration\ShellSettingsLoader.cs" />
|
||||||
<Compile Include="Environment\ExtensibleInterceptionModule.cs" />
|
<Compile Include="Environment\ExtensibleInterceptionModule.cs" />
|
||||||
<Compile Include="Environment\ShellBuilders\DefaultShellContainerFactory.cs" />
|
<Compile Include="Environment\ShellBuilders\DefaultShellContainerFactory.cs" />
|
||||||
|
|||||||
Reference in New Issue
Block a user