U4-9201 - properly manage database instances

This commit is contained in:
Stephan
2016-11-18 09:42:07 +01:00
parent d086244820
commit 13ed3757da
4 changed files with 337 additions and 103 deletions

View File

@@ -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) + ".");
}
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);