Merge dev-v7.6 into dev-v7-deploy
This commit is contained in:
@@ -25,9 +25,6 @@
|
||||
public const int DefaultContentListViewDataTypeId = -95;
|
||||
public const int DefaultMediaListViewDataTypeId = -96;
|
||||
public const int DefaultMembersListViewDataTypeId = -97;
|
||||
|
||||
// identifiers for lock objects
|
||||
public const int ServersLock = -331;
|
||||
}
|
||||
|
||||
public static class DatabaseProviders
|
||||
|
||||
@@ -326,7 +326,7 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <summary>
|
||||
/// A boolean flag indicating if a property type has been removed from this instance.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Models
|
||||
NoAccessNodeId = noAccessNode.Id;
|
||||
_protectedNodeId = protectedNode.Id;
|
||||
|
||||
_ruleCollection = new ObservableCollection<PublicAccessRule>(ruleCollection);
|
||||
_ruleCollection = new ObservableCollection<PublicAccessRule>(ruleCollection);
|
||||
_ruleCollection.CollectionChanged += _ruleCollection_CollectionChanged;
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace Umbraco.Core.Models
|
||||
internal IEnumerable<Guid> RemovedRules
|
||||
{
|
||||
get { return _removedRules; }
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<PublicAccessRule> Rules
|
||||
{
|
||||
@@ -123,7 +123,7 @@ namespace Umbraco.Core.Models
|
||||
get { return _noAccessNodeId; }
|
||||
set { SetPropertyValueAndDetectChanges(value, ref _noAccessNodeId, Ps.Value.NoAccessNodeIdSelector); }
|
||||
}
|
||||
|
||||
|
||||
[DataMember]
|
||||
public int ProtectedNodeId
|
||||
{
|
||||
|
||||
29
src/Umbraco.Core/Models/Rdbms/LockDto.cs
Normal file
29
src/Umbraco.Core/Models/Rdbms/LockDto.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseAnnotations;
|
||||
|
||||
namespace Umbraco.Core.Models.Rdbms
|
||||
{
|
||||
[TableName("umbracoLock")]
|
||||
[PrimaryKey("id")]
|
||||
[ExplicitColumns]
|
||||
internal class LockDto
|
||||
{
|
||||
public LockDto()
|
||||
{
|
||||
Value = 1;
|
||||
}
|
||||
|
||||
[Column("id")]
|
||||
[PrimaryKeyColumn(Name = "PK_umbracoLock")]
|
||||
public int Id { get; set; }
|
||||
|
||||
[Column("value")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
public int Value { get; set; }
|
||||
|
||||
[Column("name")]
|
||||
[NullSetting(NullSetting = NullSettings.NotNull)]
|
||||
[Length(64)]
|
||||
public string Name { get; set; }
|
||||
}
|
||||
}
|
||||
11
src/Umbraco.Core/Persistence/Constants-Locks.cs
Normal file
11
src/Umbraco.Core/Persistence/Constants-Locks.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
static partial class Constants
|
||||
{
|
||||
public static class Locks
|
||||
{
|
||||
public const int Servers = -331;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
ValidateDatabase(database);
|
||||
|
||||
database.Execute("UPDATE umbracoNode SET sortOrder = (CASE WHEN (sortOrder=1) THEN -1 ELSE 1 END) WHERE id=@id",
|
||||
database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
|
||||
new { @id = nodeId });
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
ValidateDatabase(database);
|
||||
|
||||
database.ExecuteScalar<int>("SELECT sortOrder FROM umbracoNode WHERE id=@id",
|
||||
database.ExecuteScalar<int>("SELECT value FROM umbracoLock WHERE id=@id",
|
||||
new { @id = nodeId });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence
|
||||
//[ThreadStatic]
|
||||
//private static volatile UmbracoDatabase _nonHttpInstance;
|
||||
|
||||
private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory";
|
||||
private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory";
|
||||
|
||||
private static UmbracoDatabase NonContextValue
|
||||
{
|
||||
@@ -75,16 +75,16 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
public UmbracoDatabase CreateDatabase()
|
||||
{
|
||||
// no http context, create the call context object
|
||||
// no http context, create the call context object
|
||||
// NOTHING is going to track the object and it is the responsibility of the caller to release it!
|
||||
// using the ReleaseDatabase method.
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("Get NON http [T" + Environment.CurrentManagedThreadId + "]");
|
||||
var value = NonContextValue;
|
||||
if (value != null) return value;
|
||||
lock (Locker)
|
||||
{
|
||||
var value = NonContextValue;
|
||||
if (value != null) return value;
|
||||
lock (Locker)
|
||||
{
|
||||
value = NonContextValue;
|
||||
if (value != null) return value;
|
||||
|
||||
@@ -93,9 +93,9 @@ namespace Umbraco.Core.Persistence
|
||||
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
|
||||
: new UmbracoDatabase(_connectionStringName, _logger);
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
// we have an http context, so only create one per request.
|
||||
// UmbracoDatabase is marked IDisposeOnRequestEnd and therefore will be disposed when
|
||||
@@ -103,38 +103,40 @@ namespace Umbraco.Core.Persistence
|
||||
// connections at the end of each request. no need to call ReleaseDatabase.
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("Get http [T" + Environment.CurrentManagedThreadId + "]");
|
||||
if (HttpContext.Current.Items.Contains(typeof(DefaultDatabaseFactory)) == false)
|
||||
{
|
||||
{
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("Create http [T" + Environment.CurrentManagedThreadId + "]");
|
||||
HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory),
|
||||
string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
|
||||
HttpContext.Current.Items.Add(typeof(DefaultDatabaseFactory),
|
||||
string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
|
||||
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
|
||||
: new UmbracoDatabase(_connectionStringName, _logger));
|
||||
}
|
||||
return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
}
|
||||
}
|
||||
return (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
}
|
||||
|
||||
// releases the "context" database
|
||||
public void ReleaseDatabase()
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
var value = NonContextValue;
|
||||
if (value != null) value.Dispose();
|
||||
NonContextValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var db = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
if (db != null) db.Dispose();
|
||||
public void ReleaseDatabase()
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
var value = NonContextValue;
|
||||
if (value != null) value.Dispose();
|
||||
NonContextValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
if (db != null) db.Dispose();
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
ReleaseDatabase();
|
||||
}
|
||||
|
||||
// during tests, the thread static var can leak between tests
|
||||
// this method provides a way to force-reset the variable
|
||||
internal void ResetForTests()
|
||||
{
|
||||
var value = NonContextValue;
|
||||
@@ -148,46 +150,46 @@ namespace Umbraco.Core.Persistence
|
||||
// the logical call context...
|
||||
|
||||
static DefaultDatabaseFactory()
|
||||
{
|
||||
SafeCallContext.Register(DetachDatabase, AttachDatabase);
|
||||
}
|
||||
{
|
||||
SafeCallContext.Register(DetachDatabase, AttachDatabase);
|
||||
}
|
||||
|
||||
// detaches the current database
|
||||
// ie returns the database and remove it from whatever is "context"
|
||||
private static UmbracoDatabase DetachDatabase()
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
var db = NonContextValue;
|
||||
NonContextValue = null;
|
||||
return db;
|
||||
}
|
||||
else
|
||||
{
|
||||
var db = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
private static UmbracoDatabase DetachDatabase()
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
var db = NonContextValue;
|
||||
NonContextValue = null;
|
||||
return db;
|
||||
}
|
||||
else
|
||||
{
|
||||
var db = (UmbracoDatabase)HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
return db;
|
||||
}
|
||||
return db;
|
||||
}
|
||||
}
|
||||
|
||||
// attach a current database
|
||||
// ie assign it to whatever is "context"
|
||||
// throws if there already is a database
|
||||
private static void AttachDatabase(object o)
|
||||
{
|
||||
var database = o as UmbracoDatabase;
|
||||
private static void AttachDatabase(object o)
|
||||
{
|
||||
var database = o as UmbracoDatabase;
|
||||
if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o");
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
if (NonContextValue != null) throw new InvalidOperationException();
|
||||
if (database != null) NonContextValue = database;
|
||||
if (database != null) NonContextValue = database;
|
||||
}
|
||||
else
|
||||
{
|
||||
else
|
||||
{
|
||||
if (HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] != null) throw new InvalidOperationException();
|
||||
if (database != null) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)] = database;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -8,7 +8,17 @@ namespace Umbraco.Core.Persistence.Mappers
|
||||
{
|
||||
public abstract class BaseMapper
|
||||
{
|
||||
|
||||
private readonly ISqlSyntaxProvider _sqlSyntax;
|
||||
|
||||
protected BaseMapper() : this(SqlSyntaxContext.SqlSyntaxProvider)
|
||||
{
|
||||
}
|
||||
|
||||
protected BaseMapper(ISqlSyntaxProvider sqlSyntax)
|
||||
{
|
||||
_sqlSyntax = sqlSyntax;
|
||||
}
|
||||
|
||||
internal abstract ConcurrentDictionary<string, DtoMapModel> PropertyInfoCache { get; }
|
||||
|
||||
internal abstract void BuildMap();
|
||||
@@ -58,8 +68,8 @@ namespace Umbraco.Core.Persistence.Mappers
|
||||
string columnName = columnAttribute.Name;
|
||||
|
||||
string columnMap = string.Format("{0}.{1}",
|
||||
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(tableName),
|
||||
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(columnName));
|
||||
_sqlSyntax.GetQuotedTableName(tableName),
|
||||
_sqlSyntax.GetQuotedColumnName(columnName));
|
||||
return columnMap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Concurrent;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Mappers
|
||||
{
|
||||
@@ -16,6 +17,11 @@ namespace Umbraco.Core.Persistence.Mappers
|
||||
{
|
||||
private static readonly ConcurrentDictionary<string, DtoMapModel> PropertyInfoCacheInstance = new ConcurrentDictionary<string, DtoMapModel>();
|
||||
|
||||
public ContentMapper(ISqlSyntaxProvider sqlSyntax) : base(sqlSyntax)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
//NOTE: its an internal class but the ctor must be public since we're using Activator.CreateInstance to create it
|
||||
// otherwise that would fail because there is no public constructor.
|
||||
public ContentMapper()
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace Umbraco.Core.Persistence.Mappers
|
||||
{
|
||||
return byAttribute.Result;
|
||||
}
|
||||
throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type");
|
||||
throw new Exception("Invalid Type: A Mapper could not be resolved based on the passed in Type " + type);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +33,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
CreateUmbracNodeData();
|
||||
}
|
||||
|
||||
if(tableName.Equals("cmsContentType"))
|
||||
if (tableName.Equals("umbracoLock"))
|
||||
{
|
||||
CreateUmbracoLockData();
|
||||
}
|
||||
|
||||
if (tableName.Equals("cmsContentType"))
|
||||
{
|
||||
CreateCmsContentTypeData();
|
||||
}
|
||||
@@ -141,9 +146,12 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
//_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1038, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1038", SortOrder = 2, UniqueId = new Guid("1251c96c-185c-4e9b-93f4-b48205573cbd"), Text = "Simple Editor", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
|
||||
|
||||
//_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = 1042, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1,1042", SortOrder = 2, UniqueId = new Guid("0a452bd5-83f9-4bc3-8403-1286e13fb77e"), Text = "Macro Container", NodeObjectType = new Guid(Constants.ObjectTypes.DataType), CreateDate = DateTime.Now });
|
||||
}
|
||||
|
||||
private void CreateUmbracoLockData()
|
||||
{
|
||||
// all lock objects
|
||||
_database.Insert("umbracoNode", "id", false, new NodeDto { NodeId = Constants.System.ServersLock, Trashed = false, ParentId = -1, UserId = 0, Level = 1, Path = "-1," + Constants.System.ServersLock, SortOrder = 1, UniqueId = new Guid("0AF5E610-A310-4B6F-925F-E928D5416AF7"), Text = "LOCK: Servers", NodeObjectType = Constants.ObjectTypes.LockObjectGuid, CreateDate = DateTime.Now });
|
||||
_database.Insert("umbracoLock", "id", false, new LockDto { Id = Constants.Locks.Servers, Name = "Servers" });
|
||||
}
|
||||
|
||||
private void CreateCmsContentTypeData()
|
||||
|
||||
@@ -84,7 +84,8 @@ namespace Umbraco.Core.Persistence.Migrations.Initial
|
||||
{45, typeof (MigrationDto)},
|
||||
{46, typeof (UmbracoDeployChecksumDto)},
|
||||
{47, typeof (UmbracoDeployDependencyDto)},
|
||||
{48, typeof (RedirectUrlDto) }
|
||||
{48, typeof (RedirectUrlDto) },
|
||||
{49, typeof (LockDto) }
|
||||
};
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive
|
||||
{
|
||||
[Migration("7.5.5", 101, GlobalSettings.UmbracoMigrationName)]
|
||||
public class AddLockObjects : MigrationBase
|
||||
{
|
||||
public AddLockObjects(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
EnsureLockObject(Constants.Locks.Servers, "Servers");
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
|
||||
private void EnsureLockObject(int id, string name)
|
||||
{
|
||||
Execute.Code(db =>
|
||||
{
|
||||
var exists = db.Exists<LockDto>(id);
|
||||
if (exists) return string.Empty;
|
||||
// be safe: delete old umbracoNode lock objects if any
|
||||
db.Execute("DELETE FROM umbracoNode WHERE id=@id;", new { id });
|
||||
// then create umbracoLock object
|
||||
db.Execute("INSERT umbracoLock (id, name, value) VALUES (@id, @name, 1);", new { id, name });
|
||||
return string.Empty;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveFive
|
||||
{
|
||||
[Migration("7.5.5", 100, GlobalSettings.UmbracoMigrationName)]
|
||||
public class AddLockTable : MigrationBase
|
||||
{
|
||||
public AddLockTable(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var tables = SqlSyntax.GetTablesInSchema(Context.Database).ToArray();
|
||||
if (tables.InvariantContains("umbracoLock") == false)
|
||||
{
|
||||
Create.Table("umbracoLock")
|
||||
.WithColumn("id").AsInt32().PrimaryKey("PK_umbracoLock")
|
||||
.WithColumn("value").AsInt32().NotNullable()
|
||||
.WithColumn("name").AsString(64).NotNullable();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
// not implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenFiveZer
|
||||
// for some reason it was not, so it was created during migrations but not during
|
||||
// new installs, so for ppl that upgrade, make sure they have it
|
||||
|
||||
EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
|
||||
EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenThreeZe
|
||||
Create.Column("isMaster").OnTable("umbracoServer").AsBoolean().NotNullable().WithDefaultValue(0);
|
||||
}
|
||||
|
||||
EnsureLockObject(Constants.System.ServersLock, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
|
||||
EnsureLockObject(Constants.Locks.Servers, "0AF5E610-A310-4B6F-925F-E928D5416AF7", "LOCK: Servers");
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
public static Sql Where<T>(this Sql sql, Expression<Func<T, bool>> predicate)
|
||||
{
|
||||
var expresionist = new PocoToSqlExpressionHelper<T>();
|
||||
var expresionist = new PocoToSqlExpressionVisitor<T>();
|
||||
var whereExpression = expresionist.Visit(predicate);
|
||||
return sql.Where(whereExpression, expresionist.GetSqlParameters());
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,31 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
/// <summary>
|
||||
/// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do.
|
||||
/// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests.
|
||||
/// We have to wait till v8 to make this change I suppose.
|
||||
/// </summary>
|
||||
internal static class QueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all translated where clauses and their sql parameters
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Tuple<string, object[]>> GetWhereClauses<T>(this IQuery<T> query)
|
||||
{
|
||||
var q = query as Query<T>;
|
||||
if (q == null)
|
||||
{
|
||||
throw new NotSupportedException(typeof(IQuery<T>) + " cannot be cast to " + typeof(Query<T>));
|
||||
}
|
||||
return q.GetWhereClauses();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a query for building Linq translatable SQL queries
|
||||
/// </summary>
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
internal class ModelToSqlExpressionHelper<T> : BaseExpressionHelper<T>
|
||||
{
|
||||
|
||||
private readonly BaseMapper _mapper;
|
||||
|
||||
public ModelToSqlExpressionHelper()
|
||||
{
|
||||
_mapper = MappingResolver.Current.ResolveMapperByType(typeof(T));
|
||||
}
|
||||
|
||||
protected override string VisitMemberAccess(MemberExpression m)
|
||||
{
|
||||
if (m.Expression != null &&
|
||||
m.Expression.NodeType == ExpressionType.Parameter
|
||||
&& m.Expression.Type == typeof(T))
|
||||
{
|
||||
var field = _mapper.Map(m.Member.Name, true);
|
||||
if (field.IsNullOrWhiteSpace())
|
||||
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
|
||||
return field;
|
||||
}
|
||||
|
||||
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
|
||||
{
|
||||
var field = _mapper.Map(m.Member.Name, true);
|
||||
if (field.IsNullOrWhiteSpace())
|
||||
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
|
||||
return field;
|
||||
}
|
||||
|
||||
var member = Expression.Convert(m, typeof(object));
|
||||
var lambda = Expression.Lambda<Func<object>>(member);
|
||||
var getter = lambda.Compile();
|
||||
object o = getter();
|
||||
|
||||
SqlParameters.Add(o);
|
||||
return string.Format("@{0}", SqlParameters.Count - 1);
|
||||
|
||||
//return GetQuotedValue(o, o != null ? o.GetType() : null);
|
||||
|
||||
}
|
||||
|
||||
//protected bool IsFieldName(string quotedExp)
|
||||
//{
|
||||
// //Not entirely sure this is reliable, but its better then simply returning true
|
||||
// return quotedExp.LastIndexOf("'", StringComparison.InvariantCultureIgnoreCase) + 1 != quotedExp.Length;
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
/// <summary>
|
||||
/// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression,
|
||||
/// based on Umbraco's business logic models.
|
||||
/// </summary>
|
||||
/// <remarks>This object is stateful and cannot be re-used to parse an expression.</remarks>
|
||||
internal class ModelToSqlExpressionVisitor<T> : ExpressionVisitorBase
|
||||
{
|
||||
private readonly BaseMapper _mapper;
|
||||
|
||||
public ModelToSqlExpressionVisitor(ISqlSyntaxProvider sqlSyntax, BaseMapper mapper)
|
||||
: base(sqlSyntax)
|
||||
{
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public ModelToSqlExpressionVisitor()
|
||||
: this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T)))
|
||||
{ }
|
||||
|
||||
protected override string VisitMemberAccess(MemberExpression m)
|
||||
{
|
||||
if (m.Expression != null &&
|
||||
m.Expression.NodeType == ExpressionType.Parameter
|
||||
&& m.Expression.Type == typeof(T))
|
||||
{
|
||||
//don't execute if compiled
|
||||
if (Visited == false)
|
||||
{
|
||||
var field = _mapper.Map(m.Member.Name, true);
|
||||
if (field.IsNullOrWhiteSpace())
|
||||
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
|
||||
return field;
|
||||
}
|
||||
//already compiled, return
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
|
||||
{
|
||||
//don't execute if compiled
|
||||
if (Visited == false)
|
||||
{
|
||||
var field = _mapper.Map(m.Member.Name, true);
|
||||
if (field.IsNullOrWhiteSpace())
|
||||
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
|
||||
return field;
|
||||
}
|
||||
//already compiled, return
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var member = Expression.Convert(m, typeof(object));
|
||||
var lambda = Expression.Lambda<Func<object>>(member);
|
||||
var getter = lambda.Compile();
|
||||
var o = getter();
|
||||
|
||||
SqlParameters.Add(o);
|
||||
|
||||
//don't execute if compiled
|
||||
if (Visited == false)
|
||||
return string.Format("@{0}", SqlParameters.Count - 1);
|
||||
//already compiled, return
|
||||
return string.Empty;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
internal class PocoToSqlExpressionHelper<T> : BaseExpressionHelper<T>
|
||||
{
|
||||
private readonly Database.PocoData _pd;
|
||||
|
||||
public PocoToSqlExpressionHelper()
|
||||
{
|
||||
_pd = new Database.PocoData(typeof(T));
|
||||
}
|
||||
|
||||
protected override string VisitMemberAccess(MemberExpression m)
|
||||
{
|
||||
if (m.Expression != null &&
|
||||
m.Expression.NodeType == ExpressionType.Parameter
|
||||
&& m.Expression.Type == typeof(T))
|
||||
{
|
||||
string field = GetFieldName(_pd, m.Member.Name);
|
||||
return field;
|
||||
}
|
||||
|
||||
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
|
||||
{
|
||||
string field = GetFieldName(_pd, m.Member.Name);
|
||||
return field;
|
||||
}
|
||||
|
||||
var member = Expression.Convert(m, typeof(object));
|
||||
var lambda = Expression.Lambda<Func<object>>(member);
|
||||
var getter = lambda.Compile();
|
||||
object o = getter();
|
||||
|
||||
SqlParameters.Add(o);
|
||||
return string.Format("@{0}", SqlParameters.Count - 1);
|
||||
|
||||
//return GetQuotedValue(o, o != null ? o.GetType() : null);
|
||||
|
||||
}
|
||||
|
||||
protected virtual string GetFieldName(Database.PocoData pocoData, string name)
|
||||
{
|
||||
var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name);
|
||||
return string.Format("{0}.{1}",
|
||||
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(pocoData.TableInfo.TableName),
|
||||
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(column.Value.ColumnName));
|
||||
}
|
||||
|
||||
//protected bool IsFieldName(string quotedExp)
|
||||
//{
|
||||
// return true;
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
/// <summary>
|
||||
/// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression,
|
||||
/// based on Umbraco's DTOs.
|
||||
/// </summary>
|
||||
/// <remarks>This object is stateful and cannot be re-used to parse an expression.</remarks>
|
||||
internal class PocoToSqlExpressionVisitor<T> : ExpressionVisitorBase
|
||||
{
|
||||
private readonly Database.PocoData _pd;
|
||||
|
||||
public PocoToSqlExpressionVisitor()
|
||||
: base(SqlSyntaxContext.SqlSyntaxProvider)
|
||||
{
|
||||
_pd = new Database.PocoData(typeof(T));
|
||||
}
|
||||
|
||||
protected override string VisitMemberAccess(MemberExpression m)
|
||||
{
|
||||
if (m.Expression != null &&
|
||||
m.Expression.NodeType == ExpressionType.Parameter
|
||||
&& m.Expression.Type == typeof(T))
|
||||
{
|
||||
//don't execute if compiled
|
||||
if (Visited == false)
|
||||
{
|
||||
string field = GetFieldName(_pd, m.Member.Name);
|
||||
return field;
|
||||
}
|
||||
//already compiled, return
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Convert)
|
||||
{
|
||||
//don't execute if compiled
|
||||
if (Visited == false)
|
||||
{
|
||||
string field = GetFieldName(_pd, m.Member.Name);
|
||||
return field;
|
||||
}
|
||||
//already compiled, return
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var member = Expression.Convert(m, typeof(object));
|
||||
var lambda = Expression.Lambda<Func<object>>(member);
|
||||
var getter = lambda.Compile();
|
||||
var o = getter();
|
||||
|
||||
SqlParameters.Add(o);
|
||||
|
||||
//don't execute if compiled
|
||||
if (Visited == false)
|
||||
return string.Format("@{0}", SqlParameters.Count - 1);
|
||||
//already compiled, return
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
protected virtual string GetFieldName(Database.PocoData pocoData, string name)
|
||||
{
|
||||
var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name);
|
||||
return string.Format("{0}.{1}",
|
||||
SqlSyntax.GetQuotedTableName(pocoData.TableInfo.TableName),
|
||||
SqlSyntax.GetQuotedColumnName(column.Value.ColumnName));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,8 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
if (predicate != null)
|
||||
{
|
||||
var expressionHelper = new ModelToSqlExpressionHelper<T>();
|
||||
//TODO: This should have an SqlSyntax object passed in, this ctor is relying on a singleton
|
||||
var expressionHelper = new ModelToSqlExpressionVisitor<T>();
|
||||
string whereExpression = expressionHelper.Visit(predicate);
|
||||
|
||||
_wheres.Add(new Tuple<string, object[]>(whereExpression, expressionHelper.GetSqlParameters()));
|
||||
|
||||
27
src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs
Normal file
27
src/Umbraco.Core/Persistence/Querying/QueryExtensions.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
/// <summary>
|
||||
/// SD: This is a horrible hack but unless we break compatibility with anyone who's actually implemented IQuery{T} there's not much we can do.
|
||||
/// The IQuery{T} interface is useless without having a GetWhereClauses method and cannot be used for tests.
|
||||
/// We have to wait till v8 to make this change I suppose.
|
||||
/// </summary>
|
||||
internal static class QueryExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all translated where clauses and their sql parameters
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<Tuple<string, object[]>> GetWhereClauses<T>(this IQuery<T> query)
|
||||
{
|
||||
var q = query as Query<T>;
|
||||
if (q == null)
|
||||
{
|
||||
throw new NotSupportedException(typeof(IQuery<T>) + " cannot be cast to " + typeof(Query<T>));
|
||||
}
|
||||
return q.GetWhereClauses();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -104,6 +104,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
#endregion
|
||||
|
||||
#region Static Queries
|
||||
|
||||
private readonly IQuery<IContent> _publishedQuery = Query<IContent>.Builder.Where(x => x.Published == true);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of PetaPocoRepositoryBase<IContent>
|
||||
|
||||
|
||||
@@ -205,7 +211,6 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
try
|
||||
{
|
||||
// InsertOrUpdate tries to update first, which is good since it is what
|
||||
// should happen in most cases, then it tries to insert, and it should work
|
||||
// unless the node has been deleted, and we just report the exception
|
||||
Database.InsertOrUpdate(xmlItem);
|
||||
|
||||
@@ -83,6 +83,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
}
|
||||
|
||||
|
||||
#region Static Queries
|
||||
|
||||
private IQuery<TEntity> _hasIdQuery;
|
||||
|
||||
#endregion
|
||||
|
||||
protected virtual TId GetEntityId(TEntity entity)
|
||||
{
|
||||
return (TId)(object)entity.Id;
|
||||
@@ -111,9 +117,14 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
RuntimeCache,
|
||||
new RepositoryCachePolicyOptions(() =>
|
||||
{
|
||||
//create it once if it is needed (no need for locking here)
|
||||
if (_hasIdQuery == null)
|
||||
{
|
||||
_hasIdQuery = Query<TEntity>.Builder.Where(x => x.Id != 0);
|
||||
}
|
||||
|
||||
//Get count of all entities of current type (TEntity) to ensure cached result is correct
|
||||
var query = Query<TEntity>.Builder.Where(x => x.Id != 0);
|
||||
return PerformCount(query);
|
||||
return PerformCount(_hasIdQuery);
|
||||
})));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,12 @@ namespace Umbraco.Core.Services
|
||||
_userService = userService;
|
||||
}
|
||||
|
||||
#region Static Queries
|
||||
|
||||
private readonly IQuery<IContent> _notTrashedQuery = Query<IContent>.Builder.Where(x => x.Trashed == false);
|
||||
|
||||
#endregion
|
||||
|
||||
public int CountPublished(string contentTypeAlias = null)
|
||||
{
|
||||
var uow = UowProvider.GetUnitOfWork();
|
||||
@@ -789,8 +795,7 @@ namespace Umbraco.Core.Services
|
||||
{
|
||||
using (var repository = RepositoryFactory.CreateContentRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
var query = Query<IContent>.Builder.Where(x => x.Trashed == false);
|
||||
return repository.GetByPublishedVersion(query);
|
||||
return repository.GetByPublishedVersion(_notTrashedQuery);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,6 +62,12 @@ namespace Umbraco.Core.Services
|
||||
|
||||
}
|
||||
|
||||
#region Static Queries
|
||||
|
||||
private readonly IQuery<IUmbracoEntity> _rootEntityQuery = Query<IUmbracoEntity>.Builder.Where(x => x.ParentId == -1);
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Returns the integer id for a given GUID
|
||||
/// </summary>
|
||||
@@ -389,8 +395,7 @@ namespace Umbraco.Core.Services
|
||||
var objectTypeId = umbracoObjectType.GetGuid();
|
||||
using (var repository = RepositoryFactory.CreateEntityRepository(UowProvider.GetUnitOfWork()))
|
||||
{
|
||||
var query = Query<IUmbracoEntity>.Builder.Where(x => x.ParentId == -1);
|
||||
var entities = repository.GetByQuery(query, objectTypeId);
|
||||
var entities = repository.GetByQuery(_rootEntityQuery, objectTypeId);
|
||||
|
||||
return entities;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Services
|
||||
private readonly static string CurrentServerIdentityValue = NetworkHelper.MachineName // eg DOMAIN\SERVER
|
||||
+ "/" + HttpRuntime.AppDomainAppId; // eg /LM/S3SVC/11/ROOT
|
||||
|
||||
private static readonly int[] LockingRepositoryIds = { Constants.System.ServersLock };
|
||||
private static readonly int[] LockingRepositoryIds = { Constants.Locks.Servers };
|
||||
private ServerRole _currentServerRole = ServerRole.Unknown;
|
||||
private readonly LockingRepository<IServerRegistrationRepository> _lrepo;
|
||||
|
||||
|
||||
@@ -184,7 +184,7 @@ namespace Umbraco.Core
|
||||
/// <param name="input"></param>
|
||||
/// <param name="ignoreFromClean"></param>
|
||||
/// <returns></returns>
|
||||
internal static string CleanForXss(this string input, params char[] ignoreFromClean)
|
||||
public static string CleanForXss(this string input, params char[] ignoreFromClean)
|
||||
{
|
||||
//remove any html
|
||||
input = input.StripHtml();
|
||||
|
||||
@@ -414,6 +414,7 @@
|
||||
<Compile Include="Media\Exif\ExifTag.cs" />
|
||||
<Compile Include="Media\Exif\ExifTagFactory.cs" />
|
||||
<Compile Include="Models\Rdbms\AccessRuleDto.cs" />
|
||||
<Compile Include="Models\Rdbms\LockDto.cs" />
|
||||
<Compile Include="Models\Rdbms\RedirectUrlDto.cs" />
|
||||
<Compile Include="Models\Rdbms\DocumentPublishedReadOnlyDto.cs" />
|
||||
<Compile Include="Models\Rdbms\ExternalLoginDto.cs" />
|
||||
@@ -427,6 +428,7 @@
|
||||
<Compile Include="Models\IDomain.cs" />
|
||||
<Compile Include="Persistence\BulkDataReader.cs" />
|
||||
<Compile Include="SafeCallContext.cs" />
|
||||
<Compile Include="Persistence\Constants-Locks.cs" />
|
||||
<Compile Include="Persistence\DatabaseNodeLockExtensions.cs" />
|
||||
<Compile Include="Persistence\Factories\ExternalLoginFactory.cs" />
|
||||
<Compile Include="Persistence\Factories\MigrationEntryFactory.cs" />
|
||||
@@ -440,6 +442,8 @@
|
||||
<Compile Include="Persistence\Mappers\MigrationEntryMapper.cs" />
|
||||
<Compile Include="Persistence\Migrations\LocalMigrationContext.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionFourOneZero\AddPreviewXmlTable.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveFive\AddLockObjects.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveFive\AddLockTable.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveFive\UpdateAllowedMediaTypesAtRoot.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveZero\EnsureServersLockObject.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenFiveZero\AddRedirectUrlTable.cs" />
|
||||
@@ -486,6 +490,7 @@
|
||||
<Compile Include="Media\Exif\Utility.cs" />
|
||||
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeOne\UpdateUserLanguagesToIsoCode.cs" />
|
||||
<Compile Include="Persistence\PocoDataDataReader.cs" />
|
||||
<Compile Include="Persistence\Querying\QueryExtensions.cs" />
|
||||
<Compile Include="Persistence\RecordPersistenceType.cs" />
|
||||
<Compile Include="Persistence\Relators\AccessRulesRelator.cs" />
|
||||
<Compile Include="Persistence\Repositories\AuditRepository.cs" />
|
||||
@@ -518,6 +523,7 @@
|
||||
<Compile Include="PropertyEditors\ValueConverters\ImageCropperValueConverter.cs" />
|
||||
<Compile Include="Publishing\UnPublishedStatusType.cs" />
|
||||
<Compile Include="Publishing\UnPublishStatus.cs" />
|
||||
<Compile Include="SafeCallContext.cs" />
|
||||
<Compile Include="Security\ActiveDirectoryBackOfficeUserPasswordChecker.cs" />
|
||||
<Compile Include="Security\BackOfficeClaimsIdentityFactory.cs" />
|
||||
<Compile Include="Security\BackOfficeCookieAuthenticationProvider.cs" />
|
||||
@@ -1026,10 +1032,10 @@
|
||||
<Compile Include="Persistence\PetaPocoConnectionExtensions.cs" />
|
||||
<Compile Include="Persistence\PetaPocoExtensions.cs" />
|
||||
<Compile Include="Persistence\PetaPocoSqlExtensions.cs" />
|
||||
<Compile Include="Persistence\Querying\BaseExpressionHelper.cs" />
|
||||
<Compile Include="Persistence\Querying\PocoToSqlExpressionHelper.cs" />
|
||||
<Compile Include="Persistence\Querying\ExpressionVisitorBase.cs" />
|
||||
<Compile Include="Persistence\Querying\PocoToSqlExpressionVisitor.cs" />
|
||||
<Compile Include="Persistence\Querying\IQuery.cs" />
|
||||
<Compile Include="Persistence\Querying\ModelToSqlExpressionHelper.cs" />
|
||||
<Compile Include="Persistence\Querying\ModelToSqlExpressionVisitor.cs" />
|
||||
<Compile Include="Persistence\Querying\Query.cs" />
|
||||
<Compile Include="Persistence\Querying\SqlTranslator.cs" />
|
||||
<Compile Include="Persistence\Relators\DictionaryLanguageTextRelator.cs" />
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Xml;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using BenchmarkDotNet.Columns;
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Diagnosers;
|
||||
using BenchmarkDotNet.Diagnostics.Windows;
|
||||
using BenchmarkDotNet.Jobs;
|
||||
using BenchmarkDotNet.Loggers;
|
||||
using BenchmarkDotNet.Reports;
|
||||
using BenchmarkDotNet.Running;
|
||||
using BenchmarkDotNet.Validators;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.Migrations.Initial;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using ILogger = Umbraco.Core.Logging.ILogger;
|
||||
|
||||
namespace Umbraco.Tests.Benchmarks
|
||||
{
|
||||
[Config(typeof(Config))]
|
||||
public class ModelToSqlExpressionHelperBenchmarks
|
||||
{
|
||||
private class Config : ManualConfig
|
||||
{
|
||||
public Config()
|
||||
{
|
||||
Add(new MemoryDiagnoser());
|
||||
}
|
||||
}
|
||||
|
||||
public ModelToSqlExpressionHelperBenchmarks()
|
||||
{
|
||||
_contentMapper = new ContentMapper(_syntaxProvider);
|
||||
_contentMapper.BuildMap();
|
||||
_cachedExpression = new CachedExpression();
|
||||
}
|
||||
|
||||
private readonly ISqlSyntaxProvider _syntaxProvider = new SqlCeSyntaxProvider();
|
||||
private readonly BaseMapper _contentMapper;
|
||||
private readonly CachedExpression _cachedExpression;
|
||||
|
||||
[Benchmark(Baseline = true)]
|
||||
public void WithNonCached()
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
var a = i;
|
||||
var b = i*10;
|
||||
Expression<Func<IContent, bool>> predicate = content =>
|
||||
content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b);
|
||||
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>(_syntaxProvider, _contentMapper);
|
||||
var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Benchmark()]
|
||||
public void WithCachedExpression()
|
||||
{
|
||||
for (int i = 0; i < 100; i++)
|
||||
{
|
||||
var a = i;
|
||||
var b = i * 10;
|
||||
Expression<Func<IContent, bool>> predicate = content =>
|
||||
content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b);
|
||||
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>(_syntaxProvider, _contentMapper);
|
||||
|
||||
//wrap it!
|
||||
_cachedExpression.Wrap(predicate);
|
||||
|
||||
var result = modelToSqlExpressionHelper.Visit(_cachedExpression);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -8,12 +8,7 @@ namespace Umbraco.Tests.Benchmarks
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
if (args.Length == 0)
|
||||
{
|
||||
var summary = BenchmarkRunner.Run<BulkInsertBenchmarks>();
|
||||
Console.ReadLine();
|
||||
}
|
||||
else if (args.Length == 1)
|
||||
if (args.Length == 1)
|
||||
{
|
||||
var type = Assembly.GetExecutingAssembly().GetType("Umbraco.Tests.Benchmarks." +args[0]);
|
||||
if (type == null)
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="ModelToSqlExpressionHelperBenchmarks.cs" />
|
||||
<Compile Include="BulkInsertBenchmarks.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
DatabaseContext = _dbContext,
|
||||
IsReady = true
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
@@ -102,7 +102,7 @@ namespace Umbraco.Tests.Persistence
|
||||
|
||||
var appCtx = new ApplicationContext(
|
||||
_dbContext,
|
||||
new ServiceContext(migrationEntryService: Mock.Of<IMigrationEntryService>()),
|
||||
new ServiceContext(migrationEntryService: Mock.Of<IMigrationEntryService>()),
|
||||
CacheHelper.CreateDisabledCacheHelper(),
|
||||
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
|
||||
|
||||
|
||||
304
src/Umbraco.Tests/Persistence/LocksTests.cs
Normal file
304
src/Umbraco.Tests/Persistence/LocksTests.cs
Normal file
@@ -0,0 +1,304 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlServerCe;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models.Rdbms;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Core.Publishing;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Ignore = NUnit.Framework.IgnoreAttribute;
|
||||
|
||||
namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
|
||||
[TestFixture]
|
||||
[Ignore("Takes too much time.")]
|
||||
public class LocksTests : BaseDatabaseFactoryTest
|
||||
{
|
||||
private ThreadSafetyServiceTest.PerThreadPetaPocoUnitOfWorkProvider _uowProvider;
|
||||
private ThreadSafetyServiceTest.PerThreadDatabaseFactory _dbFactory;
|
||||
|
||||
[SetUp]
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
//we need to use our own custom IDatabaseFactory for the DatabaseContext because we MUST ensure that
|
||||
//a Database instance is created per thread, whereas the default implementation which will work in an HttpContext
|
||||
//threading environment, or a single apartment threading environment will not work for this test because
|
||||
//it is multi-threaded.
|
||||
_dbFactory = new ThreadSafetyServiceTest.PerThreadDatabaseFactory(Logger);
|
||||
//overwrite the local object
|
||||
ApplicationContext.DatabaseContext = new DatabaseContext(_dbFactory, Logger, new SqlCeSyntaxProvider(), Constants.DatabaseProviders.SqlCe);
|
||||
|
||||
//disable cache
|
||||
var cacheHelper = CacheHelper.CreateDisabledCacheHelper();
|
||||
|
||||
//here we are going to override the ServiceContext because normally with our test cases we use a
|
||||
//global Database object but this is NOT how it should work in the web world or in any multi threaded scenario.
|
||||
//we need a new Database object for each thread.
|
||||
var repositoryFactory = new RepositoryFactory(cacheHelper, Logger, SqlSyntax, SettingsForTests.GenerateMockSettings());
|
||||
_uowProvider = new ThreadSafetyServiceTest.PerThreadPetaPocoUnitOfWorkProvider(_dbFactory);
|
||||
var evtMsgs = new TransientMessagesFactory();
|
||||
ApplicationContext.Services = new ServiceContext(
|
||||
repositoryFactory,
|
||||
_uowProvider,
|
||||
new FileUnitOfWorkProvider(),
|
||||
new PublishingStrategy(evtMsgs, Logger),
|
||||
cacheHelper,
|
||||
Logger,
|
||||
evtMsgs);
|
||||
|
||||
// create a few lock objects
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
{
|
||||
database.Execute("SET IDENTITY_INSERT umbracoLock ON");
|
||||
database.Insert("umbracoLock", "id", false, new LockDto { Id = 1, Name = "Lock.1" });
|
||||
database.Insert("umbracoLock", "id", false, new LockDto { Id = 2, Name = "Lock.2" });
|
||||
database.Insert("umbracoLock", "id", false, new LockDto { Id = 3, Name = "Lock.3" });
|
||||
database.Execute("SET IDENTITY_INSERT umbracoLock OFF");
|
||||
database.CompleteTransaction();
|
||||
}
|
||||
catch
|
||||
{
|
||||
database.AbortTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public override void TearDown()
|
||||
{
|
||||
//dispose!
|
||||
_dbFactory.Dispose();
|
||||
_uowProvider.Dispose();
|
||||
|
||||
base.TearDown();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Test()
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
{
|
||||
database.AcquireLockNodeReadLock(Constants.Locks.Servers);
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConcurrentReadersTest()
|
||||
{
|
||||
var threads = new List<Thread>();
|
||||
var locker = new object();
|
||||
var acquired = 0;
|
||||
var maxAcquired = 0;
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
threads.Add(new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
{
|
||||
database.AcquireLockNodeReadLock(Constants.Locks.Servers);
|
||||
lock (locker)
|
||||
{
|
||||
acquired++;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
lock (locker)
|
||||
{
|
||||
if (maxAcquired < acquired) maxAcquired = acquired;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
lock (locker)
|
||||
{
|
||||
acquired--;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
}
|
||||
}));
|
||||
}
|
||||
foreach (var thread in threads) thread.Start();
|
||||
foreach (var thread in threads) thread.Join();
|
||||
Assert.AreEqual(5, maxAcquired);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConcurrentWritersTest()
|
||||
{
|
||||
var threads = new List<Thread>();
|
||||
var locker = new object();
|
||||
var acquired = 0;
|
||||
var maxAcquired = 0;
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
threads.Add(new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(Constants.Locks.Servers);
|
||||
lock (locker)
|
||||
{
|
||||
acquired++;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
lock (locker)
|
||||
{
|
||||
if (maxAcquired < acquired) maxAcquired = acquired;
|
||||
}
|
||||
Thread.Sleep(500);
|
||||
lock (locker)
|
||||
{
|
||||
acquired--;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
}
|
||||
}));
|
||||
}
|
||||
foreach (var thread in threads) thread.Start();
|
||||
foreach (var thread in threads) thread.Join();
|
||||
Assert.AreEqual(1, maxAcquired);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DeadLockTest()
|
||||
{
|
||||
Exception e1 = null, e2 = null;
|
||||
|
||||
var thread1 = new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(1);
|
||||
Thread.Sleep(1000);
|
||||
database.AcquireLockNodeWriteLock(2);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e1 = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
}
|
||||
});
|
||||
var thread2 = new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(2);
|
||||
Thread.Sleep(1000);
|
||||
database.AcquireLockNodeWriteLock(1);
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e2 = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
}
|
||||
});
|
||||
thread1.Start();
|
||||
thread2.Start();
|
||||
thread1.Join();
|
||||
thread2.Join();
|
||||
Assert.IsNotNull(e1);
|
||||
Assert.IsNotNull(e2);
|
||||
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e1);
|
||||
Assert.IsInstanceOf<SqlCeLockTimeoutException>(e2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NoDeadLockTest()
|
||||
{
|
||||
Exception e1 = null, e2 = null;
|
||||
|
||||
var thread1 = new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
{
|
||||
database.AcquireLockNodeWriteLock(1);
|
||||
var info = database.Query<dynamic>("SELECT * FROM sys.lock_information;");
|
||||
Console.WriteLine("LOCKS:");
|
||||
foreach (var row in info)
|
||||
Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", row.request_spid,
|
||||
row.resource_type, row.resource_description, row.request_mode, row.resource_table,
|
||||
row.resource_table_id, row.request_status));
|
||||
Thread.Sleep(6000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e1 = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
}
|
||||
});
|
||||
var thread2 = new Thread(() =>
|
||||
{
|
||||
var database = DatabaseContext.Database;
|
||||
database.BeginTransaction(IsolationLevel.RepeatableRead);
|
||||
try
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
database.AcquireLockNodeWriteLock(2);
|
||||
var info = database.Query<dynamic>("SELECT * FROM sys.lock_information;");
|
||||
Console.WriteLine("LOCKS:");
|
||||
foreach (var row in info)
|
||||
Console.WriteLine(string.Format("> {0} {1} {2} {3} {4} {5} {6}", row.request_spid,
|
||||
row.resource_type, row.resource_description, row.request_mode, row.resource_table,
|
||||
row.resource_table_id, row.request_status));
|
||||
Thread.Sleep(1000);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e2 = e;
|
||||
}
|
||||
finally
|
||||
{
|
||||
database.CompleteTransaction();
|
||||
}
|
||||
});
|
||||
thread1.Start();
|
||||
thread2.Start();
|
||||
thread1.Join();
|
||||
thread2.Join();
|
||||
Assert.IsNull(e1);
|
||||
Assert.IsNull(e2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,30 +16,54 @@ namespace Umbraco.Tests.Persistence.Querying
|
||||
[TestFixture]
|
||||
public class ExpressionTests : BaseUsingSqlCeSyntax
|
||||
{
|
||||
// [Test]
|
||||
// public void Can_Query_With_Content_Type_Alias()
|
||||
// {
|
||||
// //Arrange
|
||||
// Expression<Func<IMedia, bool>> predicate = content => content.ContentType.Alias == "Test";
|
||||
// var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>();
|
||||
// var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
// [Test]
|
||||
// public void Can_Query_With_Content_Type_Alias()
|
||||
// {
|
||||
// //Arrange
|
||||
// Expression<Func<IMedia, bool>> predicate = content => content.ContentType.Alias == "Test";
|
||||
// var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>();
|
||||
// var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
|
||||
// Debug.Print("Model to Sql ExpressionHelper: \n" + result);
|
||||
// Debug.Print("Model to Sql ExpressionHelper: \n" + result);
|
||||
|
||||
// Assert.AreEqual("[cmsContentType].[alias] = @0", result);
|
||||
// Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]);
|
||||
// }
|
||||
// Assert.AreEqual("[cmsContentType].[alias] = @0", result);
|
||||
// Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]);
|
||||
// }
|
||||
|
||||
[Test]
|
||||
public void CachedExpression_Can_Verify_Path_StartsWith_Predicate_In_Same_Result()
|
||||
{
|
||||
//Arrange
|
||||
|
||||
//use a single cached expression for multiple expressions and ensure the correct output
|
||||
// is done for both of them.
|
||||
var cachedExpression = new CachedExpression();
|
||||
|
||||
|
||||
Expression<Func<IContent, bool>> predicate1 = content => content.Path.StartsWith("-1");
|
||||
cachedExpression.Wrap(predicate1);
|
||||
var modelToSqlExpressionHelper1 = new ModelToSqlExpressionVisitor<IContent>();
|
||||
var result1 = modelToSqlExpressionHelper1.Visit(cachedExpression);
|
||||
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result1);
|
||||
Assert.AreEqual("-1%", modelToSqlExpressionHelper1.GetSqlParameters()[0]);
|
||||
|
||||
Expression<Func<IContent, bool>> predicate2 = content => content.Path.StartsWith("-1,123,97");
|
||||
cachedExpression.Wrap(predicate2);
|
||||
var modelToSqlExpressionHelper2 = new ModelToSqlExpressionVisitor<IContent>();
|
||||
var result2 = modelToSqlExpressionHelper2.Visit(cachedExpression);
|
||||
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result2);
|
||||
Assert.AreEqual("-1,123,97%", modelToSqlExpressionHelper2.GetSqlParameters()[0]);
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Verify_Path_StartsWith_Predicate_In_Same_Result()
|
||||
{
|
||||
//Arrange
|
||||
Expression<Func<IContent, bool>> predicate = content => content.Path.StartsWith("-1");
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>();
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>();
|
||||
var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
|
||||
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
|
||||
|
||||
|
||||
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result);
|
||||
Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]);
|
||||
}
|
||||
@@ -49,7 +73,7 @@ namespace Umbraco.Tests.Persistence.Querying
|
||||
{
|
||||
//Arrange
|
||||
Expression<Func<IContent, bool>> predicate = content => content.ParentId == -1;
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IContent>();
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>();
|
||||
var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
|
||||
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
|
||||
@@ -62,7 +86,7 @@ namespace Umbraco.Tests.Persistence.Querying
|
||||
public void Equals_Operator_For_Value_Gets_Escaped()
|
||||
{
|
||||
Expression<Func<IUser, bool>> predicate = user => user.Username == "hello@world.com";
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IUser>();
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IUser>();
|
||||
var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
|
||||
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
|
||||
@@ -75,7 +99,7 @@ namespace Umbraco.Tests.Persistence.Querying
|
||||
public void Equals_Method_For_Value_Gets_Escaped()
|
||||
{
|
||||
Expression<Func<IUser, bool>> predicate = user => user.Username.Equals("hello@world.com");
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IUser>();
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IUser>();
|
||||
var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
|
||||
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
|
||||
@@ -91,7 +115,7 @@ namespace Umbraco.Tests.Persistence.Querying
|
||||
SqlSyntaxContext.SqlSyntaxProvider = new MySqlSyntaxProvider(Mock.Of<ILogger>());
|
||||
|
||||
Expression<Func<IUser, bool>> predicate = user => user.Username.Equals("mydomain\\myuser");
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IUser>();
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IUser>();
|
||||
var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
|
||||
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
|
||||
@@ -108,7 +132,7 @@ namespace Umbraco.Tests.Persistence.Querying
|
||||
SqlSyntaxContext.SqlSyntaxProvider = new MySqlSyntaxProvider(Mock.Of<ILogger>());
|
||||
|
||||
Expression<Func<UserDto, bool>> predicate = user => user.Login.StartsWith("mydomain\\myuser");
|
||||
var modelToSqlExpressionHelper = new PocoToSqlExpressionHelper<UserDto>();
|
||||
var modelToSqlExpressionHelper = new PocoToSqlExpressionVisitor<UserDto>();
|
||||
var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
|
||||
Debug.Print("Poco to Sql ExpressionHelper: \n" + result);
|
||||
@@ -121,7 +145,7 @@ namespace Umbraco.Tests.Persistence.Querying
|
||||
public void Sql_Replace_Mapped()
|
||||
{
|
||||
Expression<Func<IUser, bool>> predicate = user => user.Username.Replace("@world", "@test") == "hello@test.com";
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionHelper<IUser>();
|
||||
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IUser>();
|
||||
var result = modelToSqlExpressionHelper.Visit(predicate);
|
||||
|
||||
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
|
||||
|
||||
@@ -180,6 +180,7 @@
|
||||
<Compile Include="IO\ShadowFileSystemTests.cs" />
|
||||
<Compile Include="Logging\ConsoleLogger.cs" />
|
||||
<Compile Include="Migrations\MigrationIssuesTests.cs" />
|
||||
<Compile Include="Persistence\LocksTests.cs" />
|
||||
<Compile Include="Persistence\BulkDataReaderTests.cs" />
|
||||
<Compile Include="Persistence\Migrations\MigrationStartupHandlerTests.cs" />
|
||||
<Compile Include="Persistence\PetaPocoCachesTest.cs" />
|
||||
|
||||
@@ -1973,6 +1973,8 @@
|
||||
<Content Include="Views\Partials\Grid\Editors\Textstring.cshtml" />
|
||||
<Content Include="Views\Partials\Grid\Bootstrap2.cshtml" />
|
||||
<Content Include="Views\Partials\Grid\Editors\Base.cshtml" />
|
||||
<Content Include="Views\Partials\Grid\Bootstrap3-Fluid.cshtml" />
|
||||
<Content Include="Views\Partials\Grid\Bootstrap2-Fluid.cshtml" />
|
||||
<None Include="Web.Debug.config.transformed" />
|
||||
<None Include="web.Template.Debug.config">
|
||||
<DependentUpon>Web.Template.config</DependentUpon>
|
||||
|
||||
@@ -64,21 +64,29 @@
|
||||
JObject cfg = contentItem.config;
|
||||
|
||||
if(cfg != null)
|
||||
foreach (JProperty property in cfg.Properties()) {
|
||||
attrs.Add(property.Name + "='" + property.Value.ToString() + "'");
|
||||
foreach (JProperty property in cfg.Properties())
|
||||
{
|
||||
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
|
||||
attrs.Add(property.Name + "=\"" + propertyValue + "\"");
|
||||
}
|
||||
|
||||
|
||||
JObject style = contentItem.styles;
|
||||
|
||||
if (style != null) {
|
||||
var cssVals = new List<string>();
|
||||
foreach (JProperty property in style.Properties())
|
||||
cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
|
||||
if (style != null) {
|
||||
var cssVals = new List<string>();
|
||||
foreach (JProperty property in style.Properties())
|
||||
{
|
||||
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
|
||||
if (string.IsNullOrWhiteSpace(propertyValue) == false)
|
||||
{
|
||||
cssVals.Add(property.Name + ":" + propertyValue + ";");
|
||||
}
|
||||
}
|
||||
|
||||
if (cssVals.Any())
|
||||
attrs.Add("style='" + string.Join(" ", cssVals) + "'");
|
||||
if (cssVals.Any())
|
||||
attrs.Add("style='" + string.Join(" ", cssVals) + "'");
|
||||
}
|
||||
|
||||
|
||||
return new MvcHtmlString(string.Join(" ", attrs));
|
||||
}
|
||||
}
|
||||
@@ -64,21 +64,29 @@
|
||||
JObject cfg = contentItem.config;
|
||||
|
||||
if(cfg != null)
|
||||
foreach (JProperty property in cfg.Properties()) {
|
||||
attrs.Add(property.Name + "=\"" + property.Value.ToString() + "\"");
|
||||
foreach (JProperty property in cfg.Properties())
|
||||
{
|
||||
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
|
||||
attrs.Add(property.Name + "=\"" + propertyValue + "\"");
|
||||
}
|
||||
|
||||
|
||||
JObject style = contentItem.styles;
|
||||
|
||||
if (style != null) {
|
||||
var cssVals = new List<string>();
|
||||
foreach (JProperty property in style.Properties())
|
||||
cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
|
||||
if (style != null) {
|
||||
var cssVals = new List<string>();
|
||||
foreach (JProperty property in style.Properties())
|
||||
{
|
||||
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
|
||||
if (string.IsNullOrWhiteSpace(propertyValue) == false)
|
||||
{
|
||||
cssVals.Add(property.Name + ":" + propertyValue + ";");
|
||||
}
|
||||
}
|
||||
|
||||
if (cssVals.Any())
|
||||
attrs.Add("style=\"" + string.Join(" ", cssVals) + "\"");
|
||||
if (cssVals.Any())
|
||||
attrs.Add("style=\"" + string.Join(" ", cssVals) + "\"");
|
||||
}
|
||||
|
||||
|
||||
return new MvcHtmlString(string.Join(" ", attrs));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
@*
|
||||
Razor helpers located at the bottom of this file
|
||||
*@
|
||||
|
||||
@if (Model != null && Model.sections != null)
|
||||
{
|
||||
var oneColumn = ((System.Collections.ICollection)Model.sections).Count == 1;
|
||||
@@ -59,21 +60,29 @@
|
||||
JObject cfg = contentItem.config;
|
||||
|
||||
if(cfg != null)
|
||||
foreach (JProperty property in cfg.Properties()) {
|
||||
attrs.Add(property.Name + "='" + property.Value.ToString() + "'");
|
||||
foreach (JProperty property in cfg.Properties())
|
||||
{
|
||||
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
|
||||
attrs.Add(property.Name + "=\"" + propertyValue + "\"");
|
||||
}
|
||||
|
||||
|
||||
JObject style = contentItem.styles;
|
||||
|
||||
if (style != null) {
|
||||
var cssVals = new List<string>();
|
||||
foreach (JProperty property in style.Properties())
|
||||
cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
|
||||
if (style != null) {
|
||||
var cssVals = new List<string>();
|
||||
foreach (JProperty property in style.Properties())
|
||||
{
|
||||
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
|
||||
if (string.IsNullOrWhiteSpace(propertyValue) == false)
|
||||
{
|
||||
cssVals.Add(property.Name + ":" + propertyValue + ";");
|
||||
}
|
||||
}
|
||||
|
||||
if (cssVals.Any())
|
||||
attrs.Add("style='" + string.Join(" ", cssVals) + "'");
|
||||
if (cssVals.Any())
|
||||
attrs.Add("style='" + string.Join(" ", cssVals) + "'");
|
||||
}
|
||||
|
||||
|
||||
return new MvcHtmlString(string.Join(" ", attrs));
|
||||
}
|
||||
}
|
||||
@@ -64,21 +64,29 @@
|
||||
JObject cfg = contentItem.config;
|
||||
|
||||
if(cfg != null)
|
||||
foreach (JProperty property in cfg.Properties()) {
|
||||
attrs.Add(property.Name + "=\"" + property.Value.ToString() + "\"");
|
||||
foreach (JProperty property in cfg.Properties())
|
||||
{
|
||||
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
|
||||
attrs.Add(property.Name + "=\"" + propertyValue + "\"");
|
||||
}
|
||||
|
||||
|
||||
JObject style = contentItem.styles;
|
||||
|
||||
if (style != null) {
|
||||
var cssVals = new List<string>();
|
||||
foreach (JProperty property in style.Properties())
|
||||
cssVals.Add(property.Name + ":" + property.Value.ToString() + ";");
|
||||
if (style != null) {
|
||||
var cssVals = new List<string>();
|
||||
foreach (JProperty property in style.Properties())
|
||||
{
|
||||
var propertyValue = TemplateUtilities.CleanForXss(property.Value.ToString());
|
||||
if (string.IsNullOrWhiteSpace(propertyValue) == false)
|
||||
{
|
||||
cssVals.Add(property.Name + ":" + propertyValue + ";");
|
||||
}
|
||||
}
|
||||
|
||||
if (cssVals.Any())
|
||||
attrs.Add("style=\"" + string.Join(" ", cssVals) + "\"");
|
||||
if (cssVals.Any())
|
||||
attrs.Add("style=\"" + string.Join(" ", cssVals) + "\"");
|
||||
}
|
||||
|
||||
|
||||
return new MvcHtmlString(string.Join(" ", attrs));
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
@model dynamic
|
||||
@using Umbraco.Web.Templates
|
||||
|
||||
@functions {
|
||||
public static string EditorView(dynamic contentItem)
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
@model dynamic
|
||||
@using Umbraco.Web.Templates
|
||||
@Html.Raw(Model.value)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
@inherits UmbracoViewPage<dynamic>
|
||||
@using Umbraco.Web.Templates
|
||||
|
||||
|
||||
@if (Model.value != null)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
@model dynamic
|
||||
@using Umbraco.Web.Templates
|
||||
|
||||
@if (Model.value != null)
|
||||
{
|
||||
|
||||
@@ -4,10 +4,9 @@
|
||||
@if (Model.editor.config.markup != null)
|
||||
{
|
||||
string markup = Model.editor.config.markup.ToString();
|
||||
|
||||
var UmbracoHelper = new UmbracoHelper(UmbracoContext.Current);
|
||||
|
||||
markup = markup.Replace("#value#", UmbracoHelper.ReplaceLineBreaksForHtml(Model.value.ToString()));
|
||||
markup = markup.Replace("#value#", UmbracoHelper.ReplaceLineBreaksForHtml(TemplateUtilities.CleanForXss(Model.value.ToString())));
|
||||
markup = markup.Replace("#style#", Model.editor.config.style.ToString());
|
||||
|
||||
<text>
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Web.Cache
|
||||
/// </summary>
|
||||
/// <param name="json"></param>
|
||||
/// <returns></returns>
|
||||
private static JsonPayload[] DeserializeFromJsonPayload(string json)
|
||||
internal static JsonPayload[] DeserializeFromJsonPayload(string json)
|
||||
{
|
||||
var serializer = new JavaScriptSerializer();
|
||||
var jsonObject = serializer.Deserialize<JsonPayload[]>(json);
|
||||
@@ -45,30 +45,28 @@ namespace Umbraco.Web.Cache
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="isDeleted">if the item was deleted</param>
|
||||
/// <returns></returns>
|
||||
private static JsonPayload FromContentType(IContentTypeBase contentType, bool isDeleted = false)
|
||||
internal static JsonPayload FromContentType(IContentTypeBase contentType, bool isDeleted = false)
|
||||
{
|
||||
var payload = new JsonPayload
|
||||
{
|
||||
Alias = contentType.Alias,
|
||||
Id = contentType.Id,
|
||||
PropertyTypeIds = contentType.PropertyTypes.Select(x => x.Id).ToArray(),
|
||||
//either IContentType or IMediaType or IMemberType
|
||||
Type = (contentType is IContentType)
|
||||
? typeof(IContentType).Name
|
||||
: (contentType is IMediaType)
|
||||
{
|
||||
Alias = contentType.Alias,
|
||||
Id = contentType.Id,
|
||||
PropertyTypeIds = contentType.PropertyTypes.Select(x => x.Id).ToArray(),
|
||||
//either IContentType or IMediaType or IMemberType
|
||||
Type = (contentType is IContentType)
|
||||
? typeof(IContentType).Name
|
||||
: (contentType is IMediaType)
|
||||
? typeof(IMediaType).Name
|
||||
: typeof(IMemberType).Name,
|
||||
DescendantPayloads = contentType.Descendants().Select(x => FromContentType(x)).ToArray(),
|
||||
WasDeleted = isDeleted
|
||||
};
|
||||
//here we need to check if the alias of the content type changed or if one of the properties was removed.
|
||||
var dirty = contentType as IRememberBeingDirty;
|
||||
if (dirty != null)
|
||||
{
|
||||
payload.PropertyRemoved = dirty.WasPropertyDirty("HasPropertyTypeBeenRemoved");
|
||||
payload.AliasChanged = dirty.WasPropertyDirty("Alias");
|
||||
payload.IsNew = dirty.WasPropertyDirty("HasIdentity");
|
||||
}
|
||||
DescendantPayloads = contentType.Descendants().Select(x => FromContentType(x)).ToArray(),
|
||||
WasDeleted = isDeleted,
|
||||
PropertyRemoved = contentType.WasPropertyDirty("HasPropertyTypeBeenRemoved"),
|
||||
AliasChanged = contentType.WasPropertyDirty("Alias"),
|
||||
PropertyTypeAliasChanged = contentType.PropertyTypes.Any(x => x.WasPropertyDirty("Alias")),
|
||||
IsNew = contentType.WasPropertyDirty("HasIdentity")
|
||||
};
|
||||
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
@@ -90,7 +88,7 @@ namespace Umbraco.Web.Cache
|
||||
|
||||
#region Sub classes
|
||||
|
||||
private class JsonPayload
|
||||
internal class JsonPayload
|
||||
{
|
||||
public JsonPayload()
|
||||
{
|
||||
@@ -103,6 +101,7 @@ namespace Umbraco.Web.Cache
|
||||
public string Type { get; set; }
|
||||
public bool AliasChanged { get; set; }
|
||||
public bool PropertyRemoved { get; set; }
|
||||
public bool PropertyTypeAliasChanged { get; set; }
|
||||
public JsonPayload[] DescendantPayloads { get; set; }
|
||||
public bool WasDeleted { get; set; }
|
||||
public bool IsNew { get; set; }
|
||||
@@ -190,21 +189,21 @@ namespace Umbraco.Web.Cache
|
||||
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.IdToKeyCacheKey);
|
||||
ApplicationContext.Current.ApplicationCache.RuntimeCache.ClearCacheByKeySearch(CacheKeys.KeyToIdCacheKey);
|
||||
|
||||
payloads.ForEach(payload =>
|
||||
foreach (var payload in payloads)
|
||||
{
|
||||
//clear the cache for each item
|
||||
ClearContentTypeCache(payload);
|
||||
|
||||
//we only need to do this for IContentType NOT for IMediaType, we don't want to refresh the whole cache.
|
||||
//if the item was deleted or the alias changed or property removed then we need to refresh the content.
|
||||
//and, don't refresh the cache if it is new.
|
||||
if (payload.Type == typeof(IContentType).Name
|
||||
&& payload.IsNew == false
|
||||
&& (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged))
|
||||
{
|
||||
//clear the cache for each item
|
||||
ClearContentTypeCache(payload);
|
||||
|
||||
//we only need to do this for IContentType NOT for IMediaType, we don't want to refresh the whole cache.
|
||||
//if the item was deleted or the alias changed or property removed then we need to refresh the content.
|
||||
//and, don't refresh the cache if it is new.
|
||||
if (payload.Type == typeof(IContentType).Name
|
||||
&& !payload.IsNew
|
||||
&& (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved))
|
||||
{
|
||||
needsContentRefresh = true;
|
||||
}
|
||||
});
|
||||
needsContentRefresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
//need to refresh the xml content cache if required
|
||||
if (needsContentRefresh)
|
||||
@@ -237,7 +236,7 @@ namespace Umbraco.Web.Cache
|
||||
//cache if only a media type has changed.
|
||||
//we don't want to update the routes cache if all of the content types here are new.
|
||||
if (payloads.Any(x => x.Type == typeof(IContentType).Name)
|
||||
&& !payloads.All(x => x.IsNew)) //if they are all new then don't proceed
|
||||
&& payloads.All(x => x.IsNew) == false) //if they are all new then don't proceed
|
||||
{
|
||||
// SD: we need to clear the routes cache here!
|
||||
//
|
||||
|
||||
@@ -1,159 +0,0 @@
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using StackExchange.Profiling;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Profiling
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows
|
||||
/// us to profile items during app startup - before an HttpRequest is created
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all
|
||||
/// profiling data and this sub class no longer performs any logic.
|
||||
/// </remarks>
|
||||
internal class StartupWebProfilerProvider : WebRequestProfilerProvider
|
||||
{
|
||||
public StartupWebProfilerProvider()
|
||||
{
|
||||
_startupPhase = StartupPhase.Boot;
|
||||
//create the startup profiler
|
||||
_startupProfiler = new MiniProfiler("http://localhost/umbraco-startup", ProfileLevel.Verbose)
|
||||
{
|
||||
Name = "StartupProfiler"
|
||||
};
|
||||
}
|
||||
|
||||
private MiniProfiler _startupProfiler;
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine which phase the boot process is in
|
||||
/// </summary>
|
||||
private enum StartupPhase
|
||||
{
|
||||
None = 0,
|
||||
Boot = 1,
|
||||
Request = 2
|
||||
}
|
||||
|
||||
private volatile StartupPhase _startupPhase;
|
||||
|
||||
/// <summary>
|
||||
/// Executed once the application boot process is complete and changes the phase to Request
|
||||
/// </summary>
|
||||
public void BootComplete()
|
||||
{
|
||||
using (new ReadLock(_locker))
|
||||
{
|
||||
if (_startupPhase != StartupPhase.Boot) return;
|
||||
}
|
||||
|
||||
using (var l = new UpgradeableReadLock(_locker))
|
||||
{
|
||||
if (_startupPhase == StartupPhase.Boot)
|
||||
{
|
||||
l.UpgradeToWriteLock();
|
||||
_startupPhase = StartupPhase.Request;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executed when a profiling operation is completed
|
||||
/// </summary>
|
||||
/// <param name="discardResults"></param>
|
||||
/// <remarks>
|
||||
/// This checks if the bootup phase is None, if so, it just calls the base class, otherwise it checks
|
||||
/// if a profiler is active (i.e. in startup), then sets the phase to None so that the base class will be used
|
||||
/// for all subsequent calls.
|
||||
/// </remarks>
|
||||
public override void Stop(bool discardResults)
|
||||
{
|
||||
using (new ReadLock(_locker))
|
||||
{
|
||||
if (_startupPhase == StartupPhase.None)
|
||||
{
|
||||
base.Stop(discardResults);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
using (var l = new UpgradeableReadLock(_locker))
|
||||
{
|
||||
if (_startupPhase > 0 && base.GetCurrentProfiler() == null)
|
||||
{
|
||||
l.UpgradeToWriteLock();
|
||||
|
||||
_startupPhase = StartupPhase.None;
|
||||
|
||||
//This is required to pass the mini profiling context from before a request
|
||||
// to the current startup request.
|
||||
if (HttpContext.Current != null)
|
||||
{
|
||||
HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler;
|
||||
base.Stop(discardResults);
|
||||
_startupProfiler = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
base.Stop(discardResults);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executed when a profiling operation is started
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This checks if the startup phase is not None, if this is the case and the current profiler is NULL
|
||||
/// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method.
|
||||
/// </remarks>
|
||||
public override MiniProfiler Start(ProfileLevel level)
|
||||
{
|
||||
using (new ReadLock(_locker))
|
||||
{
|
||||
if (_startupPhase > 0 && base.GetCurrentProfiler() == null)
|
||||
{
|
||||
SetProfilerActive(_startupProfiler);
|
||||
return _startupProfiler;
|
||||
}
|
||||
|
||||
return base.Start(level);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns the current profiler
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// If the boot phase is not None, then this will return the startup profiler (this), otherwise
|
||||
/// returns the base class
|
||||
/// </remarks>
|
||||
public override MiniProfiler GetCurrentProfiler()
|
||||
{
|
||||
using (new ReadLock(_locker))
|
||||
{
|
||||
if (_startupPhase > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
var current = base.GetCurrentProfiler();
|
||||
if (current == null) return _startupProfiler;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return _startupProfiler;
|
||||
}
|
||||
}
|
||||
|
||||
return base.GetCurrentProfiler();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using StackExchange.Profiling;
|
||||
using StackExchange.Profiling.SqlFormatters;
|
||||
@@ -14,22 +15,23 @@ namespace Umbraco.Web.Profiling
|
||||
/// </summary>
|
||||
internal class WebProfiler : IProfiler
|
||||
{
|
||||
private StartupWebProfilerProvider _startupWebProfilerProvider;
|
||||
private const string BootRequestItemKey = "Umbraco.Web.Profiling.WebProfiler__isBootRequest";
|
||||
private WebProfilerProvider _provider;
|
||||
private int _first;
|
||||
|
||||
/// <summary>
|
||||
/// Constructor
|
||||
/// </summary>
|
||||
internal WebProfiler()
|
||||
{
|
||||
//setup some defaults
|
||||
// create our own provider, which can provide a profiler even during boot
|
||||
// MiniProfiler's default cannot because there's no HttpRequest in HttpContext
|
||||
_provider = new WebProfilerProvider();
|
||||
|
||||
// settings
|
||||
MiniProfiler.Settings.SqlFormatter = new SqlServerFormatter();
|
||||
MiniProfiler.Settings.StackMaxLength = 5000;
|
||||
|
||||
//At this point we know that we've been constructed during app startup, there won't be an HttpRequest in the HttpContext
|
||||
// since it hasn't started yet. So we need to do some hacking to enable profiling during startup.
|
||||
_startupWebProfilerProvider = new StartupWebProfilerProvider();
|
||||
//this should always be the case during startup, we'll need to set a custom profiler provider
|
||||
MiniProfiler.Settings.ProfilerProvider = _startupWebProfilerProvider;
|
||||
MiniProfiler.Settings.ProfilerProvider = _provider;
|
||||
|
||||
//Binds to application events to enable the MiniProfiler with a real HttpRequest
|
||||
UmbracoApplicationBase.ApplicationInit += UmbracoApplicationApplicationInit;
|
||||
@@ -57,43 +59,33 @@ namespace Umbraco.Web.Profiling
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the begin request event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
void UmbracoApplicationEndRequest(object sender, EventArgs e)
|
||||
{
|
||||
if (_startupWebProfilerProvider != null)
|
||||
{
|
||||
Stop();
|
||||
_startupWebProfilerProvider = null;
|
||||
}
|
||||
else if (CanPerformProfilingAction(sender))
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle the end request event
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
void UmbracoApplicationBeginRequest(object sender, EventArgs e)
|
||||
{
|
||||
if (_startupWebProfilerProvider != null)
|
||||
// if this is the first request, notify our own provider that this request is the boot request
|
||||
var first = Interlocked.Exchange(ref _first, 1) == 0;
|
||||
if (first)
|
||||
{
|
||||
_startupWebProfilerProvider.BootComplete();
|
||||
_provider.BeginBootRequest();
|
||||
((HttpApplication)sender).Context.Items[BootRequestItemKey] = true;
|
||||
// and no need to start anything, profiler is already there
|
||||
}
|
||||
|
||||
if (CanPerformProfilingAction(sender))
|
||||
{
|
||||
// else start a profiler, the normal way
|
||||
else if (ShouldProfile(sender))
|
||||
Start();
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanPerformProfilingAction(object sender)
|
||||
void UmbracoApplicationEndRequest(object sender, EventArgs e)
|
||||
{
|
||||
// if this is the boot request, or if we should profile this request, stop
|
||||
// (the boot request is always profiled, no matter what)
|
||||
var isBootRequest = ((HttpApplication)sender).Context.Items[BootRequestItemKey] != null; // fixme perfs
|
||||
if (isBootRequest)
|
||||
_provider.EndBootRequest();
|
||||
if (isBootRequest || ShouldProfile(sender))
|
||||
Stop();
|
||||
}
|
||||
|
||||
private bool ShouldProfile(object sender)
|
||||
{
|
||||
if (GlobalSettings.DebugMode == false)
|
||||
return false;
|
||||
@@ -108,10 +100,10 @@ namespace Umbraco.Web.Profiling
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrEmpty(request.Result.QueryString["umbDebug"]))
|
||||
return true;
|
||||
return false;
|
||||
|
||||
if (request.Result.Url.IsBackOfficeRequest(HttpRuntime.AppDomainAppVirtualPath))
|
||||
return true;
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
121
src/Umbraco.Web/Profiling/WebProfilerProvider.cs
Normal file
121
src/Umbraco.Web/Profiling/WebProfilerProvider.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using StackExchange.Profiling;
|
||||
|
||||
namespace Umbraco.Web.Profiling
|
||||
{
|
||||
/// <summary>
|
||||
/// This is a custom MiniProfiler WebRequestProfilerProvider (which is generally the default) that allows
|
||||
/// us to profile items during app startup - before an HttpRequest is created
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Once the boot phase is changed to StartupPhase.Request then the base class (default) provider will handle all
|
||||
/// profiling data and this sub class no longer performs any logic.
|
||||
/// </remarks>
|
||||
internal class WebProfilerProvider : WebRequestProfilerProvider
|
||||
{
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim();
|
||||
private MiniProfiler _startupProfiler;
|
||||
private int _first;
|
||||
private volatile BootPhase _bootPhase;
|
||||
|
||||
public WebProfilerProvider()
|
||||
{
|
||||
// booting...
|
||||
_bootPhase = BootPhase.Boot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates the boot phase.
|
||||
/// </summary>
|
||||
private enum BootPhase
|
||||
{
|
||||
Boot = 0, // boot phase (before the 1st request even begins)
|
||||
BootRequest = 1, // request boot phase (during the 1st request)
|
||||
Booted = 2 // done booting
|
||||
}
|
||||
|
||||
public void BeginBootRequest()
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_bootPhase != BootPhase.Boot)
|
||||
throw new InvalidOperationException("Invalid boot phase.");
|
||||
_bootPhase = BootPhase.BootRequest;
|
||||
|
||||
// assign the profiler to be the current MiniProfiler for the request
|
||||
// is's already active, starting and all
|
||||
HttpContext.Current.Items[":mini-profiler:"] = _startupProfiler;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
public void EndBootRequest()
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
try
|
||||
{
|
||||
if (_bootPhase != BootPhase.BootRequest)
|
||||
throw new InvalidOperationException("Invalid boot phase.");
|
||||
_bootPhase = BootPhase.Booted;
|
||||
|
||||
_startupProfiler = null;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_locker.ExitWriteLock();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executed when a profiling operation is started
|
||||
/// </summary>
|
||||
/// <param name="level"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// This checks if the startup phase is not None, if this is the case and the current profiler is NULL
|
||||
/// then this sets the startup profiler to be active. Otherwise it just calls the base class Start method.
|
||||
/// </remarks>
|
||||
public override MiniProfiler Start(ProfileLevel level)
|
||||
{
|
||||
var first = Interlocked.Exchange(ref _first, 1) == 0;
|
||||
if (first == false) return base.Start(level);
|
||||
|
||||
_startupProfiler = new MiniProfiler("http://localhost/umbraco-startup") { Name = "StartupProfiler" };
|
||||
SetProfilerActive(_startupProfiler);
|
||||
return _startupProfiler;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This returns the current profiler
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// If the boot phase is not None, then this will return the startup profiler (this), otherwise
|
||||
/// returns the base class
|
||||
/// </remarks>
|
||||
public override MiniProfiler GetCurrentProfiler()
|
||||
{
|
||||
// if not booting then just use base (fast)
|
||||
// no lock, _bootPhase is volatile
|
||||
if (_bootPhase == BootPhase.Booted)
|
||||
return base.GetCurrentProfiler();
|
||||
|
||||
// else
|
||||
try
|
||||
{
|
||||
var current = base.GetCurrentProfiler();
|
||||
return current ?? _startupProfiler;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return _startupProfiler;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
@@ -67,12 +68,12 @@ namespace Umbraco.Web.Search
|
||||
|
||||
/// <summary>
|
||||
/// This is used to refresh content indexers IndexData based on the DataService whenever a content type is changed since
|
||||
/// properties may have been added/removed
|
||||
/// properties may have been added/removed, then we need to re-index any required data if aliases have been changed
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
/// <remarks>
|
||||
/// See: http://issues.umbraco.org/issue/U4-4798
|
||||
/// See: http://issues.umbraco.org/issue/U4-4798, http://issues.umbraco.org/issue/U4-7833
|
||||
/// </remarks>
|
||||
static void ContentTypeCacheRefresherCacheUpdated(ContentTypeCacheRefresher sender, CacheRefresherEventArgs e)
|
||||
{
|
||||
@@ -81,6 +82,79 @@ namespace Umbraco.Web.Search
|
||||
{
|
||||
provider.RefreshIndexerDataFromDataService();
|
||||
}
|
||||
|
||||
if (e.MessageType == MessageType.RefreshByJson)
|
||||
{
|
||||
var contentTypesChanged = new HashSet<string>();
|
||||
var mediaTypesChanged = new HashSet<string>();
|
||||
var memberTypesChanged = new HashSet<string>();
|
||||
|
||||
var payloads = ContentTypeCacheRefresher.DeserializeFromJsonPayload(e.MessageObject.ToString());
|
||||
foreach (var payload in payloads)
|
||||
{
|
||||
if (payload.IsNew == false
|
||||
&& (payload.WasDeleted || payload.AliasChanged || payload.PropertyRemoved || payload.PropertyTypeAliasChanged))
|
||||
{
|
||||
//if we get here it means that some aliases have changed and the indexes for those particular doc types will need to be updated
|
||||
if (payload.Type == typeof(IContentType).Name)
|
||||
{
|
||||
//if it is content
|
||||
contentTypesChanged.Add(payload.Alias);
|
||||
}
|
||||
else if (payload.Type == typeof(IMediaType).Name)
|
||||
{
|
||||
//if it is media
|
||||
mediaTypesChanged.Add(payload.Alias);
|
||||
}
|
||||
else if (payload.Type == typeof(IMemberType).Name)
|
||||
{
|
||||
//if it is members
|
||||
memberTypesChanged.Add(payload.Alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//TODO: We need to update Examine to support re-indexing multiple items at once instead of one by one which will speed up
|
||||
// the re-indexing process, we don't want to revert to rebuilding the whole thing!
|
||||
|
||||
if (contentTypesChanged.Count > 0)
|
||||
{
|
||||
foreach (var alias in contentTypesChanged)
|
||||
{
|
||||
var ctType = ApplicationContext.Current.Services.ContentTypeService.GetContentType(alias);
|
||||
var contentItems = ApplicationContext.Current.Services.ContentService.GetContentOfContentType(ctType.Id);
|
||||
foreach (var contentItem in contentItems)
|
||||
{
|
||||
ReIndexForContent(contentItem, contentItem.HasPublishedVersion && contentItem.Trashed == false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mediaTypesChanged.Count > 0)
|
||||
{
|
||||
foreach (var alias in mediaTypesChanged)
|
||||
{
|
||||
var ctType = ApplicationContext.Current.Services.ContentTypeService.GetMediaType(alias);
|
||||
var mediaItems = ApplicationContext.Current.Services.MediaService.GetMediaOfMediaType(ctType.Id);
|
||||
foreach (var mediaItem in mediaItems)
|
||||
{
|
||||
ReIndexForMedia(mediaItem, mediaItem.Trashed == false);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (memberTypesChanged.Count > 0)
|
||||
{
|
||||
foreach (var alias in memberTypesChanged)
|
||||
{
|
||||
var ctType = ApplicationContext.Current.Services.MemberTypeService.Get(alias);
|
||||
var memberItems = ApplicationContext.Current.Services.MemberService.GetMembersByMemberType(ctType.Id);
|
||||
foreach (var memberItem in memberItems)
|
||||
{
|
||||
ReIndexForMember(memberItem);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static void MemberCacheRefresherCacheUpdated(MemberCacheRefresher sender, CacheRefresherEventArgs e)
|
||||
@@ -432,7 +506,7 @@ namespace Umbraco.Web.Search
|
||||
//add an icon attribute to get indexed
|
||||
xml.Add(new XAttribute("icon", sender.ContentType.Icon));
|
||||
|
||||
ExamineManager.Instance.ReIndexNode(
|
||||
ExamineManager.Instance.ReIndexNode(
|
||||
xml, IndexTypes.Content,
|
||||
ExamineManager.Instance.IndexProviderCollection.OfType<BaseUmbracoIndexer>()
|
||||
|
||||
|
||||
@@ -7,17 +7,17 @@ using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Templates
|
||||
{
|
||||
//NOTE: I realize there is only one class in this namespace but I'm pretty positive that there will be more classes in
|
||||
//this namespace once we start migrating and cleaning up more code.
|
||||
//NOTE: I realize there is only one class in this namespace but I'm pretty positive that there will be more classes in
|
||||
//this namespace once we start migrating and cleaning up more code.
|
||||
|
||||
/// <summary>
|
||||
/// Utility class used for templates
|
||||
/// </summary>
|
||||
public static class TemplateUtilities
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility class used for templates
|
||||
/// </summary>
|
||||
public static class TemplateUtilities
|
||||
{
|
||||
//TODO: Pass in an Umbraco context!!!!!!!! Don't rely on the singleton so things are more testable
|
||||
internal static string ParseInternalLinks(string text, bool preview)
|
||||
{
|
||||
{
|
||||
// save and set for url provider
|
||||
var inPreviewMode = UmbracoContext.Current.InPreviewMode;
|
||||
UmbracoContext.Current.InPreviewMode = preview;
|
||||
@@ -33,79 +33,84 @@ namespace Umbraco.Web.Templates
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Parses the string looking for the {localLink} syntax and updates them to their correct links.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static string ParseInternalLinks(string text)
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the string looking for the {localLink} syntax and updates them to their correct links.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
public static string ParseInternalLinks(string text)
|
||||
{
|
||||
//TODO: Pass in an Umbraco context!!!!!!!! Don't rely on the singleton so things are more testable, better yet, pass in urlprovider, routing context, separately
|
||||
|
||||
//don't attempt to proceed without a context as we cannot lookup urls without one
|
||||
if (UmbracoContext.Current == null || UmbracoContext.Current.RoutingContext == null)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
//don't attempt to proceed without a context as we cannot lookup urls without one
|
||||
if (UmbracoContext.Current == null || UmbracoContext.Current.RoutingContext == null)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
|
||||
var urlProvider = UmbracoContext.Current.UrlProvider;
|
||||
var urlProvider = UmbracoContext.Current.UrlProvider;
|
||||
|
||||
// Parse internal links
|
||||
var tags = Regex.Matches(text, @"href=""[/]?(?:\{|\%7B)localLink:([0-9]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
foreach (Match tag in tags)
|
||||
if (tag.Groups.Count > 0)
|
||||
{
|
||||
var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1);
|
||||
var newLink = urlProvider.GetUrl(int.Parse(id));
|
||||
text = text.Replace(tag.Value, "href=\"" + newLink);
|
||||
}
|
||||
// Parse internal links
|
||||
var tags = Regex.Matches(text, @"href=""[/]?(?:\{|\%7B)localLink:([0-9]+)(?:\}|\%7D)", RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
foreach (Match tag in tags)
|
||||
if (tag.Groups.Count > 0)
|
||||
{
|
||||
var id = tag.Groups[1].Value; //.Remove(tag.Groups[1].Value.Length - 1, 1);
|
||||
var newLink = urlProvider.GetUrl(int.Parse(id));
|
||||
text = text.Replace(tag.Value, "href=\"" + newLink);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
// static compiled regex for faster performance
|
||||
private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
// static compiled regex for faster performance
|
||||
private readonly static Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?",
|
||||
RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
|
||||
|
||||
/// <summary>
|
||||
/// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// When used with a Virtual-Directory set-up, this would resolve all URLs correctly.
|
||||
/// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs.
|
||||
/// </remarks>
|
||||
public static string ResolveUrlsFromTextString(string text)
|
||||
{
|
||||
/// <summary>
|
||||
/// The RegEx matches any HTML attribute values that start with a tilde (~), those that match are passed to ResolveUrl to replace the tilde with the application path.
|
||||
/// </summary>
|
||||
/// <param name="text"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// When used with a Virtual-Directory set-up, this would resolve all URLs correctly.
|
||||
/// The recommendation is that the "ResolveUrlsFromTextString" option (in umbracoSettings.config) is set to false for non-Virtual-Directory installs.
|
||||
/// </remarks>
|
||||
public static string ResolveUrlsFromTextString(string text)
|
||||
{
|
||||
if (UmbracoConfig.For.UmbracoSettings().Content.ResolveUrlsFromTextString == false) return text;
|
||||
|
||||
using (var timer = DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete"))
|
||||
{
|
||||
// find all relative urls (ie. urls that contain ~)
|
||||
var tags = ResolveUrlPattern.Matches(text);
|
||||
LogHelper.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count);
|
||||
foreach (Match tag in tags)
|
||||
{
|
||||
var url = "";
|
||||
if (tag.Groups[1].Success)
|
||||
url = tag.Groups[1].Value;
|
||||
using (var timer = DisposableTimer.DebugDuration(typeof(IOHelper), "ResolveUrlsFromTextString starting", "ResolveUrlsFromTextString complete"))
|
||||
{
|
||||
// find all relative urls (ie. urls that contain ~)
|
||||
var tags = ResolveUrlPattern.Matches(text);
|
||||
LogHelper.Debug(typeof(IOHelper), "After regex: " + timer.Stopwatch.ElapsedMilliseconds + " matched: " + tags.Count);
|
||||
foreach (Match tag in tags)
|
||||
{
|
||||
var url = "";
|
||||
if (tag.Groups[1].Success)
|
||||
url = tag.Groups[1].Value;
|
||||
|
||||
// The richtext editor inserts a slash in front of the url. That's why we need this little fix
|
||||
// if (url.StartsWith("/"))
|
||||
// text = text.Replace(url, ResolveUrl(url.Substring(1)));
|
||||
// else
|
||||
if (String.IsNullOrEmpty(url) == false)
|
||||
{
|
||||
var resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url);
|
||||
text = text.Replace(url, resolvedUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
// The richtext editor inserts a slash in front of the url. That's why we need this little fix
|
||||
// if (url.StartsWith("/"))
|
||||
// text = text.Replace(url, ResolveUrl(url.Substring(1)));
|
||||
// else
|
||||
if (String.IsNullOrEmpty(url) == false)
|
||||
{
|
||||
var resolvedUrl = (url.Substring(0, 1) == "/") ? IOHelper.ResolveUrl(url.Substring(1)) : IOHelper.ResolveUrl(url);
|
||||
text = text.Replace(url, resolvedUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
public static string CleanForXss(string text, params char[] ignoreFromClean)
|
||||
{
|
||||
return text.CleanForXss(ignoreFromClean);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +390,7 @@
|
||||
<Compile Include="Mvc\UmbracoRequireHttpsAttribute.cs" />
|
||||
<Compile Include="Mvc\ValidateMvcAngularAntiForgeryTokenAttribute.cs" />
|
||||
<Compile Include="OwinMiddlewareConfiguredEventArgs.cs" />
|
||||
<Compile Include="Profiling\StartupWebProfilerProvider.cs" />
|
||||
<Compile Include="Profiling\WebProfilerProvider.cs" />
|
||||
<Compile Include="Profiling\WebProfiler.cs" />
|
||||
<Compile Include="PropertyEditors\DatePreValueEditor.cs" />
|
||||
<Compile Include="PropertyEditors\DateTimePreValueEditor.cs" />
|
||||
|
||||
@@ -362,6 +362,11 @@ namespace UmbracoExamine
|
||||
|
||||
#region Protected
|
||||
|
||||
/// <summary>
|
||||
/// This is a static query, it's parameters don't change so store statically
|
||||
/// </summary>
|
||||
private static readonly IQuery<IContent> PublishedQuery = Query<IContent>.Builder.Where(x => x.Published == true);
|
||||
|
||||
protected override void PerformIndexAll(string type)
|
||||
{
|
||||
const int pageSize = 10000;
|
||||
@@ -389,9 +394,7 @@ namespace UmbracoExamine
|
||||
else
|
||||
{
|
||||
//add the published filter
|
||||
var qry = Query<IContent>.Builder.Where(x => x.Published == true);
|
||||
|
||||
descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "Path", Direction.Ascending, true, qry);
|
||||
descendants = _contentService.GetPagedDescendants(contentParentId, pageIndex, pageSize, out total, "Path", Direction.Ascending, true, PublishedQuery);
|
||||
}
|
||||
|
||||
//if specific types are declared we need to post filter them
|
||||
|
||||
Reference in New Issue
Block a user