Files
Umbraco-CMS/src/Umbraco.Core/DatabaseContext.cs
Shannon Deminick a0e4492a35 New implementation of IDatabaseFactory, allows setting the DatabaseContext.Current at runtime
with a custom IDatabaseFactory (mostly for testing).
Changes AuditTrail to internal so the public way is just with the 'Audit' class.
Fixed ThreadSafetyServiceTests which was failing with the new AuditTrail stuff because of the
Database instances, this is not solved with the new PerThreadDatabaseFactory for the unit test.
Created new 'UmbracoDatabase' object which inherits from the PetaPoco one so that we can future proof
the implementation as we might want some custom logic on there. Now the IDatabaseFactory returns
an UmbracoDatabase instead of just Database.
2012-12-12 03:47:04 +05:00

350 lines
15 KiB
C#

using System;
using System.Configuration;
using System.Data.SqlServerCe;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Configuration;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Migrations;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core
{
/// <summary>
/// The Umbraco Database context
/// </summary>
/// <remarks>
/// One per AppDomain, represents the Umbraco database
/// </remarks>
public class DatabaseContext
{
private readonly IDatabaseFactory _factory;
private bool _configured;
private string _connectionString;
private string _providerName;
#region Singleton
private static DatabaseContext _customContext = null;
private static readonly Lazy<DatabaseContext> lazy = new Lazy<DatabaseContext>(() => new DatabaseContext(new DefaultDatabaseFactory()));
/// <summary>
/// Gets the current Database Context.
/// </summary>
public static DatabaseContext Current
{
//return the _custom context if it is set, otherwise the automatic lazy instance
get { return _customContext ?? lazy.Value; }
//Allows setting a custom database context for the 'Current', normally used for unit tests or if the
//default IDatabaseFactory is not sufficient.
internal set { _customContext = value; }
}
internal DatabaseContext(IDatabaseFactory factory)
{
_factory = factory;
}
#endregion
/// <summary>
/// Gets the <see cref="Database"/> object for doing CRUD operations
/// against custom tables that resides in the Umbraco database.
/// </summary>
/// <remarks>
/// This should not be used for CRUD operations or queries against the
/// standard Umbraco tables! Use the Public services for that.
/// </remarks>
public Database Database
{
get { return _factory.CreateDatabase(); }
}
/// <summary>
/// Boolean indicating whether the database has been configured
/// </summary>
public bool IsDatabaseConfigured
{
get { return _configured; }
}
/// <summary>
/// Gets the configured umbraco db connection string.
/// </summary>
public string ConnectionString
{
get { return _connectionString; }
}
/// <summary>
/// Returns the name of the dataprovider from the connectionstring setting in config
/// </summary>
internal string ProviderName
{
get
{
if (string.IsNullOrEmpty(_providerName) == false)
return _providerName;
_providerName = "System.Data.SqlClient";
if (ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName] != null)
{
if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName))
_providerName = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName;
}
else
{
throw new InvalidOperationException("Can't find a connection string with the name '" + GlobalSettings.UmbracoConnectionName + "'");
}
return _providerName;
}
}
/// <summary>
/// Returns the Type of DatabaseProvider used
/// </summary>
public DatabaseProviders DatabaseProvider
{
get
{
string dbtype = Database.Connection == null ? ProviderName : Database.Connection.GetType().Name;
if (dbtype.StartsWith("MySql")) return DatabaseProviders.MySql;
if (dbtype.StartsWith("SqlCe") || dbtype.Contains("SqlServerCe")) return DatabaseProviders.SqlServerCE;
if (dbtype.StartsWith("Npgsql")) return DatabaseProviders.PostgreSQL;
if (dbtype.StartsWith("Oracle") || dbtype.Contains("OracleClient")) return DatabaseProviders.Oracle;
if (dbtype.StartsWith("SQLite")) return DatabaseProviders.SQLite;
if (dbtype.Contains("Azure")) return DatabaseProviders.SqlAzure;
return DatabaseProviders.SqlServer;
}
}
/// <summary>
/// Configure a ConnectionString for the embedded database.
/// </summary>
public void ConfigureDatabaseConnection()
{
var providerName = "System.Data.SqlServerCe.4.0";
var connectionString = "Datasource=|DataDirectory|Umbraco.sdf";
var appSettingsConnection = @"datalayer=SQLCE4Umbraco.SqlCEHelper,SQLCE4Umbraco;data source=|DataDirectory|\Umbraco.sdf";
SaveConnectionString(connectionString, appSettingsConnection, providerName);
var path = Path.Combine(GlobalSettings.FullpathToRoot, "App_Data", "Umbraco.sdf");
if (File.Exists(path) == false)
{
var engine = new SqlCeEngine(connectionString);
engine.CreateDatabase();
}
Initialize(providerName);
}
/// <summary>
/// Configure a ConnectionString that has been entered manually.
/// </summary>
/// <remarks>
/// Please note that we currently assume that the 'System.Data.SqlClient' provider can be used.
/// </remarks>
/// <param name="connectionString"></param>
public void ConfigureDatabaseConnection(string connectionString)
{
SaveConnectionString(connectionString, connectionString, string.Empty);
Initialize(string.Empty);
}
/// <summary>
/// Configures a ConnectionString for the Umbraco database based on the passed in properties from the installer.
/// </summary>
/// <param name="server">Name or address of the database server</param>
/// <param name="databaseName">Name of the database</param>
/// <param name="user">Database Username</param>
/// <param name="password">Database Password</param>
/// <param name="databaseProvider">Type of the provider to be used (Sql, Sql Azure, Sql Ce, MySql)</param>
public void ConfigureDatabaseConnection(string server, string databaseName, string user, string password, string databaseProvider)
{
string connectionString;
string appSettingsConnection;
string providerName = "System.Data.SqlClient";
if(databaseProvider.ToLower().Contains("mysql"))
{
providerName = "MySql.Data.MySqlClient";
connectionString = string.Format("Server={0}; Database={1};Uid={2};Pwd={3}", server, databaseName, user, password);
appSettingsConnection = connectionString;
}
else if(databaseProvider.ToLower().Contains("azure"))
{
connectionString = string.Format("Server=tcp:{0}.database.windows.net;Database={1};User ID={2}@{0};Password={3}", server, databaseName, user, password);
appSettingsConnection = connectionString;
}
else
{
connectionString = string.Format("server={0};database={1};user id={2};password={3}", server, databaseName, user, password);
appSettingsConnection = connectionString;
}
SaveConnectionString(connectionString, appSettingsConnection, providerName);
Initialize(providerName);
}
/// <summary>
/// Saves the connection string as a proper .net ConnectionString and the legacy AppSettings key/value.
/// </summary>
/// <remarks>
/// Saves the ConnectionString in the very nasty 'medium trust'-supportive way.
/// </remarks>
/// <param name="connectionString"></param>
/// <param name="appSettingsConnection"></param>
/// <param name="providerName"></param>
private void SaveConnectionString(string connectionString, string appSettingsConnection, string providerName)
{
//Set the connection string for the new datalayer
var connectionStringSettings = string.IsNullOrEmpty(providerName)
? new ConnectionStringSettings(GlobalSettings.UmbracoConnectionName,
connectionString)
: new ConnectionStringSettings(GlobalSettings.UmbracoConnectionName,
connectionString, providerName);
_connectionString = connectionString;
_providerName = providerName;
//Set the connection string in appsettings used in the legacy datalayer
GlobalSettings.DbDsn = appSettingsConnection;
var webConfig = new WebConfigurationFileMap();
var vDir = GlobalSettings.FullpathToRoot;
foreach (VirtualDirectoryMapping v in webConfig.VirtualDirectories)
{
if (v.IsAppRoot)
{
vDir = v.PhysicalDirectory;
}
}
string fileName = String.Concat(vDir, "web.config");
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
var connectionstrings = xml.Root.Descendants("connectionStrings").Single();
// Update connectionString if it exists, or else create a new appSetting for the given key and value
var setting = connectionstrings.Descendants("add").FirstOrDefault(s => s.Attribute("name").Value == GlobalSettings.UmbracoConnectionName);
if (setting == null)
connectionstrings.Add(new XElement("add",
new XAttribute("name", GlobalSettings.UmbracoConnectionName),
new XAttribute("connectionString", connectionStringSettings),
new XAttribute("providerName", providerName)));
else
{
setting.Attribute("connectionString").Value = connectionString;
}
xml.Save(fileName, SaveOptions.DisableFormatting);
LogHelper.Info<DatabaseContext>("Configured new ConnectionString: " + connectionString);
}
/// <summary>
/// Internal method to initialize the database configuration.
/// </summary>
/// <remarks>
/// If an Umbraco connectionstring exists the database can be configured on app startup,
/// but if its a new install the entry doesn't exist and the db cannot be configured.
/// So for new installs the Initialize() method should be called after the connectionstring
/// has been added to the web.config.
/// </remarks>
internal void Initialize()
{
if (ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName] != null)
{
var providerName = "System.Data.SqlClient";
if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName))
{
providerName = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ProviderName;
_connectionString =
ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString;
}
if (providerName.StartsWith("MySql"))
{
SyntaxConfig.SqlSyntaxProvider = MySqlSyntax.Provider;
}
else if (providerName.Contains("SqlServerCe"))
{
SyntaxConfig.SqlSyntaxProvider = SqlCeSyntax.Provider;
}
else
{
SyntaxConfig.SqlSyntaxProvider = SqlServerSyntax.Provider;
}
_configured = true;
}
else
{
_configured = false;
}
}
internal void Initialize(string providerName)
{
if (providerName.StartsWith("MySql"))
{
SyntaxConfig.SqlSyntaxProvider = MySqlSyntax.Provider;
}
else if (providerName.Contains("SqlServerCe"))
{
SyntaxConfig.SqlSyntaxProvider = SqlCeSyntax.Provider;
}
else
{
SyntaxConfig.SqlSyntaxProvider = SqlServerSyntax.Provider;
}
_configured = true;
}
internal Result CreateDatabaseSchemaAndDataOrUpgrade()
{
if (_configured == false || (string.IsNullOrEmpty(_connectionString) || string.IsNullOrEmpty(ProviderName)))
{
return new Result{Message = "Database configuration is invalid", Success = false, Percentage = "10"};
}
try
{
var database = new Database(_connectionString, ProviderName);
//If Configuration Status is empty its a new install - otherwise upgrade the existing
if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus))
{
database.CreateDatabaseSchema();
}
else
{
var configuredVersion = new Version(GlobalSettings.ConfigurationStatus);
var targetVersion = UmbracoVersion.Current;
var runner = new MigrationRunner(configuredVersion, targetVersion);
var upgraded = runner.Execute(database, true);
}
return new Result { Message = "Installation completed!", Success = true, Percentage = "100" };
}
catch (Exception ex)
{
return new Result { Message = ex.Message, Success = false, Percentage = "90" };
}
}
internal class Result
{
public string Message { get; set; }
public bool Success { get; set; }
public string Percentage { get; set; }
}
}
}