Merge dev-v7.6 into dev-v8

This commit is contained in:
Stephan
2016-12-14 14:06:30 +01:00
44 changed files with 1067 additions and 453 deletions

View File

@@ -115,6 +115,12 @@ namespace Umbraco.Core
// throw a BootFailedException for every requests.
}
}
// after Umbraco has started there is a scope in "context" and that context is
// going to stay there and never get destroyed nor reused, so we have to ensure that
// everything is cleared
var sa = container.GetInstance<IDatabaseScopeAccessor>();
sa.Scope?.Dispose();
}
private void AquireMainDom(IServiceFactory container)
@@ -218,7 +224,7 @@ namespace Umbraco.Core
// will be initialized with syntax providers and a logger, and will try to configure
// from the default connection string name, if possible, else will remain non-configured
// until the database context configures it properly (eg when installing)
container.RegisterSingleton<IDatabaseFactory, DefaultDatabaseFactory>();
container.RegisterSingleton<IDatabaseFactory, UmbracoDatabaseFactory>();
// register database context
container.RegisterSingleton<DatabaseContext>();

View File

@@ -1,7 +1,5 @@
using System;
using NPoco;
using Umbraco.Core.DI;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -20,7 +18,6 @@ namespace Umbraco.Core
public class DatabaseContext
{
private readonly IDatabaseFactory _databaseFactory;
private bool _canConnectOnce;
/// <summary>
/// Initializes a new instance of the <see cref="DatabaseContext"/> class.
@@ -46,7 +43,7 @@ namespace Umbraco.Core
public IQueryFactory QueryFactory => _databaseFactory.QueryFactory;
/// <summary>
/// Gets the database sql syntax.
/// Gets the database Sql syntax.
/// </summary>
public ISqlSyntaxProvider SqlSyntax => _databaseFactory.SqlSyntax;
@@ -66,11 +63,34 @@ namespace Umbraco.Core
public IQuery<T> Query<T>() => _databaseFactory.QueryFactory.Create<T>();
/// <summary>
/// Gets an "ambient" database for doing CRUD operations against custom tables that resides in the Umbraco database.
/// Gets an ambient database for doing CRUD operations against custom tables that resides in the Umbraco database.
/// </summary>
/// <remarks>Should not be used for operation against standard Umbraco tables; as services should be used instead.</remarks>
public UmbracoDatabase Database => _databaseFactory.GetDatabase();
/// <summary>
/// Gets an ambient database scope.
/// </summary>
/// <returns>A disposable object representing the scope.</returns>
public IDisposable CreateDatabaseScope() // fixme - move over to factory
{
var factory = _databaseFactory as UmbracoDatabaseFactory; // fixme - though... IDatabaseFactory?
if (factory == null) throw new NotSupportedException();
return factory.CreateScope();
}
#if DEBUG_DATABASES
public List<UmbracoDatabase> Databases
{
get
{
var factory = _databaseFactory as UmbracoDatabaseFactory;
if (factory == null) throw new NotSupportedException();
return factory.Databases;
}
}
#endif
/// <summary>
/// Gets a value indicating whether the database is configured.
/// </summary>
@@ -81,24 +101,6 @@ namespace Umbraco.Core
/// <summary>
/// Gets a value indicating whether it is possible to connect to the database.
/// </summary>
public bool CanConnect
{
get
{
var canConnect = _databaseFactory.Configured && _databaseFactory.CanConnect;
if (_canConnectOnce)
{
Current.Logger.Debug<DatabaseContext>("CanConnect: " + canConnect);
}
else
{
Current.Logger.Info<DatabaseContext>("CanConnect: " + canConnect);
_canConnectOnce = canConnect; // keep logging Info until we can connect
}
return canConnect;
}
}
public bool CanConnect => _databaseFactory.Configured && _databaseFactory.CanConnect;
}
}

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace Umbraco.Core.Events
{
@@ -47,5 +48,35 @@ namespace Umbraco.Core.Events
if (eventHandler != null)
eventHandler(sender, args);
}
}
// moves the last handler that was added to an instance event, to first position
public static void PromoteLastHandler(object sender, string eventName)
{
var fieldInfo = sender.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
PromoteLastHandler(sender, fieldInfo);
}
// moves the last handler that was added to a static event, to first position
public static void PromoteLastHandler<TSender>(string eventName)
{
var fieldInfo = typeof(TSender).GetField(eventName, BindingFlags.Static | BindingFlags.NonPublic);
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
PromoteLastHandler(null, fieldInfo);
}
private static void PromoteLastHandler(object sender, FieldInfo fieldInfo)
{
var d = fieldInfo.GetValue(sender) as Delegate;
if (d == null) return;
var l = d.GetInvocationList();
var x = l[l.Length - 1];
for (var i = l.Length - 1; i > 0; i--)
l[i] = l[i - 1];
l[0] = x;
fieldInfo.SetValue(sender, Delegate.Combine(l));
}
}
}

View File

@@ -0,0 +1,172 @@
#if DEBUG_DATABASES
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Reflection;
using System.Text;
namespace Umbraco.Core.Persistence
{
internal static class DatabaseDebugHelper
{
private const int CommandsSize = 100;
private static readonly Queue<Tuple<string, WeakReference<IDbCommand>>> Commands = new Queue<Tuple<string, WeakReference<IDbCommand>>>();
public static void SetCommand(IDbCommand command, string context)
{
command = command.UnwrapUmbraco();
lock (Commands)
{
Commands.Enqueue(Tuple.Create(context, new WeakReference<IDbCommand>(command)));
while (Commands.Count > CommandsSize) Commands.Dequeue();
}
}
public static string GetCommandContext(IDbCommand command)
{
lock (Commands)
{
var tuple = Commands.FirstOrDefault(x =>
{
IDbCommand c;
return x.Item2.TryGetTarget(out c) && c == command;
});
return tuple == null ? "?" : tuple.Item1;
}
}
public static string GetReferencedObjects(IDbConnection con)
{
con = con.UnwrapUmbraco();
var ceCon = con as System.Data.SqlServerCe.SqlCeConnection;
if (ceCon != null) return null; // "NotSupported: SqlCE";
var dbCon = con as DbConnection;
return dbCon == null
? "NotSupported: " + con.GetType()
: GetReferencedObjects(dbCon);
}
public static string GetReferencedObjects(DbConnection con)
{
var t = con.GetType();
var field = t.GetField("_innerConnection", BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null) throw new Exception("panic: _innerConnection (" + t + ").");
var innerConnection = field.GetValue(con);
var tin = innerConnection.GetType();
var fi = con is System.Data.SqlClient.SqlConnection
? tin.BaseType.BaseType.GetField("_referenceCollection", BindingFlags.Instance | BindingFlags.NonPublic)
: tin.BaseType.GetField("_referenceCollection", BindingFlags.Instance | BindingFlags.NonPublic);
if (fi == null)
//return "";
throw new Exception("panic: referenceCollection.");
var rc = fi.GetValue(innerConnection);
if (rc == null)
//return "";
throw new Exception("panic: innerCollection.");
field = rc.GetType().BaseType.GetField("_items", BindingFlags.Instance | BindingFlags.NonPublic);
if (field == null) throw new Exception("panic: items.");
var items = field.GetValue(rc);
var prop = items.GetType().GetProperty("Length", BindingFlags.Instance | BindingFlags.Public);
if (prop == null) throw new Exception("panic: Length.");
var count = Convert.ToInt32(prop.GetValue(items, null));
var miGetValue = items.GetType().GetMethod("GetValue", new[] { typeof(int) });
if (miGetValue == null) throw new Exception("panic: GetValue.");
if (count == 0) return null;
StringBuilder result = null;
var hasb = false;
for (var i = 0; i < count; i++)
{
var referencedObj = miGetValue.Invoke(items, new object[] { i });
var hasTargetProp = referencedObj.GetType().GetProperty("HasTarget");
if (hasTargetProp == null) throw new Exception("panic: HasTarget");
var hasTarget = Convert.ToBoolean(hasTargetProp.GetValue(referencedObj, null));
if (hasTarget == false) continue;
if (hasb == false)
{
result = new StringBuilder();
result.AppendLine("ReferencedItems");
hasb = true;
}
//var inUseProp = referencedObj.GetType().GetProperty("InUse");
//if (inUseProp == null) throw new Exception("panic: InUse.");
//var inUse = Convert.ToBoolean(inUseProp.GetValue(referencedObj, null));
var inUse = "?";
var targetProp = referencedObj.GetType().GetProperty("Target");
if (targetProp == null) throw new Exception("panic: Target.");
var objTarget = targetProp.GetValue(referencedObj, null);
result.AppendFormat("\tDiff.Item id=\"{0}\" inUse=\"{1}\" type=\"{2}\" hashCode=\"{3}\"" + Environment.NewLine,
i, inUse, objTarget.GetType(), objTarget.GetHashCode());
DbCommand cmd = null;
if (objTarget is DbDataReader)
{
//var rdr = objTarget as DbDataReader;
try
{
cmd = objTarget.GetType().GetProperty("Command", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(objTarget, null) as DbCommand;
}
catch (Exception e)
{
result.AppendFormat("\t\tObjTarget: DbDataReader, Exception: {0}" + Environment.NewLine, e);
}
}
else if (objTarget is DbCommand)
{
cmd = objTarget as DbCommand;
}
if (cmd == null)
{
result.AppendFormat("\t\tObjTarget: {0}" + Environment.NewLine, objTarget.GetType());
continue;
}
result.AppendFormat("\t\tCommand type=\"{0}\" hashCode=\"{1}\"" + Environment.NewLine,
cmd.GetType(), cmd.GetHashCode());
var context = GetCommandContext(cmd);
result.AppendFormat("\t\t\tContext: {0}" + Environment.NewLine, context);
var properties = cmd.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var pi in properties)
{
if (pi.PropertyType.IsPrimitive || pi.PropertyType == typeof(string))
result.AppendFormat("\t\t\t{0}: {1}" + Environment.NewLine, pi.Name, pi.GetValue(cmd, null));
if (pi.PropertyType != typeof (DbConnection) || pi.Name != "Connection") continue;
var con1 = pi.GetValue(cmd, null) as DbConnection;
result.AppendFormat("\t\t\tConnection type=\"{0}\" state=\"{1}\" hashCode=\"{2}\"" + Environment.NewLine,
con1.GetType(), con1.State, con1.GetHashCode());
var propertiesCon = con1.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var picon in propertiesCon)
{
if (picon.PropertyType.IsPrimitive || picon.PropertyType == typeof(string))
result.AppendFormat("\t\t\t\t{0}: {1}" + Environment.NewLine, picon.Name, picon.GetValue(con1, null));
}
}
}
return result?.ToString();
}
}
}
#endif

View File

@@ -0,0 +1,58 @@
using System;
namespace Umbraco.Core.Persistence
{
public class DatabaseScope : IDisposable
{
private readonly DatabaseScope _parent;
private readonly IDatabaseScopeAccessor _accessor;
private readonly UmbracoDatabaseFactory _factory;
private UmbracoDatabase _database;
private bool _isParent;
private bool _disposed;
private bool _disposeDatabase;
// can specify a database to create a "substitute" scope eg for deploy - oh my
internal DatabaseScope(IDatabaseScopeAccessor accessor, UmbracoDatabaseFactory factory, UmbracoDatabase database = null)
{
_accessor = accessor;
_factory = factory;
_database = database;
_parent = _accessor.Scope;
if (_parent != null) _parent._isParent = true;
_accessor.Scope = this;
}
public UmbracoDatabase Database
{
get
{
if (_disposed)
throw new ObjectDisposedException(null, "Cannot access a disposed object.");
if (_database != null) return _database;
if (_parent != null) return _parent.Database;
_database = _factory.CreateDatabase();
_disposeDatabase = true;
return _database;
}
}
public void Dispose()
{
if (_isParent)
throw new InvalidOperationException("Cannot dispose a parent scope.");
if (_disposed)
throw new ObjectDisposedException(null, "Cannot access a disposed object.");
_disposed = true; // fixme race
if (_disposeDatabase)
_database.Dispose();
_accessor.Scope = _parent;
if (_parent != null) _parent._isParent = false;
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,29 @@
using System.Data;
namespace Umbraco.Core.Persistence
{
internal static class DbCommandExtensions
{
/// <summary>
/// Unwraps a database command.
/// </summary>
/// <remarks>UmbracoDatabase wraps the original database connection in various layers (see
/// OnConnectionOpened); this unwraps and returns the original database command.</remarks>
public static IDbCommand UnwrapUmbraco(this IDbCommand command)
{
IDbCommand unwrapped;
var c = command;
do
{
unwrapped = c;
var profiled = unwrapped as StackExchange.Profiling.Data.ProfiledDbCommand;
if (profiled != null) unwrapped = profiled.InternalCommand;
} while (c != unwrapped);
return unwrapped;
}
}
}

View File

@@ -2,9 +2,9 @@
using System.Data;
using System.Data.Common;
using System.Linq;
using NPoco;
using Umbraco.Core.DI;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence.FaultHandling;
namespace Umbraco.Core.Persistence
{
@@ -68,6 +68,29 @@ namespace Umbraco.Core.Persistence
return true;
}
/// <summary>
/// Unwraps a database connection.
/// </summary>
/// <remarks>UmbracoDatabase wraps the original database connection in various layers (see
/// OnConnectionOpened); this unwraps and returns the original database connection.</remarks>
internal static IDbConnection UnwrapUmbraco(this IDbConnection connection)
{
IDbConnection unwrapped;
var c = connection;
do
{
unwrapped = c;
var profiled = unwrapped as StackExchange.Profiling.Data.ProfiledDbConnection;
if (profiled != null) unwrapped = profiled.InnerConnection;
var retrying = unwrapped as RetryDbConnection;
if (retrying != null) unwrapped = retrying.Inner;
} while (c != unwrapped);
return unwrapped;
}
}
}

View File

@@ -9,9 +9,10 @@ namespace Umbraco.Core.Persistence.Factories
{
internal class MemberTypeReadOnlyFactory
{
public IMemberType BuildEntity(MemberTypeReadOnlyDto dto)
public IMemberType BuildEntity(MemberTypeReadOnlyDto dto, out bool needsSaving)
{
var standardPropertyTypes = Constants.Conventions.Member.GetStandardPropertyTypeStubs();
needsSaving = false;
var memberType = new MemberType(dto.ParentId);
@@ -47,6 +48,12 @@ namespace Umbraco.Core.Persistence.Factories
{
if (dto.PropertyTypes.Any(x => x.Alias.Equals(standardPropertyType.Key))) continue;
// beware!
// means that we can return a memberType "from database" that has some property types
// that do *not* come from the database and therefore are incomplete eg have no key,
// no id, no dataTypeDefinitionId - ouch! - better notify caller of the situation
needsSaving = true;
//Add the standard PropertyType to the current list
propertyTypes.Add(standardPropertyType.Value);

View File

@@ -0,0 +1,10 @@
namespace Umbraco.Core.Persistence
{
/// <summary>
/// Provides access to DatabaseScope.
/// </summary>
public interface IDatabaseScopeAccessor
{
DatabaseScope Scope { get; set; }
}
}

View File

@@ -316,13 +316,19 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
/// <param name="dtos"></param>
/// <returns></returns>
private static IEnumerable<IMemberType> BuildFromDtos(List<MemberTypeReadOnlyDto> dtos)
private IEnumerable<IMemberType> BuildFromDtos(List<MemberTypeReadOnlyDto> dtos)
{
if (dtos == null || dtos.Any() == false)
return Enumerable.Empty<IMemberType>();
var factory = new MemberTypeReadOnlyFactory();
return dtos.Select(factory.BuildEntity);
return dtos.Select(x =>
{
bool needsSaving;
var memberType = factory.BuildEntity(x, out needsSaving);
if (needsSaving) PersistUpdatedItem(memberType);
return memberType;
}).ToList();
}
/// <summary>

View File

@@ -16,7 +16,7 @@ namespace Umbraco.Core.Persistence
/// <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>
/// <para>Is never created directly but obtained from the <see cref="DefaultDatabaseFactory"/>.</para>
/// <para>Is never created directly but obtained from the <see cref="UmbracoDatabaseFactory"/>.</para>
/// <para>It implements IDisposeOnRequestEnd which means it will be disposed when the request ends, which
/// automatically closes the connection - as implemented by NPoco Database.Dispose().</para>
/// </remarks>
@@ -29,48 +29,17 @@ namespace Umbraco.Core.Persistence
private readonly SqlContext _sqlContext;
private readonly RetryPolicy _connectionRetryPolicy;
private readonly RetryPolicy _commandRetryPolicy;
private bool _enableCount;
#region Ctor
/// <summary>
/// Used for testing
/// Initializes a new instance of the <see cref="UmbracoDatabase"/> class.
/// </summary>
internal Guid InstanceId { get; } = Guid.NewGuid();
public ISqlSyntaxProvider SqlSyntax => _sqlContext.SqlSyntax;
/// <summary>
/// Generally used for testing, will output all SQL statements executed to the logger
/// </summary>
internal bool EnableSqlTrace { get; set; }
/// <summary>
/// Used for testing
/// </summary>
internal void EnableSqlCount()
{
_enableCount = true;
}
/// <summary>
/// Used for testing
/// </summary>
internal void DisableSqlCount()
{
_enableCount = false;
SqlCount = 0;
}
/// <summary>
/// Used for testing
/// </summary>
internal int SqlCount { get; private set; }
// used by DefaultDatabaseFactory
// creates one instance per request
// also used by DatabaseContext for creating DBs and upgrading
public UmbracoDatabase(string connectionString,
SqlContext sqlContext, DbProviderFactory provider, ILogger logger,
RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
/// <remarks>
/// <para>Used by UmbracoDatabaseFactory to create databases.</para>
/// <para>Also used by DatabaseBuilder for creating databases and installing/upgrading.</para>
/// </remarks>
public UmbracoDatabase(string connectionString, SqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
: base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel)
{
_sqlContext = sqlContext;
@@ -79,41 +48,134 @@ namespace Umbraco.Core.Persistence
_connectionRetryPolicy = connectionRetryPolicy;
_commandRetryPolicy = commandRetryPolicy;
EnableSqlTrace = false;
EnableSqlTrace = EnableSqlTraceDefault;
}
// INTERNAL FOR UNIT TESTS
internal UmbracoDatabase(DbConnection connection,
SqlContext sqlContext, ILogger logger)
/// <summary>
/// Initializes a new instance of the <see cref="UmbracoDatabase"/> class.
/// </summary>
/// <remarks>Internal for unit tests only.</remarks>
internal UmbracoDatabase(DbConnection connection, SqlContext sqlContext, ILogger logger)
: base(connection, sqlContext.DatabaseType, DefaultIsolationLevel)
{
_sqlContext = sqlContext;
_logger = logger;
EnableSqlTrace = false;
EnableSqlTrace = EnableSqlTraceDefault;
}
// fixme: these two could be an extension method of IUmbracoDatabaseConfig
#endregion
/// <summary>
/// Gets the database Sql syntax.
/// </summary>
public ISqlSyntaxProvider SqlSyntax => _sqlContext.SqlSyntax;
/// <summary>
/// Creates a Sql statement.
/// </summary>
public Sql<SqlContext> Sql()
{
return NPoco.Sql.BuilderFor(_sqlContext);
}
/// <summary>
/// Creates a Sql statement.
/// </summary>
public Sql<SqlContext> Sql(string sql, params object[] args)
{
return Sql().Append(sql, args);
}
//protected override void OnConnectionClosing(DbConnection conn)
//{
// base.OnConnectionClosing(conn);
//}
#region Testing, Debugging and Troubleshooting
private bool _enableCount;
#if DEBUG_DATABASES
private int _spid = -1;
private const bool EnableSqlTraceDefault = true;
#else
private string _sid;
private const bool EnableSqlTraceDefault = false;
#endif
/// <summary>
/// Gets this instance's unique identifier.
/// </summary>
public Guid InstanceId { get; } = Guid.NewGuid();
/// <summary>
/// Gets this instance's string identifier.
/// </summary>
public string InstanceSid {
get
{
#if DEBUG_DATABASES
return InstanceId.ToString("N").Substring(0, 8) + ':' + _spid;
#else
return _sid ?? (_sid = InstanceId.ToString("N").Substring(0, 8));
#endif
}
}
/// <summary>
/// Gets or sets a value indicating whether to log all executed Sql statements.
/// </summary>
internal bool EnableSqlTrace { get; set; }
/// <summary>
/// Gets or sets a value indicating whether to count all executed Sql statements.
/// </summary>
internal bool EnableSqlCount
{
get { return _enableCount; }
set
{
_enableCount = value;
if (_enableCount == false)
SqlCount = 0;
}
}
/// <summary>
/// Gets the count of all executed Sql statements.
/// </summary>
internal int SqlCount { get; private set; }
#endregion
#region OnSomething
// fixme.poco - has new interceptors to replace OnSomething?
protected override DbConnection OnConnectionOpened(DbConnection connection)
{
if (connection == null) throw new ArgumentNullException(nameof(connection));
#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
connection = new StackExchange.Profiling.Data.ProfiledDbConnection(connection, MiniProfiler.Current);
@@ -124,14 +186,20 @@ namespace Umbraco.Core.Persistence
return connection;
}
#if DEBUG_DATABASES
public override void OnConnectionClosing(IDbConnection conn)
{
_spid = -1;
base.OnConnectionClosing(conn);
}
#endif
protected override void OnException(Exception x)
{
_logger.Error<UmbracoDatabase>("Database exception occurred", x);
_logger.Error<UmbracoDatabase>("Exception (" + InstanceSid + ").", x);
base.OnException(x);
}
// fixme.poco - has new interceptors?
protected override void OnExecutingCommand(DbCommand cmd)
{
// if no timeout is specified, and the connection has a longer timeout, use it
@@ -141,6 +209,10 @@ 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)
{
@@ -148,21 +220,38 @@ namespace Umbraco.Core.Persistence
sb.Append(p.Value);
}
_logger.Debug<UmbracoDatabase>(sb.ToString());
_logger.Debug<UmbracoDatabase>(sb.ToString().Replace("{", "{{").Replace("}", "}}"));
}
#if DEBUG_DATABASES
// detects whether the command is already in use (eg still has an open reader...)
DatabaseDebugHelper.SetCommand(cmd, InstanceSid + " [T" + Thread.CurrentThread.ManagedThreadId + "]");
var refsobj = DatabaseDebugHelper.GetReferencedObjects(cmd.Connection);
if (refsobj != null) _logger.Debug<UmbracoDatabase>("Oops!" + Environment.NewLine + refsobj);
#endif
base.OnExecutingCommand(cmd);
}
protected override void OnExecutedCommand(DbCommand cmd)
{
if (_enableCount)
{
SqlCount++;
}
base.OnExecutedCommand(cmd);
}
// fixme - see v7.6 - what about disposing & managing context and call context?
#endregion
// at the moment, NPoco does not support overriding Dispose
/*
public override void Dispose(bool disposing)
{
#if DEBUG_DATABASES
LogHelper.Debug<UmbracoDatabase>("Dispose (" + InstanceSid + ").");
#endif
base.Dispose();
}
*/
}
}

View File

@@ -26,14 +26,15 @@ namespace Umbraco.Core.Persistence
/// <para>It wraps an NPoco DatabaseFactory which is initializes with a proper IPocoDataFactory to ensure
/// that NPoco's plumbing is cached appropriately for the whole application.</para>
/// </remarks>
internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory
internal class UmbracoDatabaseFactory : DisposableObject, IDatabaseFactory
{
private readonly IUmbracoDatabaseAccessor _umbracoDatabaseAccessor;
//private readonly IUmbracoDatabaseAccessor _umbracoDatabaseAccessor;
private readonly IDatabaseScopeAccessor _databaseScopeAccessor;
private readonly ISqlSyntaxProvider[] _sqlSyntaxProviders;
private readonly IMapperCollection _mappers;
private readonly ILogger _logger;
private DatabaseFactory _databaseFactory;
private DatabaseFactory _npocoDatabaseFactory;
private IPocoDataFactory _pocoDataFactory;
private string _connectionString;
private string _providerName;
@@ -46,42 +47,35 @@ namespace Umbraco.Core.Persistence
private RetryPolicy _commandRetryPolicy;
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
#region Ctor
/// <summary>
/// Initializes a new instance of the <see cref="DefaultDatabaseFactory"/> with the default connection, and a logger.
/// Initializes a new instance of the <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <param name="sqlSyntaxProviders">The collection of available sql syntax providers.</param>
/// <param name="logger">A logger.</param>
/// <param name="umbracoDatabaseAccessor"></param>
/// <param name="mappers"></param>
/// <remarks>Used by LightInject.</remarks>
public DefaultDatabaseFactory(IEnumerable<ISqlSyntaxProvider> sqlSyntaxProviders, ILogger logger, IUmbracoDatabaseAccessor umbracoDatabaseAccessor, IMapperCollection mappers)
: this(GlobalSettings.UmbracoConnectionName, sqlSyntaxProviders, logger, umbracoDatabaseAccessor, mappers)
public UmbracoDatabaseFactory(IEnumerable<ISqlSyntaxProvider> sqlSyntaxProviders, ILogger logger, IDatabaseScopeAccessor databaseScopeAccessor, IMapperCollection mappers)
: this(GlobalSettings.UmbracoConnectionName, sqlSyntaxProviders, logger, databaseScopeAccessor, mappers)
{
if (Configured == false)
DatabaseBuilder.GiveLegacyAChance(this, logger);
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultDatabaseFactory"/> with a connection string name and a logger.
/// Initializes a new instance of the <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <param name="connectionStringName">The name of the connection string in web.config.</param>
/// <param name="sqlSyntaxProviders">The collection of available sql syntax providers.</param>
/// <param name="logger">A logger</param>
/// <param name="umbracoDatabaseAccessor"></param>
/// <param name="mappers"></param>
/// <remarks>Used by the other ctor and in tests.</remarks>
public DefaultDatabaseFactory(string connectionStringName, IEnumerable<ISqlSyntaxProvider> sqlSyntaxProviders, ILogger logger, IUmbracoDatabaseAccessor umbracoDatabaseAccessor, IMapperCollection mappers)
public UmbracoDatabaseFactory(string connectionStringName, IEnumerable<ISqlSyntaxProvider> sqlSyntaxProviders, ILogger logger, IDatabaseScopeAccessor databaseScopeAccessor, IMapperCollection mappers)
{
if (sqlSyntaxProviders == null) throw new ArgumentNullException(nameof(sqlSyntaxProviders));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (umbracoDatabaseAccessor == null) throw new ArgumentNullException(nameof(umbracoDatabaseAccessor));
if (databaseScopeAccessor == null) throw new ArgumentNullException(nameof(databaseScopeAccessor));
if (string.IsNullOrWhiteSpace(connectionStringName)) throw new ArgumentNullOrEmptyException(nameof(connectionStringName));
if (mappers == null) throw new ArgumentNullException(nameof(mappers));
_mappers = mappers;
_sqlSyntaxProviders = sqlSyntaxProviders.ToArray();
_logger = logger;
_umbracoDatabaseAccessor = umbracoDatabaseAccessor;
_databaseScopeAccessor = databaseScopeAccessor;
var settings = ConfigurationManager.ConnectionStrings[connectionStringName];
if (settings == null)
@@ -93,7 +87,7 @@ namespace Umbraco.Core.Persistence
var providerName = settings.ProviderName;
if (string.IsNullOrWhiteSpace(connectionString) || string.IsNullOrWhiteSpace(providerName))
{
logger.Debug<DefaultDatabaseFactory>("Missing connection string or provider name, defer configuration.");
logger.Debug<UmbracoDatabaseFactory>("Missing connection string or provider name, defer configuration.");
return; // not configured
}
@@ -101,36 +95,32 @@ namespace Umbraco.Core.Persistence
}
/// <summary>
/// Initializes a new instance of the <see cref="DefaultDatabaseFactory"/> with a connection string, a provider name and a logger.
/// Initializes a new instance of the <see cref="UmbracoDatabaseFactory"/>.
/// </summary>
/// <param name="connectionString">The database connection string.</param>
/// <param name="providerName">The name of the database provider.</param>
/// <param name="sqlSyntaxProviders">The collection of available sql syntax providers.</param>
/// <param name="logger">A logger.</param>
/// <param name="umbracoDatabaseAccessor"></param>
/// <param name="mappers"></param>
/// <remarks>Used in tests.</remarks>
public DefaultDatabaseFactory(string connectionString, string providerName, IEnumerable<ISqlSyntaxProvider> sqlSyntaxProviders, ILogger logger, IUmbracoDatabaseAccessor umbracoDatabaseAccessor, IMapperCollection mappers)
public UmbracoDatabaseFactory(string connectionString, string providerName, IEnumerable<ISqlSyntaxProvider> sqlSyntaxProviders, ILogger logger, IDatabaseScopeAccessor databaseScopeAccessor, IMapperCollection mappers)
{
if (sqlSyntaxProviders == null) throw new ArgumentNullException(nameof(sqlSyntaxProviders));
if (logger == null) throw new ArgumentNullException(nameof(logger));
if (umbracoDatabaseAccessor == null) throw new ArgumentNullException(nameof(umbracoDatabaseAccessor));
if (databaseScopeAccessor == null) throw new ArgumentNullException(nameof(databaseScopeAccessor));
if (mappers == null) throw new ArgumentNullException(nameof(mappers));
_mappers = mappers;
_sqlSyntaxProviders = sqlSyntaxProviders.ToArray();
_logger = logger;
_umbracoDatabaseAccessor = umbracoDatabaseAccessor;
_databaseScopeAccessor = databaseScopeAccessor;
if (string.IsNullOrWhiteSpace(connectionString) || string.IsNullOrWhiteSpace(providerName))
{
logger.Debug<DefaultDatabaseFactory>("Missing connection string or provider name, defer configuration.");
logger.Debug<UmbracoDatabaseFactory>("Missing connection string or provider name, defer configuration.");
return; // not configured
}
Configure(connectionString, providerName);
}
#endregion
/// <summary>
/// Gets a value indicating whether the database is configured (no connect test).
/// </summary>
@@ -172,7 +162,7 @@ namespace Umbraco.Core.Persistence
{
using (new WriteLock(_lock))
{
_logger.Debug<DefaultDatabaseFactory>("Configuring.");
_logger.Debug<UmbracoDatabaseFactory>("Configuring.");
if (Configured) throw new InvalidOperationException("Already configured.");
@@ -204,19 +194,19 @@ namespace Umbraco.Core.Persistence
var config = new FluentConfig(xmappers => factory);
// create the database factory
_databaseFactory = DatabaseFactory.Config(x => x
_npocoDatabaseFactory = DatabaseFactory.Config(x => x
.UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances
.WithFluentConfig(config)); // with proper configuration
if (_databaseFactory == null) throw new NullReferenceException("The call to DatabaseFactory.Config yielded a null DatabaseFactory instance.");
if (_npocoDatabaseFactory == null) throw new NullReferenceException("The call to DatabaseFactory.Config yielded a null DatabaseFactory instance.");
// these are created here because it is the DefaultDatabaseFactory that determines
// these are created here because it is the UmbracoDatabaseFactory that determines
// the sql syntax, poco data factory, and database type - so it "owns" the context
// and the query factory
_sqlContext = new SqlContext(_sqlSyntax, _pocoDataFactory, _databaseType);
_queryFactory = new QueryFactory(_sqlSyntax, _mappers);
_logger.Debug<DefaultDatabaseFactory>("Configured.");
_logger.Debug<UmbracoDatabaseFactory>("Configured.");
Configured = true;
}
}
@@ -252,20 +242,36 @@ namespace Umbraco.Core.Persistence
return new UmbracoDatabase(_connectionString, _sqlContext, _dbProviderFactory, _logger, _connectionRetryPolicy, _commandRetryPolicy);
}
// fixme temp?
public UmbracoDatabase Database => GetDatabase();
/// <summary>
/// Gets (creates or retrieves) the "ambient" database connection.
/// Gets (creates or retrieves) the ambient database connection.
/// </summary>
/// <returns>The "ambient" database connection.</returns>
/// <returns>The ambient database connection.</returns>
public UmbracoDatabase GetDatabase()
{
EnsureConfigured();
// check if it's in scope
var db = _umbracoDatabaseAccessor.UmbracoDatabase;
if (db != null) return db;
db = (UmbracoDatabase) _databaseFactory.GetDatabase();
_umbracoDatabaseAccessor.UmbracoDatabase = db;
return db;
var scope = _databaseScopeAccessor.Scope;
if (scope == null) throw new InvalidOperationException("Out of scope.");
return scope.Database;
//// check if it's in scope
//var db = _umbracoDatabaseAccessor.UmbracoDatabase;
//if (db != null) return db;
//db = (UmbracoDatabase) _npocoDatabaseFactory.GetDatabase();
//_umbracoDatabaseAccessor.UmbracoDatabase = db;
//return db;
}
/// <summary>
/// Creates a new database instance.
/// </summary>
/// <remarks>The database instance is not part of any scope and must be disposed after being used.</remarks>
public UmbracoDatabase CreateDatabase()
{
return (UmbracoDatabase) _npocoDatabaseFactory.GetDatabase();
}
protected override void DisposeResources()
@@ -274,9 +280,10 @@ namespace Umbraco.Core.Persistence
// thread, so we don't really know what we are disposing here...
// besides, we don't really want to dispose the factory, which is a singleton...
var db = _umbracoDatabaseAccessor.UmbracoDatabase;
_umbracoDatabaseAccessor.UmbracoDatabase = null;
db?.Dispose();
// fixme - does not make any sense!
//var db = _umbracoDatabaseAccessor.UmbracoDatabase;
//_umbracoDatabaseAccessor.UmbracoDatabase = null;
//db?.Dispose();
Configured = false;
}
@@ -284,9 +291,90 @@ namespace Umbraco.Core.Persistence
// this method provides a way to force-reset the variable
internal void ResetForTests()
{
var db = _umbracoDatabaseAccessor.UmbracoDatabase;
_umbracoDatabaseAccessor.UmbracoDatabase = null;
db?.Dispose();
// fixme - does not make any sense!
//var db = _umbracoDatabaseAccessor.UmbracoDatabase;
//_umbracoDatabaseAccessor.UmbracoDatabase = null;
//db?.Dispose();
_databaseScopeAccessor.Scope = null;
}
//public bool HasAmbient => _umbracoDatabaseAccessor.UmbracoDatabase != null;
//public UmbracoDatabase DetachAmbient()
//{
// var database = _umbracoDatabaseAccessor.UmbracoDatabase;
// _umbracoDatabaseAccessor.UmbracoDatabase = null;
// return database;
//}
//public void AttachAmbient(UmbracoDatabase database)
//{
// var tmp = _umbracoDatabaseAccessor.UmbracoDatabase;
// _umbracoDatabaseAccessor.UmbracoDatabase = database;
// tmp?.Dispose();
// // fixme - what shall we do with tmp?
// // fixme - what about using "disposing" of the database to remove it from "ambient"?!
//}
//public IDisposable CreateScope(bool force = false) // fixme - why would we ever force?
//{
// if (HasAmbient)
// {
// return force
// ? new DatabaseScope(this, DetachAmbient(), GetDatabase())
// : new DatabaseScope(this, null, null);
// }
// // create a new, temp, database (will be disposed with DatabaseScope)
// return new DatabaseScope(this, null, GetDatabase());
//}
public IDisposable CreateScope()
{
return new DatabaseScope(_databaseScopeAccessor, this);
}
/*
private class DatabaseScope : IDisposable
{
private readonly UmbracoDatabaseFactory _factory;
private readonly UmbracoDatabase _orig;
private readonly UmbracoDatabase _temp;
// orig is the original database that was ambient when the scope was created
// if not null, it has been detached in order to be replaced by temp, which cannot be null
// if null, either there was no ambient database, or we don't want to replace it
// temp is the scope database that is created for the scope
// if not null, it has been attached and is not the ambient database,
// and when the scope is disposed it will be detached, disposed, and replaced by orig
// if null, the scope is nested and reusing the ambient database, without touching anything
public DatabaseScope(UmbracoDatabaseFactory factory, UmbracoDatabase orig, UmbracoDatabase temp)
{
if (factory == null) throw new ArgumentNullException(nameof(factory));
_factory = factory;
_orig = orig;
_temp = temp;
}
public void Dispose()
{
if (_temp != null) // if the scope had its own database
{
// detach and ensure consistency, then dispose
var temp = _factory.DetachAmbient();
if (temp != _temp) throw new Exception("bam!");
temp.Dispose();
// re-instate original database if any
if (_orig != null)
_factory.AttachAmbient(_orig);
}
GC.SuppressFinalize(this);
}
}
*/
}
}

View File

@@ -295,7 +295,9 @@
<Compile Include="Models\PublishedContent\PropertyResult.cs" />
<Compile Include="Models\PublishedContent\PropertyResultType.cs" />
<Compile Include="Models\Rdbms\ContentNuDto.cs" />
<Compile Include="Persistence\DbCommandExtensions.cs" />
<Compile Include="Persistence\IUmbracoDatabaseAccessor.cs" />
<Compile Include="Persistence\IDatabaseScopeAccessor.cs" />
<Compile Include="Persistence\Mappers\IMapperCollection.cs" />
<Compile Include="Persistence\Mappers\MapperCollection.cs" />
<Compile Include="Persistence\Mappers\MapperCollectionBuilder.cs" />
@@ -305,6 +307,7 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionEight\AddContentNuTable.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionEight\RefactorXmlColumns.cs" />
<Compile Include="Persistence\ThreadStaticUmbracoDatabaseAccessor.cs" />
<Compile Include="Persistence\DatabaseScope.cs" />
<Compile Include="Plugins\HideFromTypeFinderAttribute.cs" />
<Compile Include="HttpContextExtensions.cs" />
<Compile Include="ICompletable.cs" />
@@ -389,6 +392,7 @@
<Compile Include="Persistence\BulkDataReader.cs" />
<Compile Include="Persistence\Constants-Locks.cs" />
<Compile Include="Persistence\DatabaseNodeLockExtensions.cs" />
<Compile Include="Persistence\DatabaseDebugHelper.cs" />
<Compile Include="Persistence\Factories\ExternalLoginFactory.cs" />
<Compile Include="Persistence\Factories\MigrationEntryFactory.cs" />
<Compile Include="Persistence\Factories\PublicAccessEntryFactory.cs" />
@@ -823,7 +827,7 @@
<Compile Include="Persistence\DatabaseModelDefinitions\ModificationType.cs" />
<Compile Include="Persistence\DatabaseModelDefinitions\SystemMethods.cs" />
<Compile Include="Persistence\DatabaseModelDefinitions\TableDefinition.cs" />
<Compile Include="Persistence\DefaultDatabaseFactory.cs" />
<Compile Include="Persistence\UmbracoDatabaseFactory.cs" />
<Compile Include="Persistence\Factories\ContentFactory.cs" />
<Compile Include="Persistence\Factories\ContentTypeFactory.cs" />
<Compile Include="Persistence\Factories\DataTypeDefinitionFactory.cs" />

View File

@@ -13,7 +13,6 @@ namespace Umbraco.Core
/// <summary>
/// Provides an abstract base class for the Umbraco HttpApplication.
/// </summary>
/// This is exposed in the core so that we can have the IApplicationEventHandler in the core project so that
public abstract class UmbracoApplicationBase : HttpApplication
{
private IRuntime _runtime;

View File

@@ -49,7 +49,7 @@ namespace Umbraco.Tests.Benchmarks
IDatabaseFactory f = null;
var l = new Lazy<IDatabaseFactory>(() => f);
var p = new SqlServerSyntaxProvider(l);
f = new DefaultDatabaseFactory(
f = new UmbracoDatabaseFactory(
"server=.\\SQLExpress;database=YOURDB;user id=YOURUSER;password=YOURPASS",
Constants.DatabaseProviders.SqlServer,
new [] { p },
@@ -61,7 +61,7 @@ namespace Umbraco.Tests.Benchmarks
private UmbracoDatabase GetSqlCeDatabase(string cstr, ILogger logger)
{
var f = new DefaultDatabaseFactory(
var f = new UmbracoDatabaseFactory(
cstr,
Constants.DatabaseProviders.SqlCe,
new[] { new SqlCeSyntaxProvider() },

View File

@@ -38,7 +38,7 @@ namespace Umbraco.Tests.Persistence
_sqlCeSyntaxProvider = new SqlCeSyntaxProvider();
_sqlSyntaxProviders = new[] { (ISqlSyntaxProvider) _sqlCeSyntaxProvider };
_logger = Mock.Of<ILogger>();
var dbFactory = new DefaultDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, _sqlSyntaxProviders, _logger, new TestUmbracoDatabaseAccessor(), Mock.Of<IMapperCollection>());
var dbFactory = new UmbracoDatabaseFactory(Core.Configuration.GlobalSettings.UmbracoConnectionName, _sqlSyntaxProviders, _logger, new TestUmbracoDatabaseAccessor(), Mock.Of<IMapperCollection>());
_runtime = Mock.Of<IRuntimeState>();
_migrationEntryService = Mock.Of<IMigrationEntryService>();
_dbContext = new DatabaseContext(dbFactory);
@@ -91,7 +91,7 @@ namespace Umbraco.Tests.Persistence
}
// re-create the database factory and database context with proper connection string
var dbFactory = new DefaultDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _sqlSyntaxProviders, _logger, new TestUmbracoDatabaseAccessor(), Mock.Of<IMapperCollection>());
var dbFactory = new UmbracoDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _sqlSyntaxProviders, _logger, new TestUmbracoDatabaseAccessor(), Mock.Of<IMapperCollection>());
_dbContext = new DatabaseContext(dbFactory);
// create application context

View File

@@ -23,7 +23,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling
const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=x;password=umbraco";
const string providerName = Constants.DbProviderNames.SqlServer;
var sqlSyntax = new[] { new SqlServerSyntaxProvider(new Lazy<IDatabaseFactory>(() => null)) };
var factory = new DefaultDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of<ILogger>(), new TestUmbracoDatabaseAccessor(), Mock.Of<IMapperCollection>());
var factory = new UmbracoDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of<ILogger>(), new TestUmbracoDatabaseAccessor(), Mock.Of<IMapperCollection>());
var database = factory.GetDatabase();
//Act
@@ -38,7 +38,7 @@ namespace Umbraco.Tests.Persistence.FaultHandling
const string connectionString = @"server=.\SQLEXPRESS;database=EmptyForTest;user id=umbraco;password=umbraco";
const string providerName = Constants.DbProviderNames.SqlServer;
var sqlSyntax = new[] { new SqlServerSyntaxProvider(new Lazy<IDatabaseFactory>(() => null)) };
var factory = new DefaultDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of<ILogger>(), new TestUmbracoDatabaseAccessor(), Mock.Of<IMapperCollection>());
var factory = new UmbracoDatabaseFactory(connectionString, providerName, sqlSyntax, Mock.Of<ILogger>(), new TestUmbracoDatabaseAccessor(), Mock.Of<IMapperCollection>());
var database = factory.GetDatabase();
//Act

View File

@@ -482,7 +482,7 @@ namespace Umbraco.Tests.Persistence.Repositories
try
{
DatabaseContext.Database.EnableSqlTrace = true;
DatabaseContext.Database.EnableSqlCount();
DatabaseContext.Database.EnableSqlCount = true;
var result = repository.GetPagedResultsByQuery(query, 0, 2, out totalRecords, "title", Direction.Ascending, false);
@@ -496,7 +496,7 @@ namespace Umbraco.Tests.Persistence.Repositories
finally
{
DatabaseContext.Database.EnableSqlTrace = false;
DatabaseContext.Database.DisableSqlCount();
DatabaseContext.Database.EnableSqlCount = false;
}
}
}
@@ -517,7 +517,7 @@ namespace Umbraco.Tests.Persistence.Repositories
try
{
DatabaseContext.Database.EnableSqlTrace = true;
DatabaseContext.Database.EnableSqlCount();
DatabaseContext.Database.EnableSqlCount = true;
var result = repository.GetPagedResultsByQuery(query, 0, 1, out totalRecords, "Name", Direction.Ascending, true);
// Assert
@@ -528,7 +528,7 @@ namespace Umbraco.Tests.Persistence.Repositories
finally
{
DatabaseContext.Database.EnableSqlTrace = false;
DatabaseContext.Database.DisableSqlCount();
DatabaseContext.Database.EnableSqlCount = false;
}
}
}

View File

@@ -1460,7 +1460,7 @@ namespace Umbraco.Tests.Services
[Test]
public void Can_Save_Lazy_Content()
{
var databaseFactory = new DefaultDatabaseFactory(
var databaseFactory = new UmbracoDatabaseFactory(
Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName,
TestObjects.GetDefaultSqlSyntaxProviders(Logger),
Logger,

View File

@@ -129,7 +129,7 @@ namespace Umbraco.Tests.Services
}
DatabaseContext.Database.EnableSqlTrace = true;
DatabaseContext.Database.EnableSqlCount();
DatabaseContext.Database.EnableSqlCount = true;
var items = ServiceContext.LocalizationService.GetDictionaryItemDescendants(_parentItemGuidId)
.ToArray();
@@ -143,7 +143,7 @@ namespace Umbraco.Tests.Services
finally
{
DatabaseContext.Database.EnableSqlTrace = false;
DatabaseContext.Database.DisableSqlCount();
DatabaseContext.Database.EnableSqlCount = false;
}
}

View File

@@ -229,13 +229,13 @@ namespace Umbraco.Tests.Services
}
/// <summary>
/// A special implementation of <see cref="IDatabaseFactory"/> that mimics the DefaultDatabaseFactory
/// A special implementation of <see cref="IDatabaseFactory"/> that mimics the UmbracoDatabaseFactory
/// (one db per HttpContext) by providing one db per thread, as required for multi-threaded
/// tests.
/// </summary>
internal class PerThreadSqlCeDatabaseFactory : DisposableObject, IDatabaseFactory
{
// the DefaultDatabaseFactory uses thread-static databases where there is no http context,
// the UmbracoDatabaseFactory uses thread-static databases where there is no http context,
// so it would need to be disposed in each thread in order for each database to be disposed,
// instead we use this factory which also maintains one database per thread but can dispose
// them all in one call

View File

@@ -226,7 +226,7 @@ namespace Umbraco.Tests.TestHelpers
//mappersBuilder.AddCore();
//var mappers = mappersBuilder.CreateCollection();
var mappers = Current.Container.GetInstance<IMapperCollection>();
databaseFactory = new DefaultDatabaseFactory(GlobalSettings.UmbracoConnectionName, GetDefaultSqlSyntaxProviders(logger), logger, accessor, mappers);
databaseFactory = new UmbracoDatabaseFactory(GlobalSettings.UmbracoConnectionName, GetDefaultSqlSyntaxProviders(logger), logger, accessor, mappers);
}
repositoryFactory = repositoryFactory ?? new RepositoryFactory(Mock.Of<IServiceContainer>());
return new NPocoUnitOfWorkProvider(new DatabaseContext(databaseFactory), repositoryFactory);

View File

@@ -121,7 +121,7 @@ namespace Umbraco.Tests.TestHelpers
Container.RegisterSingleton<IUmbracoDatabaseAccessor, TestUmbracoDatabaseAccessor>();
var sqlSyntaxProviders = TestObjects.GetDefaultSqlSyntaxProviders(Logger);
Container.RegisterSingleton<ISqlSyntaxProvider>(_ => sqlSyntaxProviders.OfType<SqlCeSyntaxProvider>().First());
Container.RegisterSingleton<IDatabaseFactory>(f => new DefaultDatabaseFactory(
Container.RegisterSingleton<IDatabaseFactory>(f => new UmbracoDatabaseFactory(
Core.Configuration.GlobalSettings.UmbracoConnectionName,
sqlSyntaxProviders,
Logger, f.GetInstance<IUmbracoDatabaseAccessor>(),

View File

@@ -42,7 +42,7 @@ namespace Umbraco.Tests.TestHelpers
/// </summary>
/// <remarks>
/// <para>Can provide a SqlCE database populated with the Umbraco schema. The database should
/// be accessed through the DefaultDatabaseFactory.</para>
/// be accessed through the UmbracoDatabaseFactory.</para>
/// <para>Provides an Umbraco context and Xml content.</para>
/// <para>fixme what else?</para>
/// </remarks>
@@ -100,7 +100,7 @@ namespace Umbraco.Tests.TestHelpers
var logger = f.GetInstance<ILogger>();
var umbracoDatabaseAccessor = f.GetInstance<IUmbracoDatabaseAccessor>();
var mappers = f.GetInstance<IMapperCollection>();
var factory = new DefaultDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, logger, umbracoDatabaseAccessor, mappers);
var factory = new UmbracoDatabaseFactory(GetDbConnectionString(), GetDbProviderName(), sqlSyntaxProviders, logger, umbracoDatabaseAccessor, mappers);
factory.ResetForTests();
return factory;
});

View File

@@ -69,6 +69,22 @@
};
}
if (!String.prototype.htmlEncode) {
/** htmlEncode extension method for string */
String.prototype.htmlEncode = function () {
//create a in-memory div, set it's inner text(which jQuery automatically encodes)
//then grab the encoded contents back out. The div never exists on the page.
return $('<div/>').text(this).html();
};
}
if (!String.prototype.htmlDecode) {
/** htmlDecode extension method for string */
String.prototype.htmlDecode = function () {
return $('<div/>').html(this).text();
};
}
if (!String.prototype.startsWith) {
/** startsWith extension method for string */
String.prototype.startsWith = function (str) {

View File

@@ -44,7 +44,7 @@
<div class="col">
<h2>Be a part of the community</h2>
<p>The Umbraco community is the best of its kind, be sure to visit, and if you have any questions, were sure that you can get your answers from the community.</p>
<p>The Umbraco community is the best of its kind, be sure to visit, and if you have any questions, we're sure that you can get your answers from the community.</p>
<a href="http://our.umbraco.org?ref=ourFromInstaller" target="_blank">our.Umbraco &rarr;</a>
</div>

View File

@@ -62,7 +62,7 @@ namespace Umbraco.Web
}
}
private void UmbracoModule_EndRequest(object sender, EventArgs e)
private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e)
{
// will clear the batch - will remain in HttpContext though - that's ok
FlushBatch();

View File

@@ -65,7 +65,7 @@ namespace Umbraco.Web
return batch;
}
private void UmbracoModule_EndRequest(object sender, EventArgs e)
private void UmbracoModule_EndRequest(object sender, UmbracoRequestEventArgs e)
{
FlushBatch();
}

View File

@@ -31,6 +31,7 @@ namespace Umbraco.Web.Editors
return Services.ContentTypeService.Count();
}
[UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)]
public MediaTypeDisplay GetById(int id)
{
var ct = Services.MediaTypeService.Get(id);

View File

@@ -0,0 +1,19 @@
using Umbraco.Core.Persistence;
namespace Umbraco.Web
{
internal class HybridDatabaseScopeAccessor : HybridAccessorBase<DatabaseScope>, IDatabaseScopeAccessor
{
protected override string ItemKey => "Umbraco.Core.Persistence.HybridDatabaseScopeAccessor";
public HybridDatabaseScopeAccessor(IHttpContextAccessor httpContextAccessor)
: base(httpContextAccessor)
{ }
public DatabaseScope Scope
{
get { return Value; }
set { Value = value; }
}
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices;
using Newtonsoft.Json.Linq;
using Umbraco.Core;
@@ -61,7 +62,14 @@ namespace Umbraco.Web.PropertyEditors
public override object ConvertEditorToDb(ContentPropertyData editorValue, object currentValue)
{
var json = editorValue.Value as JArray;
return json == null ? null : json.Select(x => x.Value<string>());
return json == null
? null
: json.Select(x => x.Value<string>()).Where(x => x.IsNullOrWhiteSpace() == false)
//First we will decode it as html because we know that if this is not a malicious post that the value is
// already Html encoded by the tags JavaScript controller. Then we'll re-Html Encode it to ensure that in case this
// is a malicious post (i.e. someone is submitting data manually by modifying the request).
.Select(WebUtility.HtmlDecode)
.Select(WebUtility.HtmlEncode);
}
/// <summary>

View File

@@ -207,7 +207,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
elt = null;
var min = int.MaxValue;
foreach (XmlElement e in xml.DocumentElement.ChildNodes)
foreach (var e in xml.DocumentElement.ChildNodes.OfType<XmlElement>())
{
var sortOrder = int.Parse(e.GetAttribute("sortOrder"));
if (sortOrder < min)
@@ -229,7 +229,7 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
if (hideTopLevelNode && startNodeId <= 0)
{
foreach (XmlElement e in elt.ChildNodes)
foreach (var e in elt.ChildNodes.OfType<XmlElement>())
{
var id = NavigateElementRoute(e, urlParts);
if (id > 0) return id;
@@ -240,14 +240,14 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
return NavigateElementRoute(elt, urlParts);
}
private int NavigateElementRoute(XmlElement elt, string[] urlParts)
private static int NavigateElementRoute(XmlElement elt, string[] urlParts)
{
var found = true;
var i = 0;
while (found && i < urlParts.Length)
{
found = false;
foreach (XmlElement child in elt.ChildNodes)
foreach (var child in elt.ChildNodes.OfType<XmlElement>())
{
var noNode = child.GetAttributeNode("isDoc") == null;
if (noNode) continue;

View File

@@ -16,14 +16,16 @@ namespace Umbraco.Web.Scheduling
private readonly IUmbracoSettingsSection _settings;
private readonly ILogger _logger;
private readonly ProfilingLogger _proflog;
private readonly DatabaseContext _databaseContext;
public LogScrubber(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
IRuntimeState runtime, IAuditService auditService, IUmbracoSettingsSection settings, ILogger logger, ProfilingLogger proflog)
IRuntimeState runtime, IAuditService auditService, IUmbracoSettingsSection settings, DatabaseContext databaseContext, ILogger logger, ProfilingLogger proflog)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_runtime = runtime;
_auditService = auditService;
_settings = settings;
_databaseContext = databaseContext;
_logger = logger;
_proflog = proflog;
}
@@ -79,6 +81,8 @@ namespace Umbraco.Web.Scheduling
return false; // do NOT repeat, going down
}
// running on a background task, requires a database scope
using (_databaseContext.CreateDatabaseScope())
using (_proflog.DebugDuration<LogScrubber>("Log scrubbing executing", "Log scrubbing complete"))
{
_auditService.CleanLogs(GetLogScrubbingMaximumAge(_settings));

View File

@@ -14,15 +14,17 @@ namespace Umbraco.Web.Scheduling
{
private readonly IRuntimeState _runtime;
private readonly IUserService _userService;
private readonly DatabaseContext _databaseContext;
private readonly ILogger _logger;
private readonly ProfilingLogger _proflog;
public ScheduledPublishing(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
IRuntimeState runtime, IUserService userService, ILogger logger, ProfilingLogger proflog)
IRuntimeState runtime, IUserService userService, DatabaseContext databaseContext, ILogger logger, ProfilingLogger proflog)
: base(runner, delayMilliseconds, periodMilliseconds)
{
_runtime = runtime;
_userService = userService;
_databaseContext = databaseContext;
_logger = logger;
_proflog = proflog;
}
@@ -80,8 +82,13 @@ namespace Umbraco.Web.Scheduling
Content = new StringContent(string.Empty)
};
//pass custom the authorization header
// running on a background task, requires a database scope
// (GetAuthenticationHeaderValue uses UserService to load the current user, hence requires a database)
using (_databaseContext.CreateDatabaseScope())
{
//pass custom the authorization header
request.Headers.Authorization = AdminTokenAuthorizeAttribute.GetAuthenticationHeaderValue(_userService);
}
var result = await wc.SendAsync(request, token);
}
@@ -97,7 +104,6 @@ namespace Umbraco.Web.Scheduling
public override bool IsAsync => true;
public override bool RunsOnShutdown => false;
}
}

View File

@@ -24,6 +24,7 @@ namespace Umbraco.Web.Scheduling
private IAuditService _auditService;
private ILogger _logger;
private ProfilingLogger _proflog;
private DatabaseContext _databaseContext;
private BackgroundTaskRunner<IBackgroundTask> _keepAliveRunner;
private BackgroundTaskRunner<IBackgroundTask> _publishingRunner;
@@ -34,11 +35,12 @@ namespace Umbraco.Web.Scheduling
private object _locker = new object();
private IBackgroundTask[] _tasks;
public void Initialize(IRuntimeState runtime, IUserService userService, IAuditService auditService, ILogger logger, ProfilingLogger proflog)
public void Initialize(IRuntimeState runtime, IUserService userService, IAuditService auditService, DatabaseContext databaseContext, ILogger logger, ProfilingLogger proflog)
{
_runtime = runtime;
_userService = userService;
_auditService = auditService;
_databaseContext = databaseContext;
_logger = logger;
_proflog = proflog;
@@ -76,9 +78,9 @@ namespace Umbraco.Web.Scheduling
var tasks = new List<IBackgroundTask>
{
new KeepAlive(_keepAliveRunner, 60000, 300000, _runtime, _logger, _proflog),
new ScheduledPublishing(_publishingRunner, 60000, 60000, _runtime, _userService, _logger, _proflog),
new ScheduledPublishing(_publishingRunner, 60000, 60000, _runtime, _userService, _databaseContext, _logger, _proflog),
new ScheduledTasks(_tasksRunner, 60000, 60000, _runtime, settings, _logger, _proflog),
new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings, _logger), _runtime, _auditService, settings, _logger, _proflog)
new LogScrubber(_scrubberRunner, 60000, LogScrubber.GetLogScrubbingInterval(settings, _logger), _runtime, _auditService, settings, _databaseContext, _logger, _proflog)
};
// ping/keepalive

View File

@@ -34,6 +34,7 @@ namespace Umbraco.Web.Search
logger.Info<ExamineComponent>("Starting initialize async background thread.");
// make it async in order not to slow down the boot
// fixme - should be a proper background task else we cannot stop it!
var bg = new Thread(() =>
{
try
@@ -83,7 +84,11 @@ namespace Umbraco.Web.Search
MediaCacheRefresher.CacheUpdated += MediaCacheRefresherUpdated;
MemberCacheRefresher.CacheUpdated += MemberCacheRefresherUpdated;
var contentIndexer = ExamineManager.Instance.IndexProviderCollection[Constants.Examine.InternalIndexer] as UmbracoContentIndexer;
// fixme - content type?
// events handling removed in ef013f9d3b945d0a48a306ff1afbd49c10c3fff8
// because, could not make sense of it?
var contentIndexer = ExamineManager.Instance.IndexProviderCollection[Constants.Examine.InternalIndexer] as UmbracoContentIndexer;
if (contentIndexer != null)
{
contentIndexer.DocumentWriting += IndexerDocumentWriting;
@@ -104,7 +109,6 @@ namespace Umbraco.Web.Search
indexer.Value.RebuildIndex();
}
private static void BindGridToExamine(GridPropertyEditor grid, IExamineIndexCollectionAccessor indexCollection)
{
var indexes = indexCollection.Indexes;

View File

@@ -398,7 +398,8 @@ namespace Umbraco.Web.Security
var viewProperties = new List<UmbracoProperty>();
foreach (var prop in memberType.PropertyTypes
.Where(x => builtIns.Contains(x.Alias) == false && memberType.MemberCanEditProperty(x.Alias)))
.Where(x => builtIns.Contains(x.Alias) == false && memberType.MemberCanEditProperty(x.Alias))
.OrderBy(p => p.SortOrder))
{
var value = string.Empty;
if (member != null)

View File

@@ -39,6 +39,7 @@ namespace Umbraco.Web.Strategies
private BackgroundTaskRunner<IBackgroundTask> _backgroundTaskRunner;
private bool _started;
private TouchServerTask _task;
private DatabaseContext _databaseContext;
public override void Compose(Composition composition)
{
@@ -94,7 +95,7 @@ namespace Umbraco.Web.Strategies
indexer.Value.RebuildIndex();
}
public void Initialize(IRuntimeState runtime, IServerRegistrar serverRegistrar, IServerRegistrationService registrationService, ILogger logger)
public void Initialize(IRuntimeState runtime, IServerRegistrar serverRegistrar, IServerRegistrationService registrationService, DatabaseContext databaseContext, ILogger logger)
{
if (UmbracoConfig.For.UmbracoSettings().DistributedCall.Enabled) return;
@@ -102,6 +103,7 @@ namespace Umbraco.Web.Strategies
if (_registrar == null) throw new Exception("panic: registar.");
_runtime = runtime;
_databaseContext = databaseContext;
_logger = logger;
_registrationService = registrationService;
@@ -149,7 +151,7 @@ namespace Umbraco.Web.Strategies
var task = new TouchServerTask(_backgroundTaskRunner,
15000, //delay before first execution
_registrar.Options.RecurringSeconds*1000, //amount of ms between executions
svc, _registrar, serverAddress, _logger);
svc, _registrar, serverAddress, _databaseContext, _logger);
// perform the rest async, we don't want to block the startup sequence
// this will just reoccur on a background thread
@@ -164,6 +166,7 @@ namespace Umbraco.Web.Strategies
private readonly IServerRegistrationService _svc;
private readonly DatabaseServerRegistrar _registrar;
private readonly string _serverAddress;
private readonly DatabaseContext _databaseContext;
private readonly ILogger _logger;
/// <summary>
@@ -175,16 +178,18 @@ namespace Umbraco.Web.Strategies
/// <param name="svc"></param>
/// <param name="registrar"></param>
/// <param name="serverAddress"></param>
/// <param name="databaseContext"></param>
/// <param name="logger"></param>
/// <remarks>The task will repeat itself periodically. Use this constructor to create a new task.</remarks>
public TouchServerTask(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
IServerRegistrationService svc, DatabaseServerRegistrar registrar, string serverAddress, ILogger logger)
IServerRegistrationService svc, DatabaseServerRegistrar registrar, string serverAddress, DatabaseContext databaseContext, ILogger logger)
: base(runner, delayMilliseconds, periodMilliseconds)
{
if (svc == null) throw new ArgumentNullException(nameof(svc));
_svc = svc;
_registrar = registrar;
_serverAddress = serverAddress;
_databaseContext = databaseContext;
_logger = logger;
}
@@ -200,7 +205,11 @@ namespace Umbraco.Web.Strategies
{
try
{
_svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout);
// running on a background task, requires a database scope
using (_databaseContext.CreateDatabaseScope())
{
_svc.TouchServer(_serverAddress, _svc.CurrentServerIdentity, _registrar.Options.StaleServerTimeout);
}
return true; // repeat
}
catch (Exception ex)

View File

@@ -167,6 +167,7 @@
<Compile Include="HealthCheck\HealthCheckCollection.cs" />
<Compile Include="HealthCheck\HeathCheckCollectionBuilder.cs" />
<Compile Include="HybridAccessorBase.cs" />
<Compile Include="HybridDatabaseScopeAccessor.cs" />
<Compile Include="HybridUmbracoContextAccessor.cs" />
<Compile Include="HttpContextUmbracoContextAccessor.cs" />
<Compile Include="HybridEventMessagesAccessor.cs" />

View File

@@ -550,7 +550,7 @@ namespace Umbraco.Web
OnEndRequest(new UmbracoRequestEventArgs(UmbracoContext.Current, new HttpContextWrapper(httpContext)));
DisposeHttpContextItems(httpContext);
DisposeHttpContextItems(httpContext);
};
}
@@ -568,9 +568,9 @@ namespace Umbraco.Web
RouteAttempt?.Invoke(this, args);
}
public static event EventHandler<EventArgs> EndRequest;
public static event EventHandler<UmbracoRequestEventArgs> EndRequest;
private void OnEndRequest(EventArgs args)
private void OnEndRequest(UmbracoRequestEventArgs args)
{
EndRequest?.Invoke(this, args);
}

View File

@@ -32,6 +32,12 @@ namespace UmbracoExamine
/// </summary>
public abstract class BaseUmbracoIndexer : LuceneIndexer
{
// note
// wrapping all operations that end up calling base.SafelyProcessQueueItems in a safe call
// context because they will fork a thread/task/whatever which should *not* capture our
// call context (and the database it can contain)! ideally we should be able to override
// SafelyProcessQueueItems but that's not possible in the current version of Examine.
/// <summary>
/// Used to store the path of a content object
/// </summary>
@@ -232,7 +238,10 @@ namespace UmbracoExamine
if (CanInitialize())
{
ProfilingLogger.Logger.Debug(GetType(), "Rebuilding index");
base.RebuildIndex();
using (new SafeCallContext())
{
base.RebuildIndex();
}
}
}
@@ -246,7 +255,10 @@ namespace UmbracoExamine
{
if (CanInitialize())
{
base.IndexAll(type);
using (new SafeCallContext())
{
base.IndexAll(type);
}
}
}
@@ -254,7 +266,10 @@ namespace UmbracoExamine
{
if (CanInitialize())
{
base.IndexItems(nodes);
using (new SafeCallContext())
{
base.IndexItems(nodes);
}
}
}
@@ -269,7 +284,10 @@ namespace UmbracoExamine
if (node.Attribute("id") != null)
{
ProfilingLogger.Logger.Debug(GetType(), "ReIndexNode {0} with type {1}", () => node.Attribute("id"), () => type);
base.ReIndexNode(node, type);
using (new SafeCallContext())
{
base.ReIndexNode(node, type);
}
}
else
{
@@ -289,7 +307,10 @@ namespace UmbracoExamine
{
if (CanInitialize())
{
base.DeleteFromIndex(nodeId);
using (new SafeCallContext())
{
base.DeleteFromIndex(nodeId);
}
}
}

View File

@@ -21,6 +21,8 @@
//using System.Data.SqlClient;
//using System.Diagnostics;
//// FIXME WOULD NEED TO WRAP THE WHOLE THING IN DATABASE SCOPE (see 7.6)
//namespace UmbracoExamine.DataServices
//{
// public class UmbracoContentService

View File

@@ -985,25 +985,24 @@ order by level,sortOrder";
protected virtual XmlNode GetPreviewXml(XmlDocument xd, Guid version)
{
var xmlDoc = new XmlDocument();
XmlDocument xmlDoc = new XmlDocument();
using (var sqlHelper = LegacySqlHelper.SqlHelper)
using (XmlReader xmlRdr = sqlHelper.ExecuteXmlReader(
"select xml from cmsPreviewXml where nodeID = @nodeId and versionId = @versionId",
sqlHelper.CreateParameter("@nodeId", Id),
sqlHelper.CreateParameter("@versionId", version)))
{
xmlDoc.Load(xmlRdr);
}
var xmlStr = Current.DatabaseContext.Database.ExecuteScalar<string>(
"select xml from cmsPreviewXml where nodeID = @nodeId and versionId = @versionId",
new { nodeId = Id, versionId = version });
if (xmlStr.IsNullOrWhiteSpace()) return null;
xmlDoc.LoadXml(xmlStr);
return xd.ImportNode(xmlDoc.FirstChild, true);
}
protected internal virtual bool PreviewExists(Guid versionId)
{
using (var sqlHelper = LegacySqlHelper.SqlHelper)
return sqlHelper.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId=@nodeId and versionId = @versionId",
sqlHelper.CreateParameter("@nodeId", Id), sqlHelper.CreateParameter("@versionId", versionId)) != 0;
return Current.DatabaseContext.Database.ExecuteScalar<int>(
"SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId=@nodeId and versionId = @versionId",
new {nodeId = Id, versionId = versionId}) != 0;
}
@@ -1018,12 +1017,9 @@ order by level,sortOrder";
var sql = PreviewExists(versionId) ? "UPDATE cmsPreviewXml SET xml = @xml, timestamp = @timestamp WHERE nodeId=@nodeId AND versionId = @versionId"
: "INSERT INTO cmsPreviewXml(nodeId, versionId, timestamp, xml) VALUES (@nodeId, @versionId, @timestamp, @xml)";
using (var sqlHelper = LegacySqlHelper.SqlHelper)
sqlHelper.ExecuteNonQuery(sql,
sqlHelper.CreateParameter("@nodeId", Id),
sqlHelper.CreateParameter("@versionId", versionId),
sqlHelper.CreateParameter("@timestamp", DateTime.Now),
sqlHelper.CreateParameter("@xml", x.OuterXml));
Current.DatabaseContext.Database.Execute(
sql, new {nodeId = Id, versionId = versionId, timestamp = DateTime.Now, xml = x.OuterXml});
}
protected void PopulateCMSNodeFromReader(IRecordsReader dr)