Merge dev-v7.6 into dev-v8
This commit is contained in:
@@ -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>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
172
src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs
Normal file
172
src/Umbraco.Core/Persistence/DatabaseDebugHelper.cs
Normal 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
|
||||
58
src/Umbraco.Core/Persistence/DatabaseScope.cs
Normal file
58
src/Umbraco.Core/Persistence/DatabaseScope.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/Umbraco.Core/Persistence/DbCommandExtensions.cs
Normal file
29
src/Umbraco.Core/Persistence/DbCommandExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
10
src/Umbraco.Core/Persistence/IDatabaseScopeAccessor.cs
Normal file
10
src/Umbraco.Core/Persistence/IDatabaseScopeAccessor.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides access to DatabaseScope.
|
||||
/// </summary>
|
||||
public interface IDatabaseScopeAccessor
|
||||
{
|
||||
DatabaseScope Scope { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>(),
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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, we’re 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 →</a>
|
||||
</div>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
19
src/Umbraco.Web/HybridDatabaseScopeAccessor.cs
Normal file
19
src/Umbraco.Web/HybridDatabaseScopeAccessor.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user