Files
Umbraco-CMS/src/Umbraco.Core/Persistence/UmbracoDatabase.cs

262 lines
8.9 KiB
C#
Raw Normal View History

2017-07-20 11:21:28 +02:00
using System;
using System.Data;
using System.Data.Common;
2017-06-23 18:54:42 +02:00
using System.Linq;
using System.Text;
using NPoco;
2013-05-10 10:15:30 -02:00
using StackExchange.Profiling;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.FaultHandling;
namespace Umbraco.Core.Persistence
{
/// <summary>
/// Extends NPoco Database for Umbraco.
/// </summary>
/// <remarks>
/// <para>Is used everywhere in place of the original NPoco Database object, and provides additional features
/// such as profiling, retry policies, logging, etc.</para>
2016-12-14 14:06:30 +01:00
/// <para>Is never created directly but obtained from the <see cref="UmbracoDatabaseFactory"/>.</para>
/// </remarks>
2016-12-16 14:18:37 +01:00
public class UmbracoDatabase : Database, IUmbracoDatabase
{
// Umbraco's default isolation level is RepeatableRead
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
2017-05-22 17:22:10 +02:00
private readonly ILogger _logger;
private readonly RetryPolicy _connectionRetryPolicy;
private readonly RetryPolicy _commandRetryPolicy;
2016-12-16 14:18:37 +01:00
private readonly Guid _instanceGuid = Guid.NewGuid();
2016-04-12 19:55:50 +02:00
2016-12-14 14:06:30 +01:00
#region Ctor
/// <summary>
2016-12-14 14:06:30 +01:00
/// Initializes a new instance of the <see cref="UmbracoDatabase"/> class.
/// </summary>
2016-12-14 14:06:30 +01:00
/// <remarks>
/// <para>Used by UmbracoDatabaseFactory to create databases.</para>
/// <para>Also used by DatabaseBuilder for creating databases and installing/upgrading.</para>
/// </remarks>
2017-09-22 18:48:58 +02:00
public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
2016-11-29 10:31:25 +01:00
: base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel)
{
2016-12-16 14:18:37 +01:00
SqlContext = sqlContext;
2016-11-29 10:31:25 +01:00
_logger = logger;
_connectionRetryPolicy = connectionRetryPolicy;
_commandRetryPolicy = commandRetryPolicy;
2016-11-29 10:31:25 +01:00
2016-12-14 14:06:30 +01:00
EnableSqlTrace = EnableSqlTraceDefault;
}
2016-12-14 14:06:30 +01:00
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoDatabase"/> class.
/// </summary>
/// <remarks>Internal for unit tests only.</remarks>
2017-09-22 18:48:58 +02:00
internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger)
2016-11-29 18:59:01 +01:00
: base(connection, sqlContext.DatabaseType, DefaultIsolationLevel)
2016-04-14 18:17:18 +02:00
{
2016-12-16 14:18:37 +01:00
SqlContext = sqlContext;
2016-04-14 18:17:18 +02:00
_logger = logger;
2016-11-29 10:31:25 +01:00
2016-12-14 14:06:30 +01:00
EnableSqlTrace = EnableSqlTraceDefault;
2016-04-14 18:17:18 +02:00
}
2016-12-14 14:06:30 +01:00
#endregion
2016-12-16 14:18:37 +01:00
/// <inheritdoc />
2017-09-22 18:48:58 +02:00
public ISqlContext SqlContext { get; }
2016-12-02 17:04:51 +01:00
2016-12-14 14:06:30 +01:00
#region Testing, Debugging and Troubleshooting
private bool _enableCount;
#if DEBUG_DATABASES
private int _spid = -1;
private const bool EnableSqlTraceDefault = true;
#else
2016-12-16 14:18:37 +01:00
private string _instanceId;
2016-12-14 14:06:30 +01:00
private const bool EnableSqlTraceDefault = false;
#endif
2016-12-16 14:18:37 +01:00
/// <inheritdoc />
public string InstanceId
{
2016-12-14 14:06:30 +01:00
get
{
#if DEBUG_DATABASES
2016-12-16 14:18:37 +01:00
return _instanceGuid.ToString("N").Substring(0, 8) + ':' + _spid;
2016-12-14 14:06:30 +01:00
#else
2016-12-16 14:18:37 +01:00
return _instanceId ?? (_instanceId = _instanceGuid.ToString("N").Substring(0, 8));
2016-12-14 14:06:30 +01:00
#endif
}
}
2017-05-12 14:49:44 +02:00
/// <inheritdoc />
public bool InTransaction { get; private set; }
protected override void OnBeginTransaction()
{
base.OnBeginTransaction();
InTransaction = true;
}
protected override void OnAbortTransaction()
{
InTransaction = false;
base.OnAbortTransaction();
}
protected override void OnCompleteTransaction()
{
InTransaction = false;
base.OnCompleteTransaction();
}
2016-12-14 14:06:30 +01:00
/// <summary>
/// Gets or sets a value indicating whether to log all executed Sql statements.
/// </summary>
internal bool EnableSqlTrace { get; set; }
2016-12-14 14:06:30 +01:00
/// <summary>
/// Gets or sets a value indicating whether to count all executed Sql statements.
/// </summary>
internal bool EnableSqlCount
{
2017-09-19 15:51:47 +02:00
get => _enableCount;
2016-12-14 14:06:30 +01:00
set
{
_enableCount = value;
if (_enableCount == false)
SqlCount = 0;
}
}
2016-12-14 14:06:30 +01:00
/// <summary>
/// Gets the count of all executed Sql statements.
/// </summary>
internal int SqlCount { get; private set; }
2016-12-14 14:06:30 +01:00
#endregion
2016-12-14 14:06:30 +01:00
#region OnSomething
2016-12-14 14:06:30 +01:00
// fixme.poco - has new interceptors to replace OnSomething?
2016-10-13 21:08:07 +02:00
protected override DbConnection OnConnectionOpened(DbConnection connection)
{
2016-04-12 19:55:50 +02:00
if (connection == null) throw new ArgumentNullException(nameof(connection));
#if DEBUG_DATABASES
2017-05-12 14:49:44 +02:00
// determines the database connection SPID for debugging
2018-05-30 16:40:25 +02:00
if (DatabaseType.IsMySql())
{
using (var command = connection.CreateCommand())
{
command.CommandText = "SELECT CONNECTION_ID()";
_spid = Convert.ToInt32(command.ExecuteScalar());
}
}
2018-05-30 16:40:25 +02:00
else if (DatabaseType.IsSqlServer())
{
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
connection = new StackExchange.Profiling.Data.ProfiledDbConnection(connection, MiniProfiler.Current);
// wrap the connection with a retrying connection
if (_connectionRetryPolicy != null || _commandRetryPolicy != null)
connection = new RetryDbConnection(connection, _connectionRetryPolicy, _commandRetryPolicy);
return connection;
2013-05-10 10:15:30 -02:00
}
#if DEBUG_DATABASES
2018-05-30 16:40:25 +02:00
protected override void OnConnectionClosing(DbConnection conn)
{
_spid = -1;
2016-12-14 14:06:30 +01:00
base.OnConnectionClosing(conn);
}
#endif
protected override void OnException(Exception x)
{
2016-12-16 14:18:37 +01:00
_logger.Error<UmbracoDatabase>("Exception (" + InstanceId + ").", x);
_logger.Debug<UmbracoDatabase>(() => "At:\r\n" + Environment.StackTrace);
2017-05-22 17:22:10 +02:00
if (EnableSqlTrace == false)
_logger.Debug<UmbracoDatabase>(() => "Sql:\r\n" + CommandToString(LastSQL, LastArgs));
base.OnException(x);
}
2017-05-22 17:22:10 +02:00
private DbCommand _cmd;
protected override void OnExecutingCommand(DbCommand cmd)
{
// if no timeout is specified, and the connection has a longer timeout, use it
if (OneTimeCommandTimeout == 0 && CommandTimeout == 0 && cmd.Connection.ConnectionTimeout > 30)
cmd.CommandTimeout = cmd.Connection.ConnectionTimeout;
if (EnableSqlTrace)
_logger.Debug<UmbracoDatabase>(() => CommandToString(cmd).Replace("{", "{{").Replace("}", "}}")); // fixme these escapes should be builtin
2016-12-02 17:04:51 +01:00
#if DEBUG_DATABASES
2016-12-14 14:06:30 +01:00
// detects whether the command is already in use (eg still has an open reader...)
2018-05-30 16:40:25 +02:00
DatabaseDebugHelper.SetCommand(cmd, InstanceId + " [T" + System.Threading.Thread.CurrentThread.ManagedThreadId + "]");
2016-12-02 17:04:51 +01:00
var refsobj = DatabaseDebugHelper.GetReferencedObjects(cmd.Connection);
if (refsobj != null) _logger.Debug<UmbracoDatabase>("Oops!" + Environment.NewLine + refsobj);
#endif
2017-05-22 17:22:10 +02:00
_cmd = cmd;
base.OnExecutingCommand(cmd);
}
2017-06-23 18:54:42 +02:00
private string CommandToString(DbCommand cmd)
{
return CommandToString(cmd.CommandText, cmd.Parameters.Cast<DbParameter>().Select(x => x.Value).ToArray());
}
private string CommandToString(string sql, object[] args)
2017-05-22 17:22:10 +02:00
{
var sb = new StringBuilder();
#if DEBUG_DATABASES
sb.Append(InstanceId);
sb.Append(": ");
#endif
2017-06-23 18:54:42 +02:00
sb.Append(sql);
if (args.Length > 0)
2017-05-22 17:22:10 +02:00
sb.Append(" --");
var i = 0;
2017-06-23 18:54:42 +02:00
foreach (var arg in args)
2017-05-22 17:22:10 +02:00
{
sb.Append(" @");
sb.Append(i++);
sb.Append(":");
2017-06-23 18:54:42 +02:00
sb.Append(arg);
2017-05-22 17:22:10 +02:00
}
return sb.ToString();
}
protected override void OnExecutedCommand(DbCommand cmd)
{
if (_enableCount)
SqlCount++;
base.OnExecutedCommand(cmd);
}
2016-11-29 10:31:25 +01:00
2016-12-14 14:06:30 +01:00
#endregion
}
2017-07-20 11:21:28 +02:00
}