U4-9201 - properly manage database instances
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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 Semver;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -30,7 +30,6 @@ namespace Umbraco.Core
|
||||
private readonly ILogger _logger;
|
||||
private readonly SqlSyntaxProviders _syntaxProviders;
|
||||
private bool _configured;
|
||||
private readonly object _locker = new object();
|
||||
private string _connectionString;
|
||||
private string _providerName;
|
||||
private DatabaseSchemaResult _result;
|
||||
@@ -40,7 +39,7 @@ namespace Umbraco.Core
|
||||
: this(factory, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[]
|
||||
{
|
||||
new MySqlSyntaxProvider(LoggerResolver.Current.Logger),
|
||||
new SqlCeSyntaxProvider(),
|
||||
new SqlCeSyntaxProvider(),
|
||||
new SqlServerSyntaxProvider()
|
||||
}))
|
||||
{
|
||||
@@ -80,21 +79,55 @@ namespace Umbraco.Core
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
public List<UmbracoDatabase> Databases
|
||||
{
|
||||
get
|
||||
{
|
||||
var factory = _factory as DefaultDatabaseFactory;
|
||||
if (factory == null) throw new NotSupportedException();
|
||||
return factory.Databases;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public ISqlSyntaxProvider SqlSyntax { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Database"/> object for doing CRUD operations
|
||||
/// against custom tables that resides in the Umbraco database.
|
||||
/// 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.
|
||||
/// <para>Should not be used for operation against standard Umbraco tables; as services should be used instead.</para>
|
||||
/// <para>Gets or creates an "ambient" database that is either stored in http context + available for the whole
|
||||
/// request + auto-disposed at the end of the request, or in call context if there is no http context - in which
|
||||
/// case it needs to be explicitely disposed so it is removed from call context.</para>
|
||||
/// </remarks>
|
||||
public virtual UmbracoDatabase Database
|
||||
{
|
||||
get { return _factory.CreateDatabase(); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the "ambient" database (if any) and installs a new <see cref="Database"/> object.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The returned IDisposable *must* be diposed in order to properly disposed the safe database and
|
||||
/// restore the original "ambient" database (if any).</para>
|
||||
/// <para>This is to be used in background tasks to ensure that they have an "ambient" database which
|
||||
/// will be properly removed from call context and does not interfere with anything else. In most case
|
||||
/// it is not replacing anything, just temporarily installing a database in context.</para>
|
||||
/// </remarks>
|
||||
public virtual IDisposable UsingSafeDatabase
|
||||
{
|
||||
get
|
||||
{
|
||||
var factory = _factory as DefaultDatabaseFactory;
|
||||
if (factory == null) throw new NotSupportedException();
|
||||
var database = factory.CreateDatabaseInstance(DefaultDatabaseFactory.ContextOwner.None);
|
||||
return new UsedDatabase(database, _logger);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether the database has been configured
|
||||
/// </summary>
|
||||
@@ -189,7 +222,7 @@ namespace Umbraco.Core
|
||||
// since it's been like this for quite some time
|
||||
//using (var engine = new SqlCeEngine(connectionString))
|
||||
//{
|
||||
// engine.CreateDatabase();
|
||||
// engine.CreateDatabase();
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -230,14 +263,14 @@ namespace Umbraco.Core
|
||||
/// <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 providerName;
|
||||
var connectionString = GetDatabaseConnectionString(server, databaseName, user, password, databaseProvider, out providerName);
|
||||
|
||||
SaveConnectionString(connectionString, providerName);
|
||||
Initialize(providerName);
|
||||
}
|
||||
|
||||
|
||||
public string GetDatabaseConnectionString(string server, string databaseName, string user, string password, string databaseProvider, out string providerName)
|
||||
{
|
||||
providerName = Constants.DatabaseProviders.SqlServer;
|
||||
@@ -268,18 +301,18 @@ namespace Umbraco.Core
|
||||
|
||||
public string GetIntegratedSecurityDatabaseConnectionString(string server, string databaseName)
|
||||
{
|
||||
return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName);
|
||||
return String.Format("Server={0};Database={1};Integrated Security=true", server, databaseName);
|
||||
}
|
||||
|
||||
internal string BuildAzureConnectionString(string server, string databaseName, string user, string password)
|
||||
{
|
||||
if (server.Contains(".") && ServerStartsWithTcp(server) == false)
|
||||
server = string.Format("tcp:{0}", server);
|
||||
|
||||
|
||||
if (server.Contains(".") == false && ServerStartsWithTcp(server))
|
||||
{
|
||||
string serverName = server.Contains(",")
|
||||
? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal))
|
||||
string serverName = server.Contains(",")
|
||||
? server.Substring(0, server.IndexOf(",", StringComparison.Ordinal))
|
||||
: server;
|
||||
|
||||
var portAddition = string.Empty;
|
||||
@@ -289,10 +322,10 @@ namespace Umbraco.Core
|
||||
|
||||
server = string.Format("{0}.database.windows.net{1}", serverName, portAddition);
|
||||
}
|
||||
|
||||
|
||||
if (ServerStartsWithTcp(server) == false)
|
||||
server = string.Format("tcp:{0}.database.windows.net", server);
|
||||
|
||||
|
||||
if (server.Contains(",") == false)
|
||||
server = string.Format("{0},1433", server);
|
||||
|
||||
@@ -336,7 +369,7 @@ namespace Umbraco.Core
|
||||
|
||||
_connectionString = connectionString;
|
||||
_providerName = providerName;
|
||||
|
||||
|
||||
var fileName = IOHelper.MapPath(string.Format("{0}/web.config", SystemDirectories.Root));
|
||||
var xml = XDocument.Load(fileName, LoadOptions.PreserveWhitespace);
|
||||
var connectionstrings = xml.Root.DescendantsAndSelf("connectionStrings").Single();
|
||||
@@ -381,7 +414,7 @@ namespace Umbraco.Core
|
||||
connString = ConfigurationManager.ConnectionStrings[GlobalSettings.UmbracoConnectionName].ConnectionString;
|
||||
}
|
||||
Initialize(providerName, connString);
|
||||
|
||||
|
||||
}
|
||||
else if (ConfigurationManager.AppSettings.ContainsKey(GlobalSettings.UmbracoConnectionName) && string.IsNullOrEmpty(ConfigurationManager.AppSettings[GlobalSettings.UmbracoConnectionName]) == false)
|
||||
{
|
||||
@@ -418,7 +451,7 @@ namespace Umbraco.Core
|
||||
|
||||
//Remove the legacy connection string, so we don't end up in a loop if something goes wrong.
|
||||
GlobalSettings.RemoveSetting(GlobalSettings.UmbracoConnectionName);
|
||||
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -437,15 +470,15 @@ namespace Umbraco.Core
|
||||
{
|
||||
if (_syntaxProviders != null)
|
||||
{
|
||||
SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName);
|
||||
SqlSyntax = _syntaxProviders.GetByProviderNameOrDefault(providerName);
|
||||
}
|
||||
else if (SqlSyntax == null)
|
||||
{
|
||||
throw new InvalidOperationException("No " + typeof(ISqlSyntaxProvider) + " specified or no " + typeof(SqlSyntaxProviders) + " instance specified");
|
||||
}
|
||||
|
||||
|
||||
SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax;
|
||||
|
||||
|
||||
_configured = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
@@ -485,7 +518,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
internal Result CreateDatabaseSchemaAndData(ApplicationContext applicationContext)
|
||||
{
|
||||
{
|
||||
try
|
||||
{
|
||||
var readyForInstall = CheckReadyForInstall();
|
||||
@@ -503,7 +536,7 @@ namespace Umbraco.Core
|
||||
// If MySQL, we're going to ensure that database calls are maintaining proper casing as to remove the necessity for checks
|
||||
// for case insensitive queries. In an ideal situation (which is what we're striving for), all calls would be case sensitive.
|
||||
|
||||
/*
|
||||
/*
|
||||
var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
if (supportsCaseInsensitiveQueries == false)
|
||||
{
|
||||
@@ -521,9 +554,9 @@ namespace Umbraco.Core
|
||||
message = GetResultMessageForMySql();
|
||||
|
||||
var schemaResult = ValidateDatabaseSchema();
|
||||
|
||||
|
||||
var installedSchemaVersion = schemaResult.DetermineInstalledVersion();
|
||||
|
||||
|
||||
//If Configuration Status is empty and the determined version is "empty" its a new install - otherwise upgrade the existing
|
||||
if (string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus) && installedSchemaVersion.Equals(new Version(0, 0, 0)))
|
||||
{
|
||||
@@ -542,9 +575,9 @@ namespace Umbraco.Core
|
||||
message = "<p>Upgrading database, this may take some time...</p>";
|
||||
return new Result
|
||||
{
|
||||
RequiresUpgrade = true,
|
||||
Message = message,
|
||||
Success = true,
|
||||
RequiresUpgrade = true,
|
||||
Message = message,
|
||||
Success = true,
|
||||
Percentage = "30"
|
||||
};
|
||||
}
|
||||
@@ -572,7 +605,7 @@ namespace Umbraco.Core
|
||||
_logger.Info<DatabaseContext>("Database upgrade started");
|
||||
|
||||
var database = new UmbracoDatabase(_connectionString, ProviderName, _logger);
|
||||
//var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
//var supportsCaseInsensitiveQueries = SqlSyntax.SupportsCaseInsensitiveQueries(database);
|
||||
|
||||
var message = GetResultMessageForMySql();
|
||||
|
||||
@@ -580,32 +613,32 @@ namespace Umbraco.Core
|
||||
|
||||
var installedSchemaVersion = new SemVersion(schemaResult.DetermineInstalledVersion());
|
||||
|
||||
var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService);
|
||||
|
||||
var installedMigrationVersion = schemaResult.DetermineInstalledVersionByMigrations(migrationEntryService);
|
||||
|
||||
var targetVersion = UmbracoVersion.Current;
|
||||
|
||||
|
||||
//In some cases - like upgrading from 7.2.6 -> 7.3, there will be no migration information in the database and therefore it will
|
||||
// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the
|
||||
// return a version of 0.0.0 and we don't necessarily want to run all migrations from 0 -> 7.3, so we'll just ensure that the
|
||||
// migrations are run for the target version
|
||||
if (installedMigrationVersion == new SemVersion(new Version(0, 0, 0)) && installedSchemaVersion > new SemVersion(new Version(0, 0, 0)))
|
||||
{
|
||||
//set the installedMigrationVersion to be one less than the target so the latest migrations are guaranteed to execute
|
||||
installedMigrationVersion = new SemVersion(targetVersion.SubtractRevision());
|
||||
}
|
||||
|
||||
|
||||
//Figure out what our current installed version is. If the web.config doesn't have a version listed, then we'll use the minimum
|
||||
// version detected between the schema installed and the migrations listed in the migration table.
|
||||
// version detected between the schema installed and the migrations listed in the migration table.
|
||||
// If there is a version in the web.config, we'll take the minimum between the listed migration in the db and what
|
||||
// is declared in the web.config.
|
||||
|
||||
|
||||
var currentInstalledVersion = string.IsNullOrEmpty(GlobalSettings.ConfigurationStatus)
|
||||
//Take the minimum version between the detected schema version and the installed migration version
|
||||
? new[] {installedSchemaVersion, installedMigrationVersion}.Min()
|
||||
//Take the minimum version between the installed migration version and the version specified in the config
|
||||
: new[] { SemVersion.Parse(GlobalSettings.ConfigurationStatus), installedMigrationVersion }.Min();
|
||||
|
||||
//Ok, another edge case here. If the current version is a pre-release,
|
||||
// then we want to ensure all migrations for the current release are executed.
|
||||
//Ok, another edge case here. If the current version is a pre-release,
|
||||
// then we want to ensure all migrations for the current release are executed.
|
||||
if (currentInstalledVersion.Prerelease.IsNullOrWhiteSpace() == false)
|
||||
{
|
||||
currentInstalledVersion = new SemVersion(currentInstalledVersion.GetVersion().SubtractRevision());
|
||||
@@ -740,7 +773,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
var datasource = dataSourcePart.Replace("|DataDirectory|", AppDomain.CurrentDomain.GetData("DataDirectory").ToString());
|
||||
var filePath = datasource.Replace("Data Source=", string.Empty);
|
||||
sqlCeDatabaseExists = File.Exists(filePath);
|
||||
sqlCeDatabaseExists = File.Exists(filePath);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -754,5 +787,32 @@ namespace Umbraco.Core
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private class UsedDatabase : IDisposable
|
||||
{
|
||||
private readonly UmbracoDatabase _database;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public UsedDatabase(UmbracoDatabase database, ILogger logger)
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
// replace with ours
|
||||
_database = DefaultDatabaseFactory.DetachDatabase();
|
||||
_logger.Debug<DatabaseContext>("Detached db " + (_database == null ? "x" : _database.InstanceSid) + ".");
|
||||
DefaultDatabaseFactory.AttachDatabase(database);
|
||||
_logger.Debug<DatabaseContext>("Attached db " + database.InstanceSid + ".");
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// restore the original DB
|
||||
var db = DefaultDatabaseFactory.DetachDatabase();
|
||||
_logger.Debug<DatabaseContext>("Detached db " + db.InstanceSid + ".");
|
||||
db.Dispose();
|
||||
DefaultDatabaseFactory.AttachDatabase(_database);
|
||||
_logger.Debug<DatabaseContext>("Attached db " + (_database == null ? "x" : _database.InstanceSid) + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,69 +75,168 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
public UmbracoDatabase CreateDatabase()
|
||||
{
|
||||
// no http context, create the call context object
|
||||
// NOTHING is going to track the object and it is the responsibility of the caller to release it!
|
||||
// using the ReleaseDatabase method.
|
||||
UmbracoDatabase database;
|
||||
|
||||
// gets or creates a database, using either the call context (if no http context) or
|
||||
// the current request context (http context) to store it. once done using the database,
|
||||
// it should be disposed - which will remove it from whatever context it is currently
|
||||
// stored in. this is automatic with http context because UmbracoDatabase implements
|
||||
// IDisposeOnRequestEnd, but NOT with call context.
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("Get NON http [T" + Environment.CurrentManagedThreadId + "]");
|
||||
var value = NonContextValue;
|
||||
if (value != null) return value;
|
||||
lock (Locker)
|
||||
database = NonContextValue;
|
||||
if (database == null)
|
||||
{
|
||||
value = NonContextValue;
|
||||
if (value != null) return value;
|
||||
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("Create NON http [T" + Environment.CurrentManagedThreadId + "]");
|
||||
NonContextValue = value = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
|
||||
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
|
||||
: new UmbracoDatabase(_connectionStringName, _logger);
|
||||
|
||||
return value;
|
||||
lock (Locker)
|
||||
{
|
||||
database = NonContextValue;
|
||||
if (database == null)
|
||||
{
|
||||
database = CreateDatabaseInstance(ContextOwner.CallContext);
|
||||
NonContextValue = database;
|
||||
}
|
||||
#if DEBUG_DATABASES
|
||||
else
|
||||
{
|
||||
Log("Get lcc", database);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if DEBUG_DATABASES
|
||||
else
|
||||
{
|
||||
Log("Get lcc", database);
|
||||
}
|
||||
#endif
|
||||
return database;
|
||||
}
|
||||
|
||||
// we have an http context, so only create one per request.
|
||||
// UmbracoDatabase is marked IDisposeOnRequestEnd and therefore will be disposed when
|
||||
// UmbracoModule attempts to dispose the relevant HttpContext items. so we DO dispose
|
||||
// connections at the end of each request. no need to call ReleaseDatabase.
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("Get http [T" + Environment.CurrentManagedThreadId + "]");
|
||||
if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false)
|
||||
{
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("Create http [T" + Environment.CurrentManagedThreadId + "]");
|
||||
HttpContext.Current.Items.Add(typeof(DefaultDatabaseFactory),
|
||||
string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
|
||||
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
|
||||
: new UmbracoDatabase(_connectionStringName, _logger));
|
||||
if (HttpContext.Current.Items.Contains(typeof (DefaultDatabaseFactory)) == false)
|
||||
{
|
||||
database = CreateDatabaseInstance(ContextOwner.HttpContext);
|
||||
HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), database);
|
||||
}
|
||||
else
|
||||
{
|
||||
database = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
#if DEBUG_DATABASES
|
||||
Log("Get ctx", database);
|
||||
#endif
|
||||
}
|
||||
return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
// called by UmbracoDatabase when disposed, so that the factory can de-list it from context
|
||||
internal void OnDispose(UmbracoDatabase disposing)
|
||||
{
|
||||
var value = disposing;
|
||||
switch (disposing.ContextOwner)
|
||||
{
|
||||
case ContextOwner.CallContext:
|
||||
value = NonContextValue;
|
||||
break;
|
||||
case ContextOwner.HttpContext:
|
||||
value = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
|
||||
break;
|
||||
}
|
||||
|
||||
if (value != null && value.InstanceId != disposing.InstanceId) throw new Exception("panic: wrong db.");
|
||||
|
||||
switch (disposing.ContextOwner)
|
||||
{
|
||||
case ContextOwner.CallContext:
|
||||
NonContextValue = null;
|
||||
break;
|
||||
case ContextOwner.HttpContext:
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
break;
|
||||
}
|
||||
|
||||
disposing.ContextOwner = ContextOwner.None;
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
_databases.Remove(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
// releases the "context" database
|
||||
public void ReleaseDatabase()
|
||||
#if DEBUG_DATABASES
|
||||
// helps identifying when non-httpContext databases are created by logging the stack trace
|
||||
private void LogCallContextStack()
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
var value = NonContextValue;
|
||||
if (value != null) value.Dispose();
|
||||
NonContextValue = null;
|
||||
}
|
||||
var trace = Environment.StackTrace;
|
||||
if (trace.IndexOf("ScheduledPublishing") > 0)
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: Scheduled Publishing");
|
||||
else if (trace.IndexOf("TouchServerTask") > 0)
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: Server Registration");
|
||||
else if (trace.IndexOf("LogScrubber") > 0)
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: Log Scrubber");
|
||||
else
|
||||
{
|
||||
var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
if (db != null) db.Dispose();
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
}
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: " + Environment.StackTrace);
|
||||
}
|
||||
|
||||
private readonly List<UmbracoDatabase> _databases = new List<UmbracoDatabase>();
|
||||
|
||||
// helps identifying database leaks by keeping track of all instances
|
||||
public List<UmbracoDatabase> Databases { get { return _databases; } }
|
||||
|
||||
private static void Log(string message, UmbracoDatabase database)
|
||||
{
|
||||
LogHelper.Debug<DefaultDatabaseFactory>(message + " (" + (database == null ? "" : database.InstanceSid) + ").");
|
||||
}
|
||||
#endif
|
||||
|
||||
internal enum ContextOwner
|
||||
{
|
||||
None,
|
||||
HttpContext,
|
||||
CallContext
|
||||
}
|
||||
|
||||
internal UmbracoDatabase CreateDatabaseInstance(ContextOwner contextOwner)
|
||||
{
|
||||
var database = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
|
||||
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
|
||||
: new UmbracoDatabase(_connectionStringName, _logger);
|
||||
database.ContextOwner = contextOwner;
|
||||
database.DatabaseFactory = this;
|
||||
//database.EnableSqlTrace = true;
|
||||
#if DEBUG_DATABASES
|
||||
Log("Create " + contextOwner, database);
|
||||
if (contextOwner == ContextOwner.CallContext)
|
||||
LogCallContextStack();
|
||||
_databases.Add(database);
|
||||
#endif
|
||||
return database;
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
ReleaseDatabase();
|
||||
}
|
||||
UmbracoDatabase database;
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
database = NonContextValue;
|
||||
#if DEBUG_DATABASES
|
||||
Log("Release lcc", database);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
|
||||
#if DEBUG_DATABASES
|
||||
Log("Release ctx", database);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (database != null) database.Dispose(); // removes it from call context
|
||||
}
|
||||
|
||||
// during tests, the thread static var can leak between tests
|
||||
// this method provides a way to force-reset the variable
|
||||
internal void ResetForTests()
|
||||
internal void ResetForTests()
|
||||
{
|
||||
var value = NonContextValue;
|
||||
if (value != null) value.Dispose();
|
||||
@@ -156,26 +255,29 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
// detaches the current database
|
||||
// ie returns the database and remove it from whatever is "context"
|
||||
private static UmbracoDatabase DetachDatabase()
|
||||
internal static UmbracoDatabase DetachDatabase()
|
||||
{
|
||||
UmbracoDatabase database;
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
var db = NonContextValue;
|
||||
database = NonContextValue;
|
||||
NonContextValue = null;
|
||||
return db;
|
||||
}
|
||||
else
|
||||
{
|
||||
var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
return db;
|
||||
database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
|
||||
HttpContext.Current.Items.Remove(typeof (DefaultDatabaseFactory));
|
||||
}
|
||||
|
||||
if (database != null) database.ContextOwner = ContextOwner.None;
|
||||
return database;
|
||||
}
|
||||
|
||||
// attach a current database
|
||||
// ie assign it to whatever is "context"
|
||||
// throws if there already is a database
|
||||
private static void AttachDatabase(object o)
|
||||
internal static void AttachDatabase(object o)
|
||||
{
|
||||
var database = o as UmbracoDatabase;
|
||||
if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o");
|
||||
@@ -183,12 +285,18 @@ namespace Umbraco.Core.Persistence
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
if (NonContextValue != null) throw new InvalidOperationException();
|
||||
if (database != null) NonContextValue = database;
|
||||
if (database == null) return;
|
||||
|
||||
NonContextValue = database;
|
||||
database.ContextOwner = ContextOwner.CallContext;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] != null) throw new InvalidOperationException();
|
||||
if (database != null) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database;
|
||||
if (HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null) throw new InvalidOperationException();
|
||||
if (database == null) return;
|
||||
|
||||
HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] = database;
|
||||
database.ContextOwner = ContextOwner.HttpContext;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Data;
|
||||
using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using System.Text;
|
||||
using MySql.Data.MySqlClient;
|
||||
using StackExchange.Profiling;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
@@ -21,6 +23,12 @@ namespace Umbraco.Core.Persistence
|
||||
private readonly ILogger _logger;
|
||||
private readonly Guid _instanceId = Guid.NewGuid();
|
||||
private bool _enableCount;
|
||||
#if DEBUG_DATABASES
|
||||
private int _spid = -1;
|
||||
#endif
|
||||
|
||||
internal DefaultDatabaseFactory.ContextOwner ContextOwner = DefaultDatabaseFactory.ContextOwner.None;
|
||||
internal DefaultDatabaseFactory DatabaseFactory = null;
|
||||
|
||||
/// <summary>
|
||||
/// Used for testing
|
||||
@@ -30,6 +38,18 @@ namespace Umbraco.Core.Persistence
|
||||
get { return _instanceId; }
|
||||
}
|
||||
|
||||
public string InstanceSid
|
||||
{
|
||||
get
|
||||
{
|
||||
#if DEBUG_DATABASES
|
||||
return _instanceId.ToString("N").Substring(0, 8) + ":" + _spid;
|
||||
#else
|
||||
return _instanceId.ToString("N").Substring(0, 8);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generally used for testing, will output all SQL statements executed to the logger
|
||||
/// </summary>
|
||||
@@ -113,13 +133,44 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
// propagate timeout if none yet
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
if (DatabaseType == DBType.MySql)
|
||||
{
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "SELECT CONNECTION_ID()";
|
||||
_spid = Convert.ToInt32(command.ExecuteScalar());
|
||||
}
|
||||
}
|
||||
else if (DatabaseType == DBType.SqlServer)
|
||||
{
|
||||
using (var command = connection.CreateCommand())
|
||||
{
|
||||
command.CommandText = "SELECT @@SPID";
|
||||
_spid = Convert.ToInt32(command.ExecuteScalar());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// includes SqlCE
|
||||
_spid = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// wrap the connection with a profiling connection that tracks timings
|
||||
return new StackExchange.Profiling.Data.ProfiledDbConnection(connection as DbConnection, MiniProfiler.Current);
|
||||
}
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
public override void OnConnectionClosing(IDbConnection conn)
|
||||
{
|
||||
_spid = -1;
|
||||
}
|
||||
#endif
|
||||
|
||||
public override void OnException(Exception x)
|
||||
{
|
||||
_logger.Error<UmbracoDatabase>("Database exception occurred", x);
|
||||
_logger.Error<UmbracoDatabase>("Exception (" + InstanceSid + ").", x);
|
||||
base.OnException(x);
|
||||
}
|
||||
|
||||
@@ -132,14 +183,18 @@ namespace Umbraco.Core.Persistence
|
||||
if (EnableSqlTrace)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
#if DEBUG_DATABASES
|
||||
sb.Append(InstanceSid);
|
||||
sb.Append(": ");
|
||||
#endif
|
||||
sb.Append(cmd.CommandText);
|
||||
foreach (DbParameter p in cmd.Parameters)
|
||||
{
|
||||
sb.Append(" - ");
|
||||
sb.Append(p.Value);
|
||||
}
|
||||
|
||||
_logger.Debug<UmbracoDatabase>(sb.ToString());
|
||||
|
||||
_logger.Debug<UmbracoDatabase>(sb.ToString().Replace("{", "{{").Replace("}", "}}"));
|
||||
}
|
||||
|
||||
base.OnExecutingCommand(cmd);
|
||||
@@ -185,9 +240,15 @@ namespace Umbraco.Core.Persistence
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//use the defaults
|
||||
base.BuildSqlDbSpecificPagingQuery(databaseType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
LogHelper.Debug<UmbracoDatabase>("Dispose (" + InstanceSid + ").");
|
||||
if (DatabaseFactory != null) DatabaseFactory.OnDispose(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Core
|
||||
/// The abstract class for the Umbraco HttpApplication
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is exposed in the core so that we can have the IApplicationEventHandler in the core project so that
|
||||
/// This is exposed in the core so that we can have the IApplicationEventHandler in the core project so that
|
||||
/// IApplicationEventHandler's can fire/execute outside of the web contenxt (i.e. in console applications)
|
||||
/// </remarks>
|
||||
public abstract class UmbracoApplicationBase : System.Web.HttpApplication
|
||||
@@ -35,7 +35,7 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
internal void StartApplication(object sender, EventArgs e)
|
||||
{
|
||||
//take care of unhandled exceptions - there is nothing we can do to
|
||||
//take care of unhandled exceptions - there is nothing we can do to
|
||||
// prevent the entire w3wp process to go down but at least we can try
|
||||
// and log the exception
|
||||
AppDomain.CurrentDomain.UnhandledException += (_, args) =>
|
||||
@@ -59,6 +59,11 @@ namespace Umbraco.Core
|
||||
|
||||
//And now we can dispose of our startup handlers - save some memory
|
||||
ApplicationEventsResolver.Current.Dispose();
|
||||
|
||||
// after Umbraco has started there is a database in "context" and that context is
|
||||
// going to stay there and never get destroyed nor reused, so we have to ensure that
|
||||
// the database is disposed (which will auto-remove it from context).
|
||||
ApplicationContext.Current.DatabaseContext.Database.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -121,7 +126,7 @@ namespace Umbraco.Core
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -188,7 +193,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Logger.Error<UmbracoApplicationBase>("An unhandled exception occurred", exc);
|
||||
|
||||
OnApplicationError(sender, e);
|
||||
|
||||
Reference in New Issue
Block a user