Compare commits

...

41 Commits

Author SHA1 Message Date
Sebastiaan Janssen 026805e4c1 Bumps version 2016-03-03 11:25:51 +01:00
Sebastiaan Janssen a7dad09f44 Ignores unit test that refuses to work on AppVeyor 2016-03-03 11:22:11 +01:00
Sebastiaan Janssen e4aa7c068b Get this branch to build on AppVeyor 2016-03-03 11:22:01 +01:00
Sebastiaan Janssen cc211ab3bd Removes unused class 2016-03-03 11:21:36 +01:00
Sebastiaan Janssen cb72e6344a Merge pull request #1155 from darrenferguson/6.2.5-dictionary-thread-safety-fix
Apply 8905878a87 to 6.2.5 for performan…
2016-03-03 11:17:07 +01:00
Shannon bf3ddd9384 Apply 8905878a87 to 6.2.5 for performance fix. 2016-03-01 15:33:22 +00:00
Shannon 8a6ece4a6d Ensures all mvc refs are original 4.0.0.0 so there's no more probs during build/run 2015-05-11 11:49:30 +10:00
Shannon d2662c17f7 Fix dictionary key access
Looks like someone does not know how to use a dictionary
2015-02-20 10:06:13 +01:00
Sebastiaan Janssen 18c2652f67 Copy webapi dlls locally so that they get included in the final zip build 2015-02-19 16:24:32 +01:00
Sebastiaan Janssen da0d97c108 Bump version 2015-02-19 15:57:10 +01:00
Sebastiaan Janssen f672a323b0 Bump version 2015-02-19 15:11:08 +01:00
Shannon a59dd32dd1 Updates cdf refs 2015-02-18 09:15:07 +01:00
Sebastiaan Janssen d4d5a355e3 Update CDF dependency 2015-02-10 15:08:27 +01:00
Shannon f4a0bcfc96 updates nuspec 2015-02-03 13:15:15 +11:00
Shannon aadae6334b Fixes: U4-2038 Violation of PRIMARY KEY constraint 'PK_cmsContentPreviewXml' YSOD occurs when publishing content
Conflicts:
	src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs
	src/Umbraco.Core/Umbraco.Core.csproj
2015-01-30 10:13:04 +11:00
Shannon 7590bd86dd Merge branch 'dev-v6' of https://github.com/umbraco/Umbraco-CMS into dev-v6 2015-01-07 15:44:05 +11:00
Shannon 6b25e5299f fixes unit test 2015-01-07 15:43:47 +11:00
Stephan b30555f5a4 Merge pull request #599 from solutionmania/dev-v6
Merge into dev v6
2014-12-23 17:44:25 +01:00
Chris Roberts d4d77611eb Fixed: U4-5193 HasAccess override method name error 2014-12-23 16:27:51 +00:00
Chris Roberts 662ed03f0a Fixed: U4-6051 HasAccces method tests the roles of the wrong user 2014-12-23 16:23:47 +00:00
Shannon 3c01dcb30c Fixes: U4-6030 2014-12-18 15:16:29 +11:00
Shannon aa439cacda working on U4-6030 2014-12-18 14:58:49 +11:00
Shannon e643f2a0ba working on U4-6030 2014-12-18 14:20:41 +11:00
Shannon a32d38f7ea Fixes: U4-5992 Caching of XPathNodeIterator in umbraco.library is not thread safe and will cause media errors like valueDictionary exception 2014-12-16 14:26:53 +11:00
Shannon 864994a56e updates error msgs when sending requests to the same server pointing to docs on baseUrl 2014-12-08 17:04:22 +11:00
Shannon 3405c5001b working on U4-5964, U4-5907, U4-5965 2014-12-08 15:18:32 +11:00
Shannon 4d8732d925 The only way i could get this project to build and run was to change the transform to explicitly target 4.0.0.1 for mvc and webapi, otherwise nothing works and horribly cryptic build errors. Added the tests for the BackgroundTaskRunner 2014-12-08 14:59:32 +11:00
Sebastiaan Janssen 4eb9a54fa5 Ports v7 changes for U4-5728 - OriginalRequestUrl not initialized, KeepAlive or ScheduledPublishing run too soon
#U4-5728
2014-12-07 16:16:29 +01:00
Shannon 71ca90d74b Merge remote-tracking branch 'origin/6.2.2' into 6.2.4 2014-11-21 09:14:22 +11:00
Shannon Deminick fb42472f31 Merge pull request #554 from cloud46limited/umbUKFest-3556
Fixed type in Step 4 install wizard - U4-3556.
2014-11-21 09:13:10 +11:00
Gareth Evans - Cloud46 Limited b6695b6953 Fixed type in Step 4 install wizard - U4-3556. 2014-11-20 14:48:26 +00:00
Shannon 1eab26c0f6 reverts IQuery 2014-10-08 17:26:55 +11:00
Shannon 0543c620eb bumps version 2014-10-08 17:25:06 +11:00
Shannon 646e5f173d Fixes descendant queries to ensure that the comma is suffixed to the path, otherwise strange things could happen when there are longer ids! 2014-10-08 17:23:14 +11:00
Shannon b5594517bd Fixes: U4-5583 Can no longer link to Media Items from RTE in 6.2.3 - Due to a cherry pick/merge issue since the newer 7.x EntityRepository deals with NVarchar and NText values whereas the 6.x one did not. Have backported the mapping poco classes to now also support NVarchar and NText for the EntityRepository. Have also fixed the basic query to ensure that there are no left joins before inner joins which can also cause problems. Fixed up the duplicate where clause. Adds more tests to entity service. 2014-10-08 17:13:28 +11:00
Sebastiaan Janssen 246dfba8ac Bump version 2014-10-02 18:06:15 +02:00
Shannon 7d189a6fb8 Backports the @ symbol escaping fixes 2014-10-03 00:28:49 +10:00
Shannon 71242430da Fixes a query that would cause the new petapoco changes to have issues since it was asking for the wrong type 2014-10-01 16:37:42 +10:00
Shannon 4ee4f296fc updates version 2014-09-29 17:31:24 +10:00
Shannon b9e4950e36 fixes up entity repo merges to support old db schema 2014-09-29 17:30:09 +10:00
Shannon ba52f9235e Backports fixes to SQL parameterization and PetaPoco mem updates 2014-09-29 17:11:58 +10:00
102 changed files with 3681 additions and 2102 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
@ECHO OFF
SET release=6.2.2
SET release=6.2.6
SET comment=
SET version=%release%
+2 -2
View File
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>UmbracoCms.Core</id>
<version>6.2.2</version>
<version>6.2.3</version>
<title>Umbraco Cms Core Binaries</title>
<authors>Umbraco HQ</authors>
<owners>Umbraco HQ</owners>
@@ -28,7 +28,7 @@
<dependency id="SharpZipLib" version="[0.86.0, 1.0.0)" />
<dependency id="MySql.Data" version="[6.6.5]" />
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
<dependency id="ClientDependency" version="[1.7.1.2, 2.0.0)" />
<dependency id="ClientDependency" version="[1.8.2.1, 2.0.0)" />
<dependency id="ClientDependency-Mvc" version="[1.7.0.4, 2.0.0)" />
<dependency id="Newtonsoft.Json" version="[4.5.11, 6.0.0)" />
<dependency id="Examine" version="[0.1.57, 1.0.0)" />
+1 -1
View File
@@ -2,7 +2,7 @@
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata>
<id>UmbracoCms</id>
<version>6.2.2</version>
<version>6.2.3</version>
<title>Umbraco Cms</title>
<authors>Umbraco HQ</authors>
<owners>Umbraco HQ</owners>
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<UmbracoVersion>6.2.2</UmbracoVersion>
<UmbracoVersion>6.2.6</UmbracoVersion>
</PropertyGroup>
<Target Name="CopyUmbracoFilesToWebRoot" BeforeTargets="AfterBuild">
<PropertyGroup>
@@ -843,6 +843,11 @@ namespace Umbraco.Core.Configuration
get { return GetKeyAsNode("/settings/scheduledTasks"); }
}
internal static string ScheduledTasksBaseUrl
{
get { return GetKey("/settings/scheduledTasks/@baseUrl"); }
}
/// <summary>
/// Gets a list of characters that will be replaced when generating urls
/// </summary>
@@ -5,7 +5,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("6.2.2");
private static readonly Version Version = new Version("6.2.6");
/// <summary>
/// Gets the current version of Umbraco.
+6
View File
@@ -34,6 +34,12 @@ namespace Umbraco.Core
AddInt(d.GetHashCode());
}
internal void AddString(string s)
{
if (s != null)
AddInt((StringComparer.InvariantCulture).GetHashCode(s));
}
internal void AddCaseInsensitiveString(string s)
{
if (s != null)
+5 -1
View File
@@ -449,10 +449,14 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (Content)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually clone this since it's not settable
clone._contentType = (IContentType)ContentType.DeepClone();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
@@ -10,12 +10,14 @@ namespace Umbraco.Core.Models
internal class ContentPreviewEntity<TContent> : ContentXmlEntity<TContent>
where TContent : IContentBase
{
public ContentPreviewEntity(bool previewExists, TContent content, Func<TContent, XElement> xml)
: base(previewExists, content, xml)
public ContentPreviewEntity(TContent content, Func<TContent, XElement> xml)
: base(content, xml)
{
Version = content.Version;
}
public Guid Version { get; private set; }
public Guid Version
{
get { return Content.Version; }
}
}
}
+6 -1
View File
@@ -599,13 +599,18 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (ContentTypeBase)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually wire up the event handlers for the property type collections - we've ensured
// its ignored from the auto-clone process because its return values are unions, not raw and
// we end up with duplicates, see: http://issues.umbraco.org/issue/U4-4842
clone._propertyTypes = (PropertyTypeCollection)_propertyTypes.DeepClone();
clone._propertyTypes.CollectionChanged += clone.PropertyTypesChanged;
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
@@ -221,11 +221,15 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (ContentTypeCompositionBase)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually assign since this is an internal field and will not be automatically mapped
clone.RemovedContentTypeKeyTracker = new List<int>();
clone._contentTypeComposition = ContentTypeComposition.Select(x => (IContentTypeComposition)x.DeepClone()).ToList();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+9 -5
View File
@@ -11,13 +11,11 @@ namespace Umbraco.Core.Models
internal class ContentXmlEntity<TContent> : IAggregateRoot
where TContent : IContentBase
{
private readonly bool _entityExists;
private readonly Func<TContent, XElement> _xml;
public ContentXmlEntity(bool entityExists, TContent content, Func<TContent, XElement> xml)
{
public ContentXmlEntity(TContent content, Func<TContent, XElement> xml)
{
if (content == null) throw new ArgumentNullException("content");
_entityExists = entityExists;
_xml = xml;
Content = content;
}
@@ -32,6 +30,7 @@ namespace Umbraco.Core.Models
{
get { return _xml(Content); }
}
public TContent Content { get; private set; }
public int Id
@@ -44,9 +43,14 @@ namespace Umbraco.Core.Models
public DateTime CreateDate { get; set; }
public DateTime UpdateDate { get; set; }
/// <summary>
/// Special case, always return false, this will cause the repositories managing
/// this object to always do an 'insert' but these are special repositories that
/// do an InsertOrUpdate on insert since the data for this needs to be managed this way
/// </summary>
public bool HasIdentity
{
get { return _entityExists; }
get { return false; }
}
public object DeepClone()
@@ -236,9 +236,17 @@ namespace Umbraco.Core.Models.EntityBase
//Memberwise clone on Entity will work since it doesn't have any deep elements
// for any sub class this will work for standard properties as well that aren't complex object's themselves.
var clone = (Entity)MemberwiseClone();
//ensure the clone has it's own dictionaries
clone.ResetChangeTrackingCollections();
//turn off change tracking
clone.DisableChangeTracking();
//Automatically deep clone ref properties that are IDeepCloneable
DeepCloneHelper.DeepCloneRefProperties(this, clone);
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
//Using data contract serializer - has issues
@@ -14,6 +14,16 @@ namespace Umbraco.Core.Models.EntityBase
[DataContract(IsReference = true)]
public abstract class TracksChangesEntityBase : IRememberBeingDirty
{
//TODO: This needs to go on to ICanBeDirty http://issues.umbraco.org/issue/U4-5662
public virtual IEnumerable<string> GetDirtyProperties()
{
return _propertyChangedInfo.Where(x => x.Value).Select(x => x.Key);
}
private bool _changeTrackingEnabled = true;
/// <summary>
/// Tracks the properties that have changed
/// </summary>
@@ -35,6 +45,9 @@ namespace Umbraco.Core.Models.EntityBase
/// <param name="propertyInfo">The property info.</param>
protected virtual void OnPropertyChanged(PropertyInfo propertyInfo)
{
//return if we're not tracking changes
if (_changeTrackingEnabled == false) return;
_propertyChangedInfo[propertyInfo.Name] = true;
if (PropertyChanged != null)
@@ -126,6 +139,22 @@ namespace Umbraco.Core.Models.EntityBase
_propertyChangedInfo = new Dictionary<string, bool>();
}
protected void ResetChangeTrackingCollections()
{
_propertyChangedInfo = new Dictionary<string, bool>();
_lastPropertyChangedInfo = new Dictionary<string, bool>();
}
protected void DisableChangeTracking()
{
_changeTrackingEnabled = false;
}
protected void EnableChangeTracking()
{
_changeTrackingEnabled = true;
}
/// <summary>
/// Used by inheritors to set the value of properties, this will detect if the property value actually changed and if it did
/// it will ensure that the property has a dirty flag set.
@@ -143,12 +172,20 @@ namespace Umbraco.Core.Models.EntityBase
{
var initVal = value;
var newVal = setValue(value);
if (!Equals(initVal, newVal))
//don't track changes, just set the value (above)
if (_changeTrackingEnabled == false) return false;
if (Equals(initVal, newVal) == false)
{
OnPropertyChanged(propertySelector);
return true;
}
return false;
}
}
}
+5 -2
View File
@@ -108,12 +108,15 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (File)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually assign since they are readonly properties
clone._alias = Alias;
clone._name = Name;
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+5 -1
View File
@@ -638,10 +638,14 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (Member)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually clone this since it's not settable
clone._contentType = (IMemberType)ContentType.DeepClone();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
+5 -2
View File
@@ -448,15 +448,18 @@ namespace Umbraco.Core.Models.Membership
public override object DeepClone()
{
var clone = (User)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to create new collections otherwise they'll get copied by ref
clone._addedSections = new List<string>();
clone._removedSections = new List<string>();
clone._sectionCollection = new ObservableCollection<string>(_sectionCollection.ToList());
//re-create the event handler
clone._sectionCollection.CollectionChanged += clone.SectionCollectionChanged;
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+5 -1
View File
@@ -151,10 +151,14 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (Property)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually assign since this is a readonly property
clone._propertyType = (PropertyType)PropertyType.DeepClone();
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+5 -2
View File
@@ -432,14 +432,17 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (PropertyType)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually assign the Lazy value as it will not be automatically mapped
if (PropertyGroupId != null)
{
clone._propertyGroupId = new Lazy<int>(() => PropertyGroupId.Value);
}
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+5 -2
View File
@@ -201,12 +201,15 @@ namespace Umbraco.Core.Models
public override object DeepClone()
{
var clone = (Template)base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//need to manually assign since they are readonly properties
clone._alias = Alias;
clone._name = Name;
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
+23
View File
@@ -287,6 +287,29 @@ namespace Umbraco.Core.Models
}
}
public override object DeepClone()
{
var clone = (UmbracoEntity) base.DeepClone();
//turn off change tracking
clone.DisableChangeTracking();
//This ensures that any value in the dictionary that is deep cloneable is cloned too
foreach (var key in clone.AdditionalData.Keys.ToArray())
{
var deepCloneable = clone.AdditionalData[key] as IDeepCloneable;
if (deepCloneable != null)
{
clone.AdditionalData[key] = deepCloneable.DeepClone();
}
}
//this shouldn't really be needed since we're not tracking
clone.ResetDirtyProperties(false);
//re-enable tracking
clone.EnableChangeTracking();
return clone;
}
/// <summary>
/// Some entities may expose additional data that other's might not, this custom data will be available in this collection
/// </summary>
@@ -99,11 +99,13 @@ namespace Umbraco.Core.Persistence.Factories
foreach (var propertyDto in dto.UmbracoPropertyDtos)
{
entity.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty
{
DataTypeControlId =
propertyDto.DataTypeControlId,
Value = propertyDto.UmbracoFile
});
{
DataTypeControlId =
propertyDto.DataTypeControlId,
Value = propertyDto.NTextValue.IsNullOrWhiteSpace()
? propertyDto.NVarcharValue
: propertyDto.NTextValue
});
}
}
+264 -187
View File
@@ -13,6 +13,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Caching;
using System.Security;
using System.Security.Permissions;
using System.Text;
@@ -1709,8 +1710,32 @@ namespace Umbraco.Core.Persistence
}
public override object ChangeType(object val) { return val; }
}
public class PocoData
{
/// <summary>
/// Container for a Memory cache object
/// </summary>
/// <remarks>
/// Better to have one memory cache instance than many so it's memory management can be handled more effectively
/// http://stackoverflow.com/questions/8463962/using-multiple-instances-of-memorycache
/// </remarks>
internal class ManagedCache
{
public ObjectCache GetCache()
{
return ObjectCache;
}
static readonly ObjectCache ObjectCache = new MemoryCache("NPoco");
}
public class PocoData
{
//USE ONLY FOR TESTING
internal static bool UseLongKeys = false;
//USE ONLY FOR TESTING - default is one hr
internal static int SlidingExpirationSeconds = 3600;
public static PocoData ForObject(object o, string primaryKeyName)
{
var t = o.GetType();
@@ -1734,7 +1759,7 @@ namespace Umbraco.Core.Persistence
#endif
return ForType(t);
}
static System.Threading.ReaderWriterLockSlim RWLock = new System.Threading.ReaderWriterLockSlim();
public static PocoData ForType(Type t)
{
#if !PETAPOCO_NO_DYNAMIC
@@ -1742,7 +1767,7 @@ namespace Umbraco.Core.Persistence
throw new InvalidOperationException("Can't use dynamic types with this method");
#endif
// Check cache
RWLock.EnterReadLock();
InnerLock.EnterReadLock();
PocoData pd;
try
{
@@ -1751,12 +1776,12 @@ namespace Umbraco.Core.Persistence
}
finally
{
RWLock.ExitReadLock();
InnerLock.ExitReadLock();
}
// Cache it
RWLock.EnterWriteLock();
InnerLock.EnterWriteLock();
try
{
// Check again
@@ -1770,7 +1795,7 @@ namespace Umbraco.Core.Persistence
}
finally
{
RWLock.ExitWriteLock();
InnerLock.ExitWriteLock();
}
return pd;
@@ -1851,224 +1876,237 @@ namespace Umbraco.Core.Persistence
return tc >= TypeCode.SByte && tc <= TypeCode.UInt64;
}
// Create factory function that can convert a IDataReader record into a POCO
public Delegate GetFactory(string sql, string connString, bool ForceDateTimesToUtc, int firstColumn, int countColumns, IDataReader r)
{
// Check cache
var key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns);
RWLock.EnterReadLock();
try
{
// Have we already created it?
Delegate factory;
if (PocoFactories.TryGetValue(key, out factory))
return factory;
}
finally
{
RWLock.ExitReadLock();
}
// Take the writer lock
RWLock.EnterWriteLock();
//TODO: It would be nice to remove the irrelevant SQL parts - for a mapping operation anything after the SELECT clause isn't required.
// This would ensure less duplicate entries that get cached, currently both of these queries would be cached even though they are
// returning the same structured data:
// SELECT * FROM MyTable ORDER BY MyColumn
// SELECT * FROM MyTable ORDER BY MyColumn DESC
try
{
string key;
if (UseLongKeys)
{
key = string.Format("{0}:{1}:{2}:{3}:{4}", sql, connString, ForceDateTimesToUtc, firstColumn, countColumns);
}
else
{
//Create a hashed key, we don't want to store so much string data in memory
var combiner = new HashCodeCombiner();
combiner.AddCaseInsensitiveString(sql);
combiner.AddCaseInsensitiveString(connString);
combiner.AddObject(ForceDateTimesToUtc);
combiner.AddInt(firstColumn);
combiner.AddInt(countColumns);
key = combiner.GetCombinedHashCode();
}
// Check again, just in case
Delegate factory;
if (PocoFactories.TryGetValue(key, out factory))
return factory;
var objectCache = _managedCache.GetCache();
// Create the method
var m = new DynamicMethod("petapoco_factory_" + PocoFactories.Count.ToString(), type, new Type[] { typeof(IDataReader) }, true);
var il = m.GetILGenerator();
Func<Delegate> factory = () =>
{
// Create the method
var m = new DynamicMethod("petapoco_factory_" + objectCache.GetCount(), type, new Type[] { typeof(IDataReader) }, true);
var il = m.GetILGenerator();
#if !PETAPOCO_NO_DYNAMIC
if (type == typeof(object))
{
// var poco=new T()
il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj
if (type == typeof(object))
{
// var poco=new T()
il.Emit(OpCodes.Newobj, typeof(System.Dynamic.ExpandoObject).GetConstructor(Type.EmptyTypes)); // obj
MethodInfo fnAdd = typeof(IDictionary<string, object>).GetMethod("Add");
MethodInfo fnAdd = typeof(IDictionary<string, object>).GetMethod("Add");
// Enumerate all fields generating a set assignment for the column
for (int i = firstColumn; i < firstColumn + countColumns; i++)
{
var srcType = r.GetFieldType(i);
// Enumerate all fields generating a set assignment for the column
for (int i = firstColumn; i < firstColumn + countColumns; i++)
{
var srcType = r.GetFieldType(i);
il.Emit(OpCodes.Dup); // obj, obj
il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname
il.Emit(OpCodes.Dup); // obj, obj
il.Emit(OpCodes.Ldstr, r.GetName(i)); // obj, obj, fieldname
// Get the converter
Func<object, object> converter = null;
if (Database.Mapper != null)
converter = Database.Mapper.GetFromDbConverter(null, srcType);
if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime))
converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); };
// Get the converter
Func<object, object> converter = null;
if (Database.Mapper != null)
converter = Database.Mapper.GetFromDbConverter(null, srcType);
if (ForceDateTimesToUtc && converter == null && srcType == typeof(DateTime))
converter = delegate(object src) { return new DateTime(((DateTime)src).Ticks, DateTimeKind.Utc); };
// Setup stack for call to converter
AddConverterToStack(il, converter);
// Setup stack for call to converter
AddConverterToStack(il, converter);
// r[i]
il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr
il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i
il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value
// r[i]
il.Emit(OpCodes.Ldarg_0); // obj, obj, fieldname, converter?, rdr
il.Emit(OpCodes.Ldc_I4, i); // obj, obj, fieldname, converter?, rdr,i
il.Emit(OpCodes.Callvirt, fnGetValue); // obj, obj, fieldname, converter?, value
// Convert DBNull to null
il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value
il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null)
var lblNotNull = il.DefineLabel();
il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value
il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter?
if (converter != null)
il.Emit(OpCodes.Pop); // obj, obj, fieldname,
il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null
if (converter != null)
{
var lblReady = il.DefineLabel();
il.Emit(OpCodes.Br_S, lblReady);
il.MarkLabel(lblNotNull);
il.Emit(OpCodes.Callvirt, fnInvoke);
il.MarkLabel(lblReady);
}
else
{
il.MarkLabel(lblNotNull);
}
// Convert DBNull to null
il.Emit(OpCodes.Dup); // obj, obj, fieldname, converter?, value, value
il.Emit(OpCodes.Isinst, typeof(DBNull)); // obj, obj, fieldname, converter?, value, (value or null)
var lblNotNull = il.DefineLabel();
il.Emit(OpCodes.Brfalse_S, lblNotNull); // obj, obj, fieldname, converter?, value
il.Emit(OpCodes.Pop); // obj, obj, fieldname, converter?
if (converter != null)
il.Emit(OpCodes.Pop); // obj, obj, fieldname,
il.Emit(OpCodes.Ldnull); // obj, obj, fieldname, null
if (converter != null)
{
var lblReady = il.DefineLabel();
il.Emit(OpCodes.Br_S, lblReady);
il.MarkLabel(lblNotNull);
il.Emit(OpCodes.Callvirt, fnInvoke);
il.MarkLabel(lblReady);
}
else
{
il.MarkLabel(lblNotNull);
}
il.Emit(OpCodes.Callvirt, fnAdd);
}
}
else
il.Emit(OpCodes.Callvirt, fnAdd);
}
}
else
#endif
if (type.IsValueType || type == typeof(string) || type == typeof(byte[]))
{
// Do we need to install a converter?
var srcType = r.GetFieldType(0);
var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type);
if (type.IsValueType || type == typeof(string) || type == typeof(byte[]))
{
// Do we need to install a converter?
var srcType = r.GetFieldType(0);
var converter = GetConverter(ForceDateTimesToUtc, null, srcType, type);
// "if (!rdr.IsDBNull(i))"
il.Emit(OpCodes.Ldarg_0); // rdr
il.Emit(OpCodes.Ldc_I4_0); // rdr,0
il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool
var lblCont = il.DefineLabel();
il.Emit(OpCodes.Brfalse_S, lblCont);
il.Emit(OpCodes.Ldnull); // null
var lblFin = il.DefineLabel();
il.Emit(OpCodes.Br_S, lblFin);
// "if (!rdr.IsDBNull(i))"
il.Emit(OpCodes.Ldarg_0); // rdr
il.Emit(OpCodes.Ldc_I4_0); // rdr,0
il.Emit(OpCodes.Callvirt, fnIsDBNull); // bool
var lblCont = il.DefineLabel();
il.Emit(OpCodes.Brfalse_S, lblCont);
il.Emit(OpCodes.Ldnull); // null
var lblFin = il.DefineLabel();
il.Emit(OpCodes.Br_S, lblFin);
il.MarkLabel(lblCont);
il.MarkLabel(lblCont);
// Setup stack for call to converter
AddConverterToStack(il, converter);
// Setup stack for call to converter
AddConverterToStack(il, converter);
il.Emit(OpCodes.Ldarg_0); // rdr
il.Emit(OpCodes.Ldc_I4_0); // rdr,0
il.Emit(OpCodes.Callvirt, fnGetValue); // value
il.Emit(OpCodes.Ldarg_0); // rdr
il.Emit(OpCodes.Ldc_I4_0); // rdr,0
il.Emit(OpCodes.Callvirt, fnGetValue); // value
// Call the converter
if (converter != null)
il.Emit(OpCodes.Callvirt, fnInvoke);
// Call the converter
if (converter != null)
il.Emit(OpCodes.Callvirt, fnInvoke);
il.MarkLabel(lblFin);
il.Emit(OpCodes.Unbox_Any, type); // value converted
}
else
{
// var poco=new T()
il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
il.MarkLabel(lblFin);
il.Emit(OpCodes.Unbox_Any, type); // value converted
}
else
{
// var poco=new T()
il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
// Enumerate all fields generating a set assignment for the column
for (int i = firstColumn; i < firstColumn + countColumns; i++)
{
// Get the PocoColumn for this db column, ignore if not known
PocoColumn pc;
if (!Columns.TryGetValue(r.GetName(i), out pc))
continue;
// Enumerate all fields generating a set assignment for the column
for (int i = firstColumn; i < firstColumn + countColumns; i++)
{
// Get the PocoColumn for this db column, ignore if not known
PocoColumn pc;
if (!Columns.TryGetValue(r.GetName(i), out pc))
continue;
// Get the source type for this column
var srcType = r.GetFieldType(i);
var dstType = pc.PropertyInfo.PropertyType;
// Get the source type for this column
var srcType = r.GetFieldType(i);
var dstType = pc.PropertyInfo.PropertyType;
// "if (!rdr.IsDBNull(i))"
il.Emit(OpCodes.Ldarg_0); // poco,rdr
il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i
il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool
var lblNext = il.DefineLabel();
il.Emit(OpCodes.Brtrue_S, lblNext); // poco
// "if (!rdr.IsDBNull(i))"
il.Emit(OpCodes.Ldarg_0); // poco,rdr
il.Emit(OpCodes.Ldc_I4, i); // poco,rdr,i
il.Emit(OpCodes.Callvirt, fnIsDBNull); // poco,bool
var lblNext = il.DefineLabel();
il.Emit(OpCodes.Brtrue_S, lblNext); // poco
il.Emit(OpCodes.Dup); // poco,poco
il.Emit(OpCodes.Dup); // poco,poco
// Do we need to install a converter?
var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType);
// Do we need to install a converter?
var converter = GetConverter(ForceDateTimesToUtc, pc, srcType, dstType);
// Fast
bool Handled = false;
if (converter == null)
{
var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) });
if (valuegetter != null
&& valuegetter.ReturnType == srcType
&& (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType)))
{
il.Emit(OpCodes.Ldarg_0); // *,rdr
il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
il.Emit(OpCodes.Callvirt, valuegetter); // *,value
// Fast
bool Handled = false;
if (converter == null)
{
var valuegetter = typeof(IDataRecord).GetMethod("Get" + srcType.Name, new Type[] { typeof(int) });
if (valuegetter != null
&& valuegetter.ReturnType == srcType
&& (valuegetter.ReturnType == dstType || valuegetter.ReturnType == Nullable.GetUnderlyingType(dstType)))
{
il.Emit(OpCodes.Ldarg_0); // *,rdr
il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
il.Emit(OpCodes.Callvirt, valuegetter); // *,value
// Convert to Nullable
if (Nullable.GetUnderlyingType(dstType) != null)
{
il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) }));
}
// Convert to Nullable
if (Nullable.GetUnderlyingType(dstType) != null)
{
il.Emit(OpCodes.Newobj, dstType.GetConstructor(new Type[] { Nullable.GetUnderlyingType(dstType) }));
}
il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
Handled = true;
}
}
il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
Handled = true;
}
}
// Not so fast
if (!Handled)
{
// Setup stack for call to converter
AddConverterToStack(il, converter);
// Not so fast
if (!Handled)
{
// Setup stack for call to converter
AddConverterToStack(il, converter);
// "value = rdr.GetValue(i)"
il.Emit(OpCodes.Ldarg_0); // *,rdr
il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
il.Emit(OpCodes.Callvirt, fnGetValue); // *,value
// "value = rdr.GetValue(i)"
il.Emit(OpCodes.Ldarg_0); // *,rdr
il.Emit(OpCodes.Ldc_I4, i); // *,rdr,i
il.Emit(OpCodes.Callvirt, fnGetValue); // *,value
// Call the converter
if (converter != null)
il.Emit(OpCodes.Callvirt, fnInvoke);
// Call the converter
if (converter != null)
il.Emit(OpCodes.Callvirt, fnInvoke);
// Assign it
il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value
il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
}
// Assign it
il.Emit(OpCodes.Unbox_Any, pc.PropertyInfo.PropertyType); // poco,poco,value
il.Emit(OpCodes.Callvirt, pc.PropertyInfo.GetSetMethod(true)); // poco
}
il.MarkLabel(lblNext);
}
il.MarkLabel(lblNext);
}
var fnOnLoaded = RecurseInheritedTypes<MethodInfo>(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
if (fnOnLoaded != null)
{
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Callvirt, fnOnLoaded);
}
}
var fnOnLoaded = RecurseInheritedTypes<MethodInfo>(type, (x) => x.GetMethod("OnLoaded", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[0], null));
if (fnOnLoaded != null)
{
il.Emit(OpCodes.Dup);
il.Emit(OpCodes.Callvirt, fnOnLoaded);
}
}
il.Emit(OpCodes.Ret);
il.Emit(OpCodes.Ret);
// return it
var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type));
return del;
};
//lazy usage of AddOrGetExisting ref: http://stackoverflow.com/questions/10559279/how-to-deal-with-costly-building-operations-using-memorycache/15894928#15894928
var newValue = new Lazy<Delegate>(factory);
// the line belows returns existing item or adds the new value if it doesn't exist
var value = (Lazy<Delegate>)objectCache.AddOrGetExisting(key, newValue, new CacheItemPolicy
{
//sliding expiration of 1 hr, if the same key isn't used in this
// timeframe it will be removed from the cache
SlidingExpiration = new TimeSpan(0, 0, SlidingExpirationSeconds)
});
return (value ?? newValue).Value; // Lazy<T> handles the locking itself
// Cache it, return it
var del = m.CreateDelegate(Expression.GetFuncType(typeof(IDataReader), type));
PocoFactories.Add(key, del);
return del;
}
finally
{
RWLock.ExitWriteLock();
}
}
private static void AddConverterToStack(ILGenerator il, Func<object, object> converter)
@@ -2144,7 +2182,7 @@ namespace Umbraco.Core.Persistence
return default(T);
}
ManagedCache _managedCache = new ManagedCache();
static Dictionary<Type, PocoData> m_PocoDatas = new Dictionary<Type, PocoData>();
static List<Func<object, object>> m_Converters = new List<Func<object, object>>();
static MethodInfo fnGetValue = typeof(IDataRecord).GetMethod("GetValue", new Type[] { typeof(int) });
@@ -2156,7 +2194,46 @@ namespace Umbraco.Core.Persistence
public string[] QueryColumns { get; private set; }
public TableInfo TableInfo { get; private set; }
public Dictionary<string, PocoColumn> Columns { get; private set; }
Dictionary<string, Delegate> PocoFactories = new Dictionary<string, Delegate>();
static System.Threading.ReaderWriterLockSlim InnerLock = new System.Threading.ReaderWriterLockSlim();
/// <summary>
/// Returns a report of the current cache being utilized by PetaPoco
/// </summary>
/// <returns></returns>
public static string PrintDebugCacheReport(out double totalBytes, out IEnumerable<string> allKeys)
{
var managedCache = new ManagedCache();
var sb = new StringBuilder();
sb.AppendLine("m_PocoDatas:");
foreach (var pocoData in m_PocoDatas)
{
sb.AppendFormat("\t{0}\n", pocoData.Key);
sb.AppendFormat("\t\tTable:{0} - Col count:{1}\n", pocoData.Value.TableInfo.TableName, pocoData.Value.QueryColumns.Length);
}
var cache = managedCache.GetCache();
allKeys = cache.Select(x => x.Key).ToArray();
sb.AppendFormat("\tTotal Poco data count:{0}\n", allKeys.Count());
var keys = string.Join("", cache.Select(x => x.Key));
//Bytes in .Net are stored as utf-16 = unicode little endian
totalBytes = Encoding.Unicode.GetByteCount(keys);
sb.AppendFormat("\tTotal byte for keys:{0}\n", totalBytes);
sb.AppendLine("\tAll Poco cache items:");
foreach (var item in cache)
{
sb.AppendFormat("\t\t Key -> {0}\n", item.Key);
sb.AppendFormat("\t\t Value -> {0}\n", item.Value);
}
sb.AppendLine("-------------------END REPORT------------------------");
return sb.ToString();
}
}
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core.Logging;
@@ -18,6 +20,95 @@ namespace Umbraco.Core.Persistence
internal static event CreateTableEventHandler NewTable;
/// <summary>
/// This will handle the issue of inserting data into a table when there can be a violation of a primary key or unique constraint which
/// can occur when two threads are trying to insert data at the exact same time when the data violates this constraint.
/// </summary>
/// <param name="db"></param>
/// <param name="poco"></param>
/// <returns>
/// Returns the action that executed, either an insert or an update
///
/// NOTE: If an insert occurred and a PK value got generated, the poco object passed in will contain the updated value.
/// </returns>
/// <remarks>
/// In different databases, there are a few raw SQL options like MySql's ON DUPLICATE KEY UPDATE or MSSQL's MERGE WHEN MATCHED, but since we are
/// also supporting SQLCE for which this doesn't exist we cannot simply rely on the underlying database to help us here. So we'll actually need to
/// try to be as proficient as possible when we know this can occur and manually handle the issue.
///
/// We do this by first trying to Update the record, this will return the number of rows affected. If it is zero then we insert, if it is one, then
/// we know the update was successful and the row was already inserted by another thread. If the rowcount is zero and we insert and get an exception,
/// that's due to a race condition, in which case we need to retry and update.
/// </remarks>
internal static RecordPersistenceType InsertOrUpdate<T>(this Database db, T poco)
where T : class
{
return db.InsertOrUpdate(poco, null, null);
}
/// <summary>
/// This will handle the issue of inserting data into a table when there can be a violation of a primary key or unique constraint which
/// can occur when two threads are trying to insert data at the exact same time when the data violates this constraint.
/// </summary>
/// <param name="db"></param>
/// <param name="poco"></param>
/// <param name="updateArgs"></param>
/// <param name="updateCommand">If the entity has a composite key they you need to specify the update command explicitly</param>
/// <returns>
/// Returns the action that executed, either an insert or an update
///
/// NOTE: If an insert occurred and a PK value got generated, the poco object passed in will contain the updated value.
/// </returns>
/// <remarks>
/// In different databases, there are a few raw SQL options like MySql's ON DUPLICATE KEY UPDATE or MSSQL's MERGE WHEN MATCHED, but since we are
/// also supporting SQLCE for which this doesn't exist we cannot simply rely on the underlying database to help us here. So we'll actually need to
/// try to be as proficient as possible when we know this can occur and manually handle the issue.
///
/// We do this by first trying to Update the record, this will return the number of rows affected. If it is zero then we insert, if it is one, then
/// we know the update was successful and the row was already inserted by another thread. If the rowcount is zero and we insert and get an exception,
/// that's due to a race condition, in which case we need to retry and update.
/// </remarks>
internal static RecordPersistenceType InsertOrUpdate<T>(this Database db,
T poco,
string updateCommand,
object updateArgs)
where T : class
{
if (poco == null) throw new ArgumentNullException("poco");
var rowCount = updateCommand.IsNullOrWhiteSpace()
? db.Update(poco)
: db.Update<T>(updateCommand, updateArgs);
if (rowCount > 0) return RecordPersistenceType.Update;
try
{
db.Insert(poco);
return RecordPersistenceType.Insert;
}
//TODO: Need to find out if this is the same exception that will occur for all databases... pretty sure it will be
catch (SqlException ex)
{
//This will occur if the constraint was violated and this record was already inserted by another thread,
//at this exact same time, in this case we need to do an update
rowCount = updateCommand.IsNullOrWhiteSpace()
? db.Update(poco)
: db.Update<T>(updateCommand, updateArgs);
if (rowCount == 0)
{
//this would be strange! in this case the only circumstance would be that at the exact same time, 3 threads executed, one
// did the insert and the other somehow managed to do a delete precisely before this update was executed... now that would
// be real crazy. In that case we need to throw an exception.
throw new DataException("Record could not be inserted or updated");
}
return RecordPersistenceType.Update;
}
}
/// <summary>
/// This will escape single @ symbols for peta poco values so it doesn't think it's a parameter
/// </summary>
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence
var expresionist = new PocoToSqlExpressionHelper<T>();
string whereExpression = expresionist.Visit(predicate);
return sql.Where(whereExpression);
return sql.Where(whereExpression, expresionist.GetSqlParameters());
}
public static Sql OrderBy<TColumn>(this Sql sql, Expression<Func<TColumn, object>> columnMember)
@@ -1,28 +1,562 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal abstract class BaseExpressionHelper<T> : BaseExpressionHelper
{
protected abstract string VisitMemberAccess(MemberExpression m);
protected internal virtual string Visit(Expression exp)
{
if (exp == null) return string.Empty;
switch (exp.NodeType)
{
case ExpressionType.Lambda:
return VisitLambda(exp as LambdaExpression);
case ExpressionType.MemberAccess:
return VisitMemberAccess(exp as MemberExpression);
case ExpressionType.Constant:
return VisitConstant(exp as ConstantExpression);
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.Divide:
case ExpressionType.Modulo:
case ExpressionType.And:
case ExpressionType.AndAlso:
case ExpressionType.Or:
case ExpressionType.OrElse:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.Coalesce:
case ExpressionType.ArrayIndex:
case ExpressionType.RightShift:
case ExpressionType.LeftShift:
case ExpressionType.ExclusiveOr:
return VisitBinary(exp as BinaryExpression);
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.Not:
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.ArrayLength:
case ExpressionType.Quote:
case ExpressionType.TypeAs:
return VisitUnary(exp as UnaryExpression);
case ExpressionType.Parameter:
return VisitParameter(exp as ParameterExpression);
case ExpressionType.Call:
return VisitMethodCall(exp as MethodCallExpression);
case ExpressionType.New:
return VisitNew(exp as NewExpression);
case ExpressionType.NewArrayInit:
case ExpressionType.NewArrayBounds:
return VisitNewArray(exp as NewArrayExpression);
default:
return exp.ToString();
}
}
protected virtual string VisitLambda(LambdaExpression lambda)
{
if (lambda.Body.NodeType == ExpressionType.MemberAccess)
{
var m = lambda.Body as MemberExpression;
if (m.Expression != null)
{
//This deals with members that are boolean (i.e. x => IsTrashed )
string r = VisitMemberAccess(m);
SqlParameters.Add(true);
return string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
//return string.Format("{0}={1}", r, GetQuotedTrueValue());
}
}
return Visit(lambda.Body);
}
protected virtual string VisitBinary(BinaryExpression b)
{
string left, right;
var operand = BindOperant(b.NodeType);
if (operand == "AND" || operand == "OR")
{
MemberExpression m = b.Left as MemberExpression;
if (m != null && m.Expression != null)
{
string r = VisitMemberAccess(m);
SqlParameters.Add(1);
left = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
//left = string.Format("{0}={1}", r, GetQuotedTrueValue());
}
else
{
left = Visit(b.Left);
}
m = b.Right as MemberExpression;
if (m != null && m.Expression != null)
{
string r = VisitMemberAccess(m);
SqlParameters.Add(1);
right = string.Format("{0} = @{1}", r, SqlParameters.Count - 1);
//right = string.Format("{0}={1}", r, GetQuotedTrueValue());
}
else
{
right = Visit(b.Right);
}
}
else
{
left = Visit(b.Left);
right = Visit(b.Right);
}
if (operand == "=" && right == "null") operand = "is";
else if (operand == "<>" && right == "null") operand = "is not";
else if (operand == "=" || operand == "<>")
{
//if (IsTrueExpression(right)) right = GetQuotedTrueValue();
//else if (IsFalseExpression(right)) right = GetQuotedFalseValue();
//if (IsTrueExpression(left)) left = GetQuotedTrueValue();
//else if (IsFalseExpression(left)) left = GetQuotedFalseValue();
}
switch (operand)
{
case "MOD":
case "COALESCE":
return string.Format("{0}({1},{2})", operand, left, right);
default:
return left + " " + operand + " " + right;
}
}
protected virtual List<Object> VisitExpressionList(ReadOnlyCollection<Expression> original)
{
var list = new List<Object>();
for (int i = 0, n = original.Count; i < n; i++)
{
if (original[i].NodeType == ExpressionType.NewArrayInit ||
original[i].NodeType == ExpressionType.NewArrayBounds)
{
list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression));
}
else
list.Add(Visit(original[i]));
}
return list;
}
protected virtual string VisitNew(NewExpression nex)
{
// TODO : check !
var member = Expression.Convert(nex, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
try
{
var getter = lambda.Compile();
object o = getter();
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o.GetType());
}
catch (InvalidOperationException)
{
// FieldName ?
List<Object> exprs = VisitExpressionList(nex.Arguments);
var r = new StringBuilder();
foreach (Object e in exprs)
{
r.AppendFormat("{0}{1}",
r.Length > 0 ? "," : "",
e);
}
return r.ToString();
}
}
protected virtual string VisitParameter(ParameterExpression p)
{
return p.Name;
}
protected virtual string VisitConstant(ConstantExpression c)
{
if (c.Value == null)
return "null";
SqlParameters.Add(c.Value);
return string.Format("@{0}", SqlParameters.Count - 1);
//if (c.Value is bool)
//{
// object o = GetQuotedValue(c.Value, c.Value.GetType());
// return string.Format("({0}={1})", GetQuotedTrueValue(), o);
//}
//return GetQuotedValue(c.Value, c.Value.GetType());
}
protected virtual string VisitUnary(UnaryExpression u)
{
switch (u.NodeType)
{
case ExpressionType.Not:
var o = Visit(u.Operand);
//use a Not equal operator instead of <> since we don't know that <> works in all sql servers
switch (u.Operand.NodeType)
{
case ExpressionType.MemberAccess:
//In this case it wil be a false property , i.e. x => !Trashed
SqlParameters.Add(true);
return string.Format("NOT ({0} = @0)", o);
default:
//In this case it could be anything else, such as: x => !x.Path.StartsWith("-20")
return string.Format("NOT ({0})", o);
}
default:
return Visit(u.Operand);
}
}
protected virtual string VisitNewArray(NewArrayExpression na)
{
List<Object> exprs = VisitExpressionList(na.Expressions);
var r = new StringBuilder();
foreach (Object e in exprs)
{
r.Append(r.Length > 0 ? "," + e : e);
}
return r.ToString();
}
protected virtual List<Object> VisitNewArrayFromExpressionList(NewArrayExpression na)
{
List<Object> exprs = VisitExpressionList(na.Expressions);
return exprs;
}
protected virtual string BindOperant(ExpressionType e)
{
switch (e)
{
case ExpressionType.Equal:
return "=";
case ExpressionType.NotEqual:
return "<>";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case ExpressionType.AndAlso:
return "AND";
case ExpressionType.OrElse:
return "OR";
case ExpressionType.Add:
return "+";
case ExpressionType.Subtract:
return "-";
case ExpressionType.Multiply:
return "*";
case ExpressionType.Divide:
return "/";
case ExpressionType.Modulo:
return "MOD";
case ExpressionType.Coalesce:
return "COALESCE";
default:
return e.ToString();
}
}
protected virtual string VisitMethodCall(MethodCallExpression m)
{
//Here's what happens with a MethodCallExpression:
// If a method is called that contains a single argument,
// then m.Object is the object on the left hand side of the method call, example:
// x.Path.StartsWith(content.Path)
// m.Object = x.Path
// and m.Arguments.Length == 1, therefor m.Arguments[0] == content.Path
// If a method is called that contains multiple arguments, then m.Object == null and the
// m.Arguments collection contains the left hand side of the method call, example:
// x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar)
// m.Object == null
// m.Arguments.Length == 3, therefor, m.Arguments[0] == x.Path, m.Arguments[1] == content.Path, m.Arguments[2] == TextColumnType.NVarchar
// So, we need to cater for these scenarios.
var objectForMethod = m.Object ?? m.Arguments[0];
var visitedObjectForMethod = Visit(objectForMethod);
var methodArgs = m.Object == null
? m.Arguments.Skip(1).ToArray()
: m.Arguments.ToArray();
switch (m.Method.Name)
{
case "ToString":
SqlParameters.Add(objectForMethod.ToString());
return string.Format("@{0}", SqlParameters.Count - 1);
case "ToUpper":
return string.Format("upper({0})", visitedObjectForMethod);
case "ToLower":
return string.Format("lower({0})", visitedObjectForMethod);
case "SqlWildcard":
case "StartsWith":
case "EndsWith":
case "Contains":
case "Equals":
case "SqlStartsWith":
case "SqlEndsWith":
case "SqlContains":
case "SqlEquals":
case "InvariantStartsWith":
case "InvariantEndsWith":
case "InvariantContains":
case "InvariantEquals":
string compareValue;
if (methodArgs[0].NodeType != ExpressionType.Constant)
{
//This occurs when we are getting a value from a non constant such as: x => x.Path.StartsWith(content.Path)
// So we'll go get the value:
var member = Expression.Convert(methodArgs[0], typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
compareValue = getter().ToString();
}
else
{
compareValue = methodArgs[0].ToString();
}
//special case, if it is 'Contains' and the member that Contains is being called on is not a string, then
// we should be doing an 'In' clause - but we currently do not support this
if (methodArgs[0].Type != typeof(string) && TypeHelper.IsTypeAssignableFrom<IEnumerable>(methodArgs[0].Type))
{
throw new NotSupportedException("An array Contains method is not supported");
}
//default column type
var colType = TextColumnType.NVarchar;
//then check if the col type argument has been passed to the current method (this will be the case for methods like
// SqlContains and other Sql methods)
if (methodArgs.Length > 1)
{
var colTypeArg = methodArgs.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType));
if (colTypeArg != null)
{
colType = (TextColumnType)((ConstantExpression)colTypeArg).Value;
}
}
return HandleStringComparison(visitedObjectForMethod, compareValue, m.Method.Name, colType);
//case "Substring":
// var startIndex = Int32.Parse(args[0].ToString()) + 1;
// if (args.Count == 2)
// {
// var length = Int32.Parse(args[1].ToString());
// return string.Format("substring({0} from {1} for {2})",
// r,
// startIndex,
// length);
// }
// else
// return string.Format("substring({0} from {1})",
// r,
// startIndex);
//case "Round":
//case "Floor":
//case "Ceiling":
//case "Coalesce":
//case "Abs":
//case "Sum":
// return string.Format("{0}({1}{2})",
// m.Method.Name,
// r,
// args.Count == 1 ? string.Format(",{0}", args[0]) : "");
//case "Concat":
// var s = new StringBuilder();
// foreach (Object e in args)
// {
// s.AppendFormat(" || {0}", e);
// }
// return string.Format("{0}{1}", r, s);
//case "In":
// var member = Expression.Convert(m.Arguments[0], typeof(object));
// var lambda = Expression.Lambda<Func<object>>(member);
// var getter = lambda.Compile();
// var inArgs = (object[])getter();
// var sIn = new StringBuilder();
// foreach (var e in inArgs)
// {
// SqlParameters.Add(e);
// sIn.AppendFormat("{0}{1}",
// sIn.Length > 0 ? "," : "",
// string.Format("@{0}", SqlParameters.Count - 1));
// //sIn.AppendFormat("{0}{1}",
// // sIn.Length > 0 ? "," : "",
// // GetQuotedValue(e, e.GetType()));
// }
// return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString());
//case "Desc":
// return string.Format("{0} DESC", r);
//case "Alias":
//case "As":
// return string.Format("{0} As {1}", r,
// GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString()))));
default:
throw new ArgumentOutOfRangeException("No logic supported for " + m.Method.Name);
//var s2 = new StringBuilder();
//foreach (Object e in args)
//{
// s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType()));
//}
//return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString());
}
}
public virtual string GetQuotedTableName(string tableName)
{
return string.Format("\"{0}\"", tableName);
}
public virtual string GetQuotedColumnName(string columnName)
{
return string.Format("\"{0}\"", columnName);
}
public virtual string GetQuotedName(string name)
{
return string.Format("\"{0}\"", name);
}
//private string GetQuotedTrueValue()
//{
// return GetQuotedValue(true, typeof(bool));
//}
//private string GetQuotedFalseValue()
//{
// return GetQuotedValue(false, typeof(bool));
//}
//public virtual string GetQuotedValue(object value, Type fieldType)
//{
// return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue);
//}
//private string GetTrueExpression()
//{
// object o = GetQuotedTrueValue();
// return string.Format("({0}={1})", o, o);
//}
//private string GetFalseExpression()
//{
// return string.Format("({0}={1})",
// GetQuotedTrueValue(),
// GetQuotedFalseValue());
//}
//private bool IsTrueExpression(string exp)
//{
// return (exp == GetTrueExpression());
//}
//private bool IsFalseExpression(string exp)
//{
// return (exp == GetFalseExpression());
//}
}
/// <summary>
/// Logic that is shared with the expression helpers
/// </summary>
internal class BaseExpressionHelper
internal class BaseExpressionHelper
{
protected List<object> SqlParameters = new List<object>();
public object[] GetSqlParameters()
{
return SqlParameters.ToArray();
}
protected string HandleStringComparison(string col, string val, string verb, TextColumnType columnType)
{
switch (verb)
{
case "SqlWildcard":
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, RemoveQuote(val), columnType);
SqlParameters.Add(RemoveQuote(val));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
case "Equals":
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, RemoveQuote(val), columnType);
SqlParameters.Add(RemoveQuote(val));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEqualComparison(col, SqlParameters.Count - 1, columnType);
case "StartsWith":
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnStartsWithComparison(col, RemoveQuote(val), columnType);
SqlParameters.Add(string.Format("{0}{1}",
RemoveQuote(val),
SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder()));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
case "EndsWith":
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnEndsWithComparison(col, RemoveQuote(val), columnType);
SqlParameters.Add(string.Format("{0}{1}",
SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(),
RemoveQuote(val)));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
case "Contains":
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnContainsComparison(col, RemoveQuote(val), columnType);
SqlParameters.Add(string.Format("{0}{1}{0}",
SqlSyntaxContext.SqlSyntaxProvider.GetWildcardPlaceholder(),
RemoveQuote(val)));
return SqlSyntaxContext.SqlSyntaxProvider.GetStringColumnWildcardComparison(col, SqlParameters.Count - 1, columnType);
case "InvariantEquals":
case "SqlEquals":
//recurse
@@ -44,61 +578,54 @@ namespace Umbraco.Core.Persistence.Querying
}
}
public virtual string GetQuotedValue(object value, Type fieldType, Func<object, string> escapeCallback = null, Func<Type, bool> shouldQuoteCallback = null)
{
if (value == null) return "NULL";
//public virtual string GetQuotedValue(object value, Type fieldType, Func<object, string> escapeCallback = null, Func<Type, bool> shouldQuoteCallback = null)
//{
// if (value == null) return "NULL";
if (escapeCallback == null)
{
escapeCallback = EscapeParam;
}
if (shouldQuoteCallback == null)
{
shouldQuoteCallback = ShouldQuoteValue;
}
// if (escapeCallback == null)
// {
// escapeCallback = EscapeParam;
// }
// if (shouldQuoteCallback == null)
// {
// shouldQuoteCallback = ShouldQuoteValue;
// }
if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string))
{
//if (TypeSerializer.CanCreateFromString(fieldType))
//{
// return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'";
//}
// if (!fieldType.UnderlyingSystemType.IsValueType && fieldType != typeof(string))
// {
// //if (TypeSerializer.CanCreateFromString(fieldType))
// //{
// // return "'" + escapeCallback(TypeSerializer.SerializeToString(value)) + "'";
// //}
throw new NotSupportedException(
string.Format("Property of type: {0} is not supported", fieldType.FullName));
}
// throw new NotSupportedException(
// string.Format("Property of type: {0} is not supported", fieldType.FullName));
// }
if (fieldType == typeof(int))
return ((int)value).ToString(CultureInfo.InvariantCulture);
// if (fieldType == typeof(int))
// return ((int)value).ToString(CultureInfo.InvariantCulture);
if (fieldType == typeof(float))
return ((float)value).ToString(CultureInfo.InvariantCulture);
// if (fieldType == typeof(float))
// return ((float)value).ToString(CultureInfo.InvariantCulture);
if (fieldType == typeof(double))
return ((double)value).ToString(CultureInfo.InvariantCulture);
// if (fieldType == typeof(double))
// return ((double)value).ToString(CultureInfo.InvariantCulture);
if (fieldType == typeof(decimal))
return ((decimal)value).ToString(CultureInfo.InvariantCulture);
// if (fieldType == typeof(decimal))
// return ((decimal)value).ToString(CultureInfo.InvariantCulture);
if (fieldType == typeof(DateTime))
{
return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'";
}
// if (fieldType == typeof(DateTime))
// {
// return "'" + escapeCallback(((DateTime)value).ToIsoString()) + "'";
// }
if (fieldType == typeof(bool))
return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture);
// if (fieldType == typeof(bool))
// return ((bool)value) ? Convert.ToString(1, CultureInfo.InvariantCulture) : Convert.ToString(0, CultureInfo.InvariantCulture);
return shouldQuoteCallback(fieldType)
? "'" + escapeCallback(value) + "'"
: value.ToString();
}
public virtual string EscapeParam(object paramValue)
{
return paramValue == null
? string.Empty
: SqlSyntaxContext.SqlSyntaxProvider.EscapeString(paramValue.ToString());
}
// return shouldQuoteCallback(fieldType)
// ? "'" + escapeCallback(value) + "'"
// : value.ToString();
//}
public virtual bool ShouldQuoteValue(Type fieldType)
{
@@ -107,16 +634,12 @@ namespace Umbraco.Core.Persistence.Querying
protected virtual string RemoveQuote(string exp)
{
if (exp.StartsWith("'") && exp.EndsWith("'"))
{
exp = exp.Remove(0, 1);
exp = exp.Remove(exp.Length - 1, 1);
}
return exp;
}
protected virtual string RemoveQuoteFromAlias(string exp)
{
//if (exp.StartsWith("'") && exp.EndsWith("'"))
//{
// exp = exp.Remove(0, 1);
// exp = exp.Remove(exp.Length - 1, 1);
//}
//return exp;
if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'"))
&&
@@ -127,5 +650,18 @@ namespace Umbraco.Core.Persistence.Querying
}
return exp;
}
//protected virtual string RemoveQuoteFromAlias(string exp)
//{
// if ((exp.StartsWith("\"") || exp.StartsWith("`") || exp.StartsWith("'"))
// &&
// (exp.EndsWith("\"") || exp.EndsWith("`") || exp.EndsWith("'")))
// {
// exp = exp.Remove(0, 1);
// exp = exp.Remove(exp.Length - 1, 1);
// }
// return exp;
//}
}
}
@@ -1,10 +1,44 @@
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>
/// <typeparam name="T"></typeparam>
public interface IQuery<T>
{
/// <summary>
/// Adds a where clause to the query
/// </summary>
/// <param name="predicate"></param>
/// <returns>This instance so calls to this method are chainable</returns>
IQuery<T> Where(Expression<Func<T, bool>> predicate);
}
}
@@ -10,149 +10,21 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal class ModelToSqlExpressionHelper<T> : BaseExpressionHelper
internal class ModelToSqlExpressionHelper<T> : BaseExpressionHelper<T>
{
private string sep = " ";
private BaseMapper _mapper;
private readonly BaseMapper _mapper;
public ModelToSqlExpressionHelper()
{
_mapper = MappingResolver.Current.ResolveMapperByType(typeof(T));
}
protected internal virtual string Visit(Expression exp)
protected override string VisitMemberAccess(MemberExpression m)
{
if (exp == null) return string.Empty;
switch (exp.NodeType)
{
case ExpressionType.Lambda:
return VisitLambda(exp as LambdaExpression);
case ExpressionType.MemberAccess:
return VisitMemberAccess(exp as MemberExpression);
case ExpressionType.Constant:
return VisitConstant(exp as ConstantExpression);
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.Divide:
case ExpressionType.Modulo:
case ExpressionType.And:
case ExpressionType.AndAlso:
case ExpressionType.Or:
case ExpressionType.OrElse:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.Coalesce:
case ExpressionType.ArrayIndex:
case ExpressionType.RightShift:
case ExpressionType.LeftShift:
case ExpressionType.ExclusiveOr:
return VisitBinary(exp as BinaryExpression);
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.Not:
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.ArrayLength:
case ExpressionType.Quote:
case ExpressionType.TypeAs:
return VisitUnary(exp as UnaryExpression);
case ExpressionType.Parameter:
return VisitParameter(exp as ParameterExpression);
case ExpressionType.Call:
return VisitMethodCall(exp as MethodCallExpression);
case ExpressionType.New:
return VisitNew(exp as NewExpression);
case ExpressionType.NewArrayInit:
case ExpressionType.NewArrayBounds:
return VisitNewArray(exp as NewArrayExpression);
default:
return exp.ToString();
}
}
protected virtual string VisitLambda(LambdaExpression lambda)
{
if (lambda.Body.NodeType == ExpressionType.MemberAccess && sep == " ")
{
MemberExpression m = lambda.Body as MemberExpression;
if (m.Expression != null)
{
string r = VisitMemberAccess(m);
return string.Format("{0}={1}", r, GetQuotedTrueValue());
}
}
return Visit(lambda.Body);
}
protected virtual string VisitBinary(BinaryExpression b)
{
string left, right;
var operand = BindOperant(b.NodeType); //sep= " " ??
if (operand == "AND" || operand == "OR")
{
MemberExpression m = b.Left as MemberExpression;
if (m != null && m.Expression != null)
{
string r = VisitMemberAccess(m);
left = string.Format("{0}={1}", r, GetQuotedTrueValue());
}
else
{
left = Visit(b.Left);
}
m = b.Right as MemberExpression;
if (m != null && m.Expression != null)
{
string r = VisitMemberAccess(m);
right = string.Format("{0}={1}", r, GetQuotedTrueValue());
}
else
{
right = Visit(b.Right);
}
}
else
{
left = Visit(b.Left);
right = Visit(b.Right);
}
if (operand == "=" && right == "null") operand = "is";
else if (operand == "<>" && right == "null") operand = "is not";
else if (operand == "=" || operand == "<>")
{
if (IsTrueExpression(right)) right = GetQuotedTrueValue();
else if (IsFalseExpression(right)) right = GetQuotedFalseValue();
if (IsTrueExpression(left)) left = GetQuotedTrueValue();
else if (IsFalseExpression(left)) left = GetQuotedFalseValue();
}
switch (operand)
{
case "MOD":
case "COALESCE":
return string.Format("{0}({1},{2})", operand, left, right);
default:
return left + sep + operand + sep + right;
}
}
protected virtual string VisitMemberAccess(MemberExpression m)
{
if (m.Expression != null && m.Expression.NodeType == ExpressionType.Parameter && m.Expression.Type == typeof(T))
if (m.Expression != null &&
m.Expression.NodeType == ExpressionType.Parameter
&& m.Expression.Type == typeof(T))
{
var field = _mapper.Map(m.Member.Name);
return field;
@@ -168,324 +40,18 @@ namespace Umbraco.Core.Persistence.Querying
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
object o = getter();
return GetQuotedValue(o, o != null ? o.GetType() : null);
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o != null ? o.GetType() : null);
}
protected virtual string VisitNew(NewExpression nex)
{
// TODO : check !
var member = Expression.Convert(nex, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
try
{
var getter = lambda.Compile();
object o = getter();
return GetQuotedValue(o, o.GetType());
}
catch (System.InvalidOperationException)
{ // FieldName ?
List<Object> exprs = VisitExpressionList(nex.Arguments);
var r = new StringBuilder();
foreach (Object e in exprs)
{
r.AppendFormat("{0}{1}", r.Length > 0 ? "," : "", e);
}
return r.ToString();
}
}
protected virtual string VisitParameter(ParameterExpression p)
{
return p.Name;
}
protected virtual string VisitConstant(ConstantExpression c)
{
if (c.Value == null)
return "null";
if (c.Value is bool)
{
object o = GetQuotedValue(c.Value, c.Value.GetType());
return string.Format("({0}={1})", GetQuotedTrueValue(), o);
}
return GetQuotedValue(c.Value, c.Value.GetType());
}
protected virtual string VisitUnary(UnaryExpression u)
{
switch (u.NodeType)
{
case ExpressionType.Not:
string o = Visit(u.Operand);
if (IsFieldName(o)) o = o + "=" + GetQuotedValue(true, typeof(bool));
return "NOT (" + o + ")";
default:
return Visit(u.Operand);
}
}
protected virtual string VisitMethodCall(MethodCallExpression m)
{
List<Object> args = this.VisitExpressionList(m.Arguments);
Object r;
if (m.Object != null)
r = Visit(m.Object);
else
{
r = args[0];
args.RemoveAt(0);
}
switch (m.Method.Name)
{
case "ToUpper":
return string.Format("upper({0})", r);
case "ToLower":
return string.Format("lower({0})", r);
case "SqlWildcard":
case "StartsWith":
case "EndsWith":
case "Contains":
case "Equals":
case "SqlStartsWith":
case "SqlEndsWith":
case "SqlContains":
case "SqlEquals":
case "InvariantStartsWith":
case "InvariantEndsWith":
case "InvariantContains":
case "InvariantEquals":
//default
var colType = TextColumnType.NVarchar;
//then check if this arg has been passed in
if (m.Arguments.Count > 1)
{
var colTypeArg = m.Arguments.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType));
if (colTypeArg != null)
{
colType = (TextColumnType) ((ConstantExpression) colTypeArg).Value;
}
}
return HandleStringComparison(r.ToString(), args[0].ToString(), m.Method.Name, colType);
case "Substring":
var startIndex = Int32.Parse(args[0].ToString()) + 1;
if (args.Count == 2)
{
var length = Int32.Parse(args[1].ToString());
return string.Format("substring({0} from {1} for {2})",
r,
startIndex,
length);
}
else
return string.Format("substring({0} from {1})",
r,
startIndex);
case "Round":
case "Floor":
case "Ceiling":
case "Coalesce":
case "Abs":
case "Sum":
return string.Format("{0}({1}{2})",
m.Method.Name,
r,
args.Count == 1 ? string.Format(",{0}", args[0]) : "");
case "Concat":
var s = new StringBuilder();
foreach (Object e in args)
{
s.AppendFormat(" || {0}", e);
}
return string.Format("{0}{1}", r, s.ToString());
case "In":
var member = Expression.Convert(m.Arguments[1], typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
var inArgs = getter() as object[];
var sIn = new StringBuilder();
foreach (Object e in inArgs)
{
if (e.GetType().ToString() != "System.Collections.Generic.List`1[System.Object]")
{
sIn.AppendFormat("{0}{1}",
sIn.Length > 0 ? "," : "",
GetQuotedValue(e, e.GetType()));
}
else
{
var listArgs = e as IList<Object>;
foreach (Object el in listArgs)
{
sIn.AppendFormat("{0}{1}",
sIn.Length > 0 ? "," : "",
GetQuotedValue(el, el.GetType()));
}
}
}
return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString());
case "Desc":
return string.Format("{0} DESC", r);
case "Alias":
case "As":
return string.Format("{0} As {1}", r,
GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString()))));
case "ToString":
return r.ToString();
default:
var s2 = new StringBuilder();
foreach (Object e in args)
{
s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType()));
}
return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString());
}
}
protected virtual List<Object> VisitExpressionList(ReadOnlyCollection<Expression> original)
{
var list = new List<Object>();
for (int i = 0, n = original.Count; i < n; i++)
{
if (original[i].NodeType == ExpressionType.NewArrayInit ||
original[i].NodeType == ExpressionType.NewArrayBounds)
{
list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression));
}
else
list.Add(Visit(original[i]));
}
return list;
}
protected virtual string VisitNewArray(NewArrayExpression na)
{
List<Object> exprs = VisitExpressionList(na.Expressions);
var r = new StringBuilder();
foreach (Object e in exprs)
{
r.Append(r.Length > 0 ? "," + e : e);
}
return r.ToString();
}
protected virtual List<Object> VisitNewArrayFromExpressionList(NewArrayExpression na)
{
List<Object> exprs = VisitExpressionList(na.Expressions);
return exprs;
}
protected virtual string BindOperant(ExpressionType e)
{
switch (e)
{
case ExpressionType.Equal:
return "=";
case ExpressionType.NotEqual:
return "<>";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case ExpressionType.AndAlso:
return "AND";
case ExpressionType.OrElse:
return "OR";
case ExpressionType.Add:
return "+";
case ExpressionType.Subtract:
return "-";
case ExpressionType.Multiply:
return "*";
case ExpressionType.Divide:
return "/";
case ExpressionType.Modulo:
return "MOD";
case ExpressionType.Coalesce:
return "COALESCE";
default:
return e.ToString();
}
}
public virtual string GetQuotedTableName(string tableName)
{
return string.Format("\"{0}\"", tableName);
}
public virtual string GetQuotedColumnName(string columnName)
{
return string.Format("\"{0}\"", columnName);
}
public virtual string GetQuotedName(string name)
{
return string.Format("\"{0}\"", name);
}
private string GetQuotedTrueValue()
{
return GetQuotedValue(true, typeof(bool));
}
private string GetQuotedFalseValue()
{
return GetQuotedValue(false, typeof(bool));
}
public virtual string GetQuotedValue(object value, Type fieldType)
{
return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue);
}
private string GetTrueExpression()
{
object o = GetQuotedTrueValue();
return string.Format("({0}={1})", o, o);
}
private string GetFalseExpression()
{
return string.Format("({0}={1})",
GetQuotedTrueValue(),
GetQuotedFalseValue());
}
private bool IsTrueExpression(string exp)
{
return (exp == GetTrueExpression());
}
private bool IsFalseExpression(string exp)
{
return (exp == GetFalseExpression());
}
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;
}
//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;
//}
}
}
@@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@@ -8,159 +9,28 @@ using Umbraco.Core.Persistence.SqlSyntax;
namespace Umbraco.Core.Persistence.Querying
{
internal class PocoToSqlExpressionHelper<T> : BaseExpressionHelper
internal class PocoToSqlExpressionHelper<T> : BaseExpressionHelper<T>
{
private string sep = " ";
private Database.PocoData pd;
private readonly Database.PocoData _pd;
public PocoToSqlExpressionHelper()
{
pd = new Database.PocoData(typeof(T));
_pd = new Database.PocoData(typeof(T));
}
protected internal virtual string Visit(Expression exp)
{
if (exp == null) return string.Empty;
switch (exp.NodeType)
{
case ExpressionType.Lambda:
return VisitLambda(exp as LambdaExpression);
case ExpressionType.MemberAccess:
return VisitMemberAccess(exp as MemberExpression);
case ExpressionType.Constant:
return VisitConstant(exp as ConstantExpression);
case ExpressionType.Add:
case ExpressionType.AddChecked:
case ExpressionType.Subtract:
case ExpressionType.SubtractChecked:
case ExpressionType.Multiply:
case ExpressionType.MultiplyChecked:
case ExpressionType.Divide:
case ExpressionType.Modulo:
case ExpressionType.And:
case ExpressionType.AndAlso:
case ExpressionType.Or:
case ExpressionType.OrElse:
case ExpressionType.LessThan:
case ExpressionType.LessThanOrEqual:
case ExpressionType.GreaterThan:
case ExpressionType.GreaterThanOrEqual:
case ExpressionType.Equal:
case ExpressionType.NotEqual:
case ExpressionType.Coalesce:
case ExpressionType.ArrayIndex:
case ExpressionType.RightShift:
case ExpressionType.LeftShift:
case ExpressionType.ExclusiveOr:
return VisitBinary(exp as BinaryExpression);
case ExpressionType.Negate:
case ExpressionType.NegateChecked:
case ExpressionType.Not:
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
case ExpressionType.ArrayLength:
case ExpressionType.Quote:
case ExpressionType.TypeAs:
return VisitUnary(exp as UnaryExpression);
case ExpressionType.Parameter:
return VisitParameter(exp as ParameterExpression);
case ExpressionType.Call:
return VisitMethodCall(exp as MethodCallExpression);
case ExpressionType.New:
return VisitNew(exp as NewExpression);
case ExpressionType.NewArrayInit:
case ExpressionType.NewArrayBounds:
return VisitNewArray(exp as NewArrayExpression);
default:
return exp.ToString();
}
}
protected virtual string VisitLambda(LambdaExpression lambda)
{
if (lambda.Body.NodeType == ExpressionType.MemberAccess && sep == " ")
{
MemberExpression m = lambda.Body as MemberExpression;
if (m.Expression != null)
{
string r = VisitMemberAccess(m);
return string.Format("{0}={1}", r, GetQuotedTrueValue());
}
}
return Visit(lambda.Body);
}
protected virtual string VisitBinary(BinaryExpression b)
{
string left, right;
var operand = BindOperant(b.NodeType); //sep= " " ??
if (operand == "AND" || operand == "OR")
{
MemberExpression m = b.Left as MemberExpression;
if (m != null && m.Expression != null)
{
string r = VisitMemberAccess(m);
left = string.Format("{0}={1}", r, GetQuotedTrueValue());
}
else
{
left = Visit(b.Left);
}
m = b.Right as MemberExpression;
if (m != null && m.Expression != null)
{
string r = VisitMemberAccess(m);
right = string.Format("{0}={1}", r, GetQuotedTrueValue());
}
else
{
right = Visit(b.Right);
}
}
else
{
left = Visit(b.Left);
right = Visit(b.Right);
}
if (operand == "=" && right == "null") operand = "is";
else if (operand == "<>" && right == "null") operand = "is not";
else if (operand == "=" || operand == "<>")
{
if (IsTrueExpression(right)) right = GetQuotedTrueValue();
else if (IsFalseExpression(right)) right = GetQuotedFalseValue();
if (IsTrueExpression(left)) left = GetQuotedTrueValue();
else if (IsFalseExpression(left)) left = GetQuotedFalseValue();
}
switch (operand)
{
case "MOD":
case "COALESCE":
return string.Format("{0}({1},{2})", operand, left, right);
default:
return left + sep + operand + sep + right;
}
}
protected virtual string VisitMemberAccess(MemberExpression m)
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);
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);
string field = GetFieldName(_pd, m.Member.Name);
return field;
}
@@ -168,303 +38,14 @@ namespace Umbraco.Core.Persistence.Querying
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
object o = getter();
return GetQuotedValue(o, o != null ? o.GetType() : null);
SqlParameters.Add(o);
return string.Format("@{0}", SqlParameters.Count - 1);
//return GetQuotedValue(o, o != null ? o.GetType() : null);
}
protected virtual string VisitNew(NewExpression nex)
{
// TODO : check !
var member = Expression.Convert(nex, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
try
{
var getter = lambda.Compile();
object o = getter();
return GetQuotedValue(o, o.GetType());
}
catch (System.InvalidOperationException)
{ // FieldName ?
List<Object> exprs = VisitExpressionList(nex.Arguments);
var r = new StringBuilder();
foreach (Object e in exprs)
{
r.AppendFormat("{0}{1}",
r.Length > 0 ? "," : "",
e);
}
return r.ToString();
}
}
protected virtual string VisitParameter(ParameterExpression p)
{
return p.Name;
}
protected virtual string VisitConstant(ConstantExpression c)
{
if (c.Value == null)
return "null";
else if (c.Value.GetType() == typeof(bool))
{
object o = GetQuotedValue(c.Value, c.Value.GetType());
return string.Format("({0}={1})", GetQuotedTrueValue(), o);
}
else
return GetQuotedValue(c.Value, c.Value.GetType());
}
protected virtual string VisitUnary(UnaryExpression u)
{
switch (u.NodeType)
{
case ExpressionType.Not:
string o = Visit(u.Operand);
if (IsFieldName(o)) o = o + "=" + GetQuotedValue(true, typeof(bool));
return "NOT (" + o + ")";
default:
return Visit(u.Operand);
}
}
protected virtual string VisitMethodCall(MethodCallExpression m)
{
List<Object> args = this.VisitExpressionList(m.Arguments);
Object r;
if (m.Object != null)
r = Visit(m.Object);
else
{
r = args[0];
args.RemoveAt(0);
}
//TODO: We should probably add the same logic we've done for ModelToSqlExpressionHelper with checking for:
// InvariantStartsWith, InvariantEndsWith, SqlWildcard, etc...
// since we should be able to easily handle that with the Poco objects too.
switch (m.Method.Name)
{
case "ToUpper":
return string.Format("upper({0})", r);
case "ToLower":
return string.Format("lower({0})", r);
case "SqlWildcard":
case "StartsWith":
case "EndsWith":
case "Contains":
case "Equals":
case "SqlStartsWith":
case "SqlEndsWith":
case "SqlContains":
case "SqlEquals":
case "InvariantStartsWith":
case "InvariantEndsWith":
case "InvariantContains":
case "InvariantEquals":
//default
var colType = TextColumnType.NVarchar;
//then check if this arg has been passed in
if (m.Arguments.Count > 1)
{
var colTypeArg = m.Arguments.FirstOrDefault(x => x is ConstantExpression && x.Type == typeof(TextColumnType));
if (colTypeArg != null)
{
colType = (TextColumnType)((ConstantExpression)colTypeArg).Value;
}
}
return HandleStringComparison(r.ToString(), args[0].ToString(), m.Method.Name, colType);
case "Substring":
var startIndex = Int32.Parse(args[0].ToString()) + 1;
if (args.Count == 2)
{
var length = Int32.Parse(args[1].ToString());
return string.Format("substring({0} from {1} for {2})",
r,
startIndex,
length);
}
else
return string.Format("substring({0} from {1})",
r,
startIndex);
case "Round":
case "Floor":
case "Ceiling":
case "Coalesce":
case "Abs":
case "Sum":
return string.Format("{0}({1}{2})",
m.Method.Name,
r,
args.Count == 1 ? string.Format(",{0}", args[0]) : "");
case "Concat":
var s = new StringBuilder();
foreach (Object e in args)
{
s.AppendFormat(" || {0}", e);
}
return string.Format("{0}{1}", r, s.ToString());
case "In":
var member = Expression.Convert(m.Arguments[1], typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
var inArgs = getter() as object[];
var sIn = new StringBuilder();
foreach (Object e in inArgs)
{
if (e.GetType().ToString() != "System.Collections.Generic.List`1[System.Object]")
{
sIn.AppendFormat("{0}{1}",
sIn.Length > 0 ? "," : "",
GetQuotedValue(e, e.GetType()));
}
else
{
var listArgs = e as IList<Object>;
foreach (Object el in listArgs)
{
sIn.AppendFormat("{0}{1}",
sIn.Length > 0 ? "," : "",
GetQuotedValue(el, el.GetType()));
}
}
}
return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString());
case "Desc":
return string.Format("{0} DESC", r);
case "Alias":
case "As":
return string.Format("{0} As {1}", r,
GetQuotedColumnName(RemoveQuoteFromAlias(RemoveQuote(args[0].ToString()))));
case "ToString":
return r.ToString();
default:
var s2 = new StringBuilder();
foreach (Object e in args)
{
s2.AppendFormat(",{0}", GetQuotedValue(e, e.GetType()));
}
return string.Format("{0}({1}{2})", m.Method.Name, r, s2.ToString());
}
}
protected virtual List<Object> VisitExpressionList(ReadOnlyCollection<Expression> original)
{
var list = new List<Object>();
for (int i = 0, n = original.Count; i < n; i++)
{
if (original[i].NodeType == ExpressionType.NewArrayInit ||
original[i].NodeType == ExpressionType.NewArrayBounds)
{
list.AddRange(VisitNewArrayFromExpressionList(original[i] as NewArrayExpression));
}
else
list.Add(Visit(original[i]));
}
return list;
}
protected virtual string VisitNewArray(NewArrayExpression na)
{
List<Object> exprs = VisitExpressionList(na.Expressions);
var r = new StringBuilder();
foreach (Object e in exprs)
{
r.Append(r.Length > 0 ? "," + e : e);
}
return r.ToString();
}
protected virtual List<Object> VisitNewArrayFromExpressionList(NewArrayExpression na)
{
List<Object> exprs = VisitExpressionList(na.Expressions);
return exprs;
}
protected virtual string BindOperant(ExpressionType e)
{
switch (e)
{
case ExpressionType.Equal:
return "=";
case ExpressionType.NotEqual:
return "<>";
case ExpressionType.GreaterThan:
return ">";
case ExpressionType.GreaterThanOrEqual:
return ">=";
case ExpressionType.LessThan:
return "<";
case ExpressionType.LessThanOrEqual:
return "<=";
case ExpressionType.AndAlso:
return "AND";
case ExpressionType.OrElse:
return "OR";
case ExpressionType.Add:
return "+";
case ExpressionType.Subtract:
return "-";
case ExpressionType.Multiply:
return "*";
case ExpressionType.Divide:
return "/";
case ExpressionType.Modulo:
return "MOD";
case ExpressionType.Coalesce:
return "COALESCE";
default:
return e.ToString();
}
}
public virtual string GetQuotedTableName(string tableName)
{
return string.Format("\"{0}\"", tableName);
}
public virtual string GetQuotedColumnName(string columnName)
{
return string.Format("\"{0}\"", columnName);
}
public virtual string GetQuotedName(string name)
{
return string.Format("\"{0}\"", name);
}
private string GetQuotedTrueValue()
{
return GetQuotedValue(true, typeof(bool));
}
private string GetQuotedFalseValue()
{
return GetQuotedValue(false, typeof(bool));
}
public virtual string GetQuotedValue(object value, Type fieldType)
{
return GetQuotedValue(value, fieldType, EscapeParam, ShouldQuoteValue);
}
protected virtual string GetFieldName(Database.PocoData pocoData, string name)
{
var column = pocoData.Columns.FirstOrDefault(x => x.Value.PropertyInfo.Name == name);
@@ -472,34 +53,10 @@ namespace Umbraco.Core.Persistence.Querying
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(pocoData.TableInfo.TableName),
SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(column.Value.ColumnName));
}
private string GetTrueExpression()
{
object o = GetQuotedTrueValue();
return string.Format("({0}={1})", o, o);
}
private string GetFalseExpression()
{
return string.Format("({0}={1})",
GetQuotedTrueValue(),
GetQuotedFalseValue());
}
private bool IsTrueExpression(string exp)
{
return (exp == GetTrueExpression());
}
private bool IsFalseExpression(string exp)
{
return (exp == GetFalseExpression());
}
protected bool IsFieldName(string quotedExp)
{
return true;
}
//protected bool IsFieldName(string quotedExp)
//{
// return true;
//}
}
}
+27 -17
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Umbraco.Core.Persistence.Querying
@@ -10,37 +11,46 @@ namespace Umbraco.Core.Persistence.Querying
/// <typeparam name="T"></typeparam>
public class Query<T> : IQuery<T>
{
//private readonly ExpressionHelper<T> _expresionist = new ExpressionHelper<T>();
private readonly ModelToSqlExpressionHelper<T> _expresionist = new ModelToSqlExpressionHelper<T>();
private readonly List<string> _wheres = new List<string>();
public Query()
: base()
{
}
private readonly List<Tuple<string, object[]>> _wheres = new List<Tuple<string, object[]>>();
/// <summary>
/// Helper method to be used instead of manually creating an instance
/// </summary>
public static IQuery<T> Builder
{
get
{
return new Query<T>();
}
get { return new Query<T>(); }
}
/// <summary>
/// Adds a where clause to the query
/// </summary>
/// <param name="predicate"></param>
/// <returns>This instance so calls to this method are chainable</returns>
public virtual IQuery<T> Where(Expression<Func<T, bool>> predicate)
{
if (predicate != null)
{
string whereExpression = _expresionist.Visit(predicate);
_wheres.Add(whereExpression);
var expressionHelper = new ModelToSqlExpressionHelper<T>();
string whereExpression = expressionHelper.Visit(predicate);
_wheres.Add(new Tuple<string, object[]>(whereExpression, expressionHelper.GetSqlParameters()));
}
return this;
}
public List<string> WhereClauses()
/// <summary>
/// Returns all translated where clauses and their sql parameters
/// </summary>
/// <returns></returns>
public IEnumerable<Tuple<string, object[]>> GetWhereClauses()
{
return _wheres;
}
[Obsolete("This is no longer used, use the GetWhereClauses method which includes the SQL parameters")]
public List<string> WhereClauses()
{
return _wheres.Select(x => x.Item1).ToList();
}
}
}
@@ -15,14 +15,10 @@ namespace Umbraco.Core.Persistence.Querying
if (sql == null)
throw new Exception("Sql cannot be null");
var query1 = query as Query<T>;
if (query1 == null)
throw new Exception("Query cannot be null");
_sql = sql;
foreach (var clause in query1.WhereClauses())
foreach (var clause in query.GetWhereClauses())
{
_sql.Where(clause);
_sql.Where(clause.Item1, clause.Item2);
}
}
@@ -0,0 +1,9 @@
namespace Umbraco.Core.Persistence
{
internal enum RecordPersistenceType
{
Insert,
Update,
Delete
}
}
@@ -60,13 +60,20 @@ namespace Umbraco.Core.Persistence.Repositories
{
throw new NotImplementedException();
}
//NOTE: Not implemented because all ContentPreviewEntity will always return false for having an Identity
protected override void PersistUpdatedItem(ContentPreviewEntity<TContent> entity)
{
throw new NotImplementedException();
}
#endregion
protected override void PersistNewItem(ContentPreviewEntity<TContent> entity)
{
if (entity.Content.HasIdentity == false)
{
throw new InvalidOperationException("Cannot insert a preview for a content item that has no identity");
throw new InvalidOperationException("Cannot insert or update a preview for a content item that has no identity");
}
var previewPoco = new PreviewXmlDto
@@ -77,33 +84,13 @@ namespace Umbraco.Core.Persistence.Repositories
Xml = entity.Xml.ToString(SaveOptions.None)
};
Database.Insert(previewPoco);
//We need to do a special InsertOrUpdate here because we know that the PreviewXmlDto table has a composite key and thus
// a unique constraint which can be violated if 2+ threads try to execute the same insert sql at the same time.
Database.InsertOrUpdate(previewPoco,
//Since the table has a composite key, we need to specify an explit update statement
"SET xml = @Xml, timestamp = @Timestamp WHERE nodeId=@NodeId AND versionId=@VersionId",
new {NodeId = previewPoco.NodeId, VersionId = previewPoco.VersionId, Xml = previewPoco.Xml, Timestamp = previewPoco.Timestamp});
}
protected override void PersistUpdatedItem(ContentPreviewEntity<TContent> entity)
{
if (entity.Content.HasIdentity == false)
{
throw new InvalidOperationException("Cannot update a preview for a content item that has no identity");
}
var previewPoco = new PreviewXmlDto
{
NodeId = entity.Id,
Timestamp = DateTime.Now,
VersionId = entity.Version,
Xml = entity.Xml.ToString(SaveOptions.None)
};
Database.Update<PreviewXmlDto>(
"SET xml = @Xml, timestamp = @Timestamp WHERE nodeId = @Id AND versionId = @Version",
new
{
Xml = previewPoco.Xml,
Timestamp = previewPoco.Timestamp,
Id = previewPoco.NodeId,
Version = previewPoco.VersionId
});
}
}
}
@@ -575,10 +575,8 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="content"></param>
/// <param name="xml"></param>
public void AddOrUpdateContentXml(IContent content, Func<IContent, XElement> xml)
{
var contentExists = Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId = @Id", new { Id = content.Id }) != 0;
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IContent>(contentExists, content, xml));
{
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IContent>(content, xml));
}
/// <summary>
@@ -597,11 +595,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="xml"></param>
public void AddOrUpdatePreviewXml(IContent content, Func<IContent, XElement> xml)
{
var previewExists =
Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId = @Id AND versionId = @Version",
new { Id = content.Id, Version = content.Version }) != 0;
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IContent>(previewExists, content, xml));
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IContent>(content, xml));
}
#endregion
@@ -55,6 +55,12 @@ namespace Umbraco.Core.Persistence.Repositories
{
get { throw new NotImplementedException(); }
}
//NOTE: Not implemented because all ContentXmlEntity will always return false for having an Identity
protected override void PersistUpdatedItem(ContentXmlEntity<TContent> entity)
{
throw new NotImplementedException();
}
#endregion
@@ -68,22 +74,21 @@ namespace Umbraco.Core.Persistence.Repositories
{
if (entity.Content.HasIdentity == false)
{
throw new InvalidOperationException("Cannot insert an xml entry for a content item that has no identity");
throw new InvalidOperationException("Cannot insert or update an xml entry for a content item that has no identity");
}
var poco = new ContentXmlDto { NodeId = entity.Id, Xml = entity.Xml.ToString(SaveOptions.None) };
Database.Insert(poco);
}
protected override void PersistUpdatedItem(ContentXmlEntity<TContent> entity)
{
if (entity.Content.HasIdentity == false)
var poco = new ContentXmlDto
{
throw new InvalidOperationException("Cannot update an xml entry for a content item that has no identity");
}
NodeId = entity.Id,
Xml = entity.Xml.ToString(SaveOptions.None)
};
var poco = new ContentXmlDto { NodeId = entity.Id, Xml = entity.Xml.ToString(SaveOptions.None) };
Database.Update(poco);
//We need to do a special InsertOrUpdate here because we know that the ContentXmlDto table has a 1:1 relation
// with the content table and a record may or may not exist so the
// unique constraint which can be violated if 2+ threads try to execute the same insert sql at the same time.
Database.InsertOrUpdate(poco);
}
}
}
@@ -66,15 +66,31 @@ namespace Umbraco.Core.Persistence.Repositories
{
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key).Append(GetGroupBy(isContent, isMedia));
var nodeDto = _work.Database.FirstOrDefault<dynamic>(sql);
if (nodeDto == null)
return null;
var factory = new UmbracoEntityFactory();
var entity = factory.BuildEntityFromDynamic(nodeDto);
var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId);
if (isMedia)
{
//for now treat media differently
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
var entities = _work.Database.Fetch<dynamic, UmbracoPropertyDto, UmbracoEntity>(
new UmbracoEntityRelator().Map, sql);
return entity;
return entities.FirstOrDefault();
}
else
{
var nodeDto = _work.Database.FirstOrDefault<dynamic>(sql);
if (nodeDto == null)
return null;
var factory = new UmbracoEntityFactory();
var entity = factory.BuildEntityFromDynamic(nodeDto);
return entity;
}
}
public virtual IUmbracoEntity Get(int id)
@@ -94,61 +110,90 @@ namespace Umbraco.Core.Persistence.Repositories
{
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
var sql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id).Append(GetGroupBy(isContent, isMedia));
var nodeDto = _work.Database.FirstOrDefault<dynamic>(sql);
if (nodeDto == null)
return null;
var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId);
if (isMedia)
{
//for now treat media differently
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
var entities = _work.Database.Fetch<dynamic, UmbracoPropertyDto, UmbracoEntity>(
new UmbracoEntityRelator().Map, sql);
var factory = new UmbracoEntityFactory();
var entity = factory.BuildEntityFromDynamic(nodeDto);
return entities.FirstOrDefault();
}
else
{
var nodeDto = _work.Database.FirstOrDefault<dynamic>(sql);
if (nodeDto == null)
return null;
return entity;
var factory = new UmbracoEntityFactory();
var entity = factory.BuildEntityFromDynamic(nodeDto);
return entity;
}
}
public virtual IEnumerable<IUmbracoEntity> GetAll(Guid objectTypeId, params int[] ids)
{
if (ids.Any())
{
foreach (var id in ids)
return PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.id in (@ids)", new {ids = ids}));
}
else
{
return PerformGetAll(objectTypeId);
}
}
public virtual IEnumerable<IUmbracoEntity> GetAll(Guid objectTypeId, params Guid[] keys)
{
if (keys.Any())
{
return PerformGetAll(objectTypeId, sql1 => sql1.Where(" umbracoNode.uniqueID in (@keys)", new { keys = keys }));
}
else
{
return PerformGetAll(objectTypeId);
}
}
private IEnumerable<IUmbracoEntity> PerformGetAll(Guid objectTypeId, Action<Sql> filter = null)
{
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
var sql = GetFullSqlForEntityType(isContent, isMedia, objectTypeId, filter);
var factory = new UmbracoEntityFactory();
if (isMedia)
{
//for now treat media differently
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
var entities = _work.Database.Fetch<dynamic, UmbracoPropertyDto, UmbracoEntity>(
new UmbracoEntityRelator().Map, sql);
foreach (var entity in entities)
{
yield return Get(id, objectTypeId);
yield return entity;
}
}
else
{
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
var sql = GetBaseWhere(GetBase, isContent, isMedia, string.Empty, objectTypeId).Append(GetGroupBy(isContent, isMedia));
var factory = new UmbracoEntityFactory();
if (isMedia)
var dtos = _work.Database.Fetch<dynamic>(sql);
foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto)))
{
//for now treat media differently
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
var entities = _work.Database.Fetch<dynamic, UmbracoPropertyDto, UmbracoEntity>(
new UmbracoEntityRelator().Map, sql);
foreach (var entity in entities)
{
yield return entity;
}
}
else
{
var dtos = _work.Database.Fetch<dynamic>(sql);
foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto)))
{
yield return entity;
}
yield return entity;
}
}
}
public virtual IEnumerable<IUmbracoEntity> GetByQuery(IQuery<IUmbracoEntity> query)
{
var wheres = string.Concat(" AND ", string.Join(" AND ", ((Query<IUmbracoEntity>) query).WhereClauses()));
var sqlClause = GetBase(false, false, wheres);
var sqlClause = GetBase(false, false, null);
var translator = new SqlTranslator<IUmbracoEntity>(sqlClause, query);
var sql = translator.Translate().Append(GetGroupBy(false, false));
@@ -162,28 +207,41 @@ namespace Umbraco.Core.Persistence.Repositories
public virtual IEnumerable<IUmbracoEntity> GetByQuery(IQuery<IUmbracoEntity> query, Guid objectTypeId)
{
bool isContent = objectTypeId == new Guid(Constants.ObjectTypes.Document);
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
var wheres = string.Concat(" AND ", string.Join(" AND ", ((Query<IUmbracoEntity>)query).WhereClauses()));
var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, wheres, objectTypeId);
var sqlClause = GetBaseWhere(GetBase, isContent, isMedia, null, objectTypeId);
var translator = new SqlTranslator<IUmbracoEntity>(sqlClause, query);
var sql = translator.Translate().Append(GetGroupBy(isContent, isMedia));
var entitySql = translator.Translate();
var factory = new UmbracoEntityFactory();
if (isMedia)
{
var wheres = query.GetWhereClauses().ToArray();
var mediaSql = GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), sql =>
{
//adds the additional filters
foreach (var whereClause in wheres)
{
sql.Where(whereClause.Item1, whereClause.Item2);
}
});
//treat media differently for now
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
var entities = _work.Database.Fetch<dynamic, UmbracoPropertyDto, UmbracoEntity>(
new UmbracoEntityRelator().Map, sql);
new UmbracoEntityRelator().Map, mediaSql);
return entities;
}
else
{
//use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData
var dtos = _work.Database.Fetch<dynamic>(sql);
var finalSql = entitySql.Append(GetGroupBy(isContent, false));
var dtos = _work.Database.Fetch<dynamic>(finalSql);
return dtos.Select(factory.BuildEntityFromDynamic).Cast<IUmbracoEntity>().ToList();
}
}
@@ -193,7 +251,68 @@ namespace Umbraco.Core.Persistence.Repositories
#region Sql Statements
protected virtual Sql GetBase(bool isContent, bool isMedia, string additionWhereStatement = "")
protected Sql GetFullSqlForEntityType(Guid key, bool isContent, bool isMedia, Guid objectTypeId)
{
var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, key);
if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false));
return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)));
}
protected Sql GetFullSqlForEntityType(int id, bool isContent, bool isMedia, Guid objectTypeId)
{
var entitySql = GetBaseWhere(GetBase, isContent, isMedia, objectTypeId, id);
if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false));
return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)));
}
protected Sql GetFullSqlForEntityType(bool isContent, bool isMedia, Guid objectTypeId, Action<Sql> filter)
{
var entitySql = GetBaseWhere(GetBase, isContent, isMedia, filter, objectTypeId);
if (isMedia == false) return entitySql.Append(GetGroupBy(isContent, false));
return GetFullSqlForMedia(entitySql.Append(GetGroupBy(isContent, true, false)), filter);
}
private Sql GetFullSqlForMedia(Sql entitySql, Action<Sql> filter = null)
{
//this will add any dataNvarchar property to the output which can be added to the additional properties
var joinSql = new Sql()
.Select("contentNodeId, versionId, dataNvarchar, dataNtext, controlId, alias as propertyTypeAlias")
.From<PropertyDataDto>()
.InnerJoin<NodeDto>()
.On<PropertyDataDto, NodeDto>(dto => dto.NodeId, dto => dto.NodeId)
.InnerJoin<PropertyTypeDto>()
.On<PropertyTypeDto, PropertyDataDto>(dto => dto.Id, dto => dto.PropertyTypeId)
.InnerJoin<DataTypeDto>()
.On<PropertyTypeDto, DataTypeDto>(dto => dto.DataTypeId, dto => dto.DataTypeId)
.Where("umbracoNode.nodeObjectType = @nodeObjectType", new {nodeObjectType = Constants.ObjectTypes.Media});
if (filter != null)
{
filter(joinSql);
}
//We're going to create a query to query against the entity SQL
// because we cannot group by nText columns and we have a COUNT in the entitySql we cannot simply left join
// the entitySql query, we have to join the wrapped query to get the ntext in the result
var wrappedSql = new Sql("SELECT * FROM (")
.Append(entitySql)
.Append(new Sql(") tmpTbl LEFT JOIN ("))
.Append(joinSql)
.Append(new Sql(") as property ON id = property.contentNodeId"))
.OrderBy("sortOrder");
return wrappedSql;
}
protected virtual Sql GetBase(bool isContent, bool isMedia, Action<Sql> customFilter)
{
var columns = new List<object>
{
@@ -221,21 +340,15 @@ namespace Umbraco.Core.Persistence.Repositories
columns.Add("contenttype.isContainer");
}
if (isMedia)
{
columns.Add("property.dataNvarchar as umbracoFile");
columns.Add("property.controlId");
}
//Creates an SQL query to return a single row for the entity
var sql = new Sql()
var entitySql = new Sql()
.Select(columns.ToArray())
.From("umbracoNode umbracoNode")
.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id");
.From("umbracoNode umbracoNode");
if (isContent || isMedia)
{
sql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id")
entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id")
.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType")
.LeftJoin(
"(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1 GROUP BY nodeId, versionId) as published")
@@ -245,60 +358,56 @@ namespace Umbraco.Core.Persistence.Repositories
.On("umbracoNode.id = latest.nodeId");
}
if (isMedia)
entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id");
if (customFilter != null)
{
sql.LeftJoin(
"(SELECT contentNodeId, versionId, dataNvarchar, controlId FROM cmsPropertyData " +
"INNER JOIN umbracoNode ON cmsPropertyData.contentNodeId = umbracoNode.id " +
"INNER JOIN cmsPropertyType ON cmsPropertyType.id = cmsPropertyData.propertytypeid " +
"INNER JOIN cmsDataType ON cmsPropertyType.dataTypeId = cmsDataType.nodeId "+
"WHERE umbracoNode.nodeObjectType = '" + Constants.ObjectTypes.Media + "'" + additionWhereStatement + ") as property")
.On("umbracoNode.id = property.contentNodeId");
customFilter(entitySql);
}
return sql;
return entitySql;
}
protected virtual Sql GetBaseWhere(Func<bool, bool, string, Sql> baseQuery, bool isContent, bool isMedia, string additionWhereStatement, Guid nodeObjectType)
protected virtual Sql GetBaseWhere(Func<bool, bool, Action<Sql>, Sql> baseQuery, bool isContent, bool isMedia, Action<Sql> filter, Guid nodeObjectType)
{
var sql = baseQuery(isContent, isMedia, additionWhereStatement)
var sql = baseQuery(isContent, isMedia, filter)
.Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType });
return sql;
}
protected virtual Sql GetBaseWhere(Func<bool, bool, string, Sql> baseQuery, bool isContent, bool isMedia, int id)
protected virtual Sql GetBaseWhere(Func<bool, bool, Action<Sql>, Sql> baseQuery, bool isContent, bool isMedia, int id)
{
var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'")
var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.id = @Id", new { Id = id })
.Append(GetGroupBy(isContent, isMedia));
return sql;
}
protected virtual Sql GetBaseWhere(Func<bool, bool, string, Sql> baseQuery, bool isContent, bool isMedia, Guid key)
protected virtual Sql GetBaseWhere(Func<bool, bool, Action<Sql>, Sql> baseQuery, bool isContent, bool isMedia, Guid key)
{
var sql = baseQuery(isContent, isMedia, " AND umbracoNode.uniqueID = '" + key + "'")
var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.uniqueID = @UniqueID", new { UniqueID = key })
.Append(GetGroupBy(isContent, isMedia));
return sql;
}
protected virtual Sql GetBaseWhere(Func<bool, bool, string, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id)
protected virtual Sql GetBaseWhere(Func<bool, bool, Action<Sql>, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, int id)
{
var sql = baseQuery(isContent, isMedia, " AND umbracoNode.id = '" + id + "'")
var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType",
new { Id = id, NodeObjectType = nodeObjectType });
new {Id = id, NodeObjectType = nodeObjectType});
return sql;
}
protected virtual Sql GetBaseWhere(Func<bool, bool, string, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key)
protected virtual Sql GetBaseWhere(Func<bool, bool, Action<Sql>, Sql> baseQuery, bool isContent, bool isMedia, Guid nodeObjectType, Guid key)
{
var sql = baseQuery(isContent, isMedia, " AND umbracoNode.uniqueID = '" + key + "'")
var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType",
new { UniqueID = key, NodeObjectType = nodeObjectType });
return sql;
}
protected virtual Sql GetGroupBy(bool isContent, bool isMedia)
protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true)
{
var columns = new List<object>
{
@@ -324,19 +433,18 @@ namespace Umbraco.Core.Persistence.Repositories
columns.Add("contenttype.thumbnail");
columns.Add("contenttype.isContainer");
}
var sql = new Sql()
.GroupBy(columns.ToArray());
if (isMedia)
if (includeSort)
{
columns.Add("property.dataNvarchar");
columns.Add("property.controlId");
sql = sql.OrderBy("umbracoNode.sortOrder");
}
var sql = new Sql()
.GroupBy(columns.ToArray())
.OrderBy("umbracoNode.sortOrder");
return sql;
}
#endregion
/// <summary>
@@ -384,10 +492,16 @@ namespace Umbraco.Core.Persistence.Repositories
[Column("controlId")]
public Guid DataTypeControlId { get; set; }
[Column("umbracoFile")]
public string UmbracoFile { get; set; }
}
[Column("propertyTypeAlias")]
public string PropertyAlias { get; set; }
[Column("dataNvarchar")]
public string NVarcharValue { get; set; }
[Column("dataNtext")]
public string NTextValue { get; set; }
}
/// <summary>
/// This is a special relator in that it is not returning a DTO but a real resolved entity and that it accepts
/// a dynamic instance.
@@ -418,10 +532,12 @@ namespace Umbraco.Core.Persistence.Repositories
Current.UmbracoProperties = new List<UmbracoEntity.UmbracoProperty>();
}
Current.UmbracoProperties.Add(new UmbracoEntity.UmbracoProperty
{
DataTypeControlId = p.DataTypeControlId,
Value = p.UmbracoFile
});
{
DataTypeControlId = p.DataTypeControlId,
Value = p.NTextValue.IsNullOrWhiteSpace()
? p.NVarcharValue
: p.NTextValue
});
// Return null to indicate we're not done with this UmbracoEntity yet
return null;
}
@@ -433,7 +549,7 @@ namespace Umbraco.Core.Persistence.Repositories
var prev = Current;
// Setup the new current UmbracoEntity
Current = _factory.BuildEntityFromDynamic(a);
//add the property/create the prop list if null
@@ -442,7 +558,9 @@ namespace Umbraco.Core.Persistence.Repositories
new UmbracoEntity.UmbracoProperty
{
DataTypeControlId = p.DataTypeControlId,
Value = p.UmbracoFile
Value = p.NTextValue.IsNullOrWhiteSpace()
? p.NVarcharValue
: p.NTextValue
}
};
@@ -181,18 +181,12 @@ namespace Umbraco.Core.Persistence.Repositories
public void AddOrUpdateContentXml(IMedia content, Func<IMedia, XElement> xml)
{
var contentExists = Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId = @Id", new { Id = content.Id }) != 0;
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IMedia>(contentExists, content, xml));
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IMedia>(content, xml));
}
public void AddOrUpdatePreviewXml(IMedia content, Func<IMedia, XElement> xml)
{
var previewExists =
Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId = @Id AND versionId = @Version",
new { Id = content.Id, Version = content.Version }) != 0;
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IMedia>(previewExists, content, xml));
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IMedia>(content, xml));
}
protected override void PerformDeleteVersion(int id, Guid versionId)
@@ -202,13 +202,13 @@ namespace Umbraco.Core.Persistence.Repositories
//find the member by username
var memberSql = new Sql();
var memberObjectType = new Guid(Constants.ObjectTypes.Member);
var escapedUsername = PetaPocoExtensions.EscapeAtSymbols(username);
memberSql.Select("umbracoNode.id")
.From<NodeDto>()
.InnerJoin<MemberDto>()
.On<NodeDto, MemberDto>(dto => dto.NodeId, dto => dto.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == memberObjectType)
.Where<MemberDto>(x => x.LoginName == escapedUsername);
.Where<MemberDto>(x => x.LoginName == username);
var memberIdUsername = Database.Fetch<int?>(memberSql).FirstOrDefault();
if (memberIdUsername.HasValue == false)
{
@@ -279,6 +279,9 @@ namespace Umbraco.Core.Persistence.Repositories
public void AssignRolesInternal(int[] memberIds, string[] roleNames)
{
//ensure they're unique
memberIds = memberIds.Distinct().ToArray();
//create the missing roles first
var existingSql = new Sql()
@@ -520,10 +520,10 @@ namespace Umbraco.Core.Persistence.Repositories
public bool Exists(string username)
{
var sql = new Sql();
var escapedUserName = PetaPocoExtensions.EscapeAtSymbols(username);
sql.Select("COUNT(*)")
.From<MemberDto>()
.Where<MemberDto>(x => x.LoginName == escapedUserName);
.Where<MemberDto>(x => x.LoginName == username);
return Database.ExecuteScalar<int>(sql) > 0;
}
@@ -609,18 +609,12 @@ namespace Umbraco.Core.Persistence.Repositories
public void AddOrUpdateContentXml(IMember content, Func<IMember, XElement> xml)
{
var contentExists = Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsContentXml WHERE nodeId = @Id", new { Id = content.Id }) != 0;
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IMember>(contentExists, content, xml));
_contentXmlRepository.AddOrUpdate(new ContentXmlEntity<IMember>(content, xml));
}
public void AddOrUpdatePreviewXml(IMember content, Func<IMember, XElement> xml)
{
var previewExists =
Database.ExecuteScalar<int>("SELECT COUNT(nodeId) FROM cmsPreviewXml WHERE nodeId = @Id AND versionId = @Version",
new { Id = content.Id, Version = content.Version }) != 0;
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IMember>(previewExists, content, xml));
_contentPreviewRepository.AddOrUpdate(new ContentPreviewEntity<IMember>(content, xml));
}
private IMember BuildFromDto(List<MemberReadOnlyDto> dtos)
@@ -262,10 +262,10 @@ namespace Umbraco.Core.Persistence.Repositories
public bool Exists(string username)
{
var sql = new Sql();
var escapedUserName = PetaPocoExtensions.EscapeAtSymbols(username);
sql.Select("COUNT(*)")
.From<UserDto>()
.Where<UserDto>(x => x.UserName == escapedUserName);
.Where<UserDto>(x => x.UserName == username);
return Database.ExecuteScalar<int>(sql) > 0;
}
@@ -188,6 +188,8 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="updateDate"></param>
/// <returns></returns>
protected PropertyCollection GetPropertyCollection(int id, Guid versionId, IContentTypeComposition contentType, DateTime createDate, DateTime updateDate)
{
var sql = new Sql();
sql.Select("*")
@@ -13,10 +13,19 @@ namespace Umbraco.Core.Persistence.SqlSyntax
{
string EscapeString(string val);
string GetWildcardPlaceholder();
string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType);
string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType);
[Obsolete("Use the overload with the parameter index instead")]
string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType);
string GetQuotedTableName(string tableName);
@@ -0,0 +1,120 @@
using System;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.SqlSyntax
{
/// <summary>
/// Abstract class for defining MS sql implementations
/// </summary>
/// <typeparam name="TSyntax"></typeparam>
public abstract class MicrosoftSqlSyntaxProviderBase<TSyntax> : SqlSyntaxProviderBase<TSyntax>
where TSyntax : ISqlSyntaxProvider
{
public override string RenameTable { get { return "sp_rename '{0}', '{1}'"; } }
public override string AddColumn { get { return "ALTER TABLE {0} ADD {1}"; } }
public override string GetQuotedTableName(string tableName)
{
return string.Format("[{0}]", tableName);
}
public override string GetQuotedColumnName(string columnName)
{
return string.Format("[{0}]", columnName);
}
public override string GetQuotedName(string name)
{
return string.Format("[{0}]", name);
}
public override string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnEqualComparison(column, paramIndex, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for = comparison with NText columns but allows this syntax
return string.Format("{0} LIKE @{1}", column, paramIndex);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnWildcardComparison(column, paramIndex, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE @{1}", column, paramIndex);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnStartsWithComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '{1}%'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnEndsWithComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '%{1}'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnContainsComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '%{1}%'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnContainsComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '{1}'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
}
}
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// Represents an SqlSyntaxProvider for Sql Ce
/// </summary>
[SqlSyntaxProviderAttribute("System.Data.SqlServerCe.4.0")]
public class SqlCeSyntaxProvider : SqlSyntaxProviderBase<SqlCeSyntaxProvider>
public class SqlCeSyntaxProvider : MicrosoftSqlSyntaxProviderBase<SqlCeSyntaxProvider>
{
public SqlCeSyntaxProvider()
{
@@ -60,6 +60,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return indexType;
}
[Obsolete("Use the overload with the parameter index instead")]
public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
@@ -74,76 +75,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
}
}
public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnStartsWithComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '{1}%'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnEndsWithComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '%{1}'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnContainsComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '%{1}%'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnContainsComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '{1}'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetQuotedTableName(string tableName)
{
return string.Format("[{0}]", tableName);
}
public override string GetQuotedColumnName(string columnName)
{
return string.Format("[{0}]", columnName);
}
public override string GetQuotedName(string name)
{
return string.Format("[{0}]", name);
}
public override string FormatColumnRename(string tableName, string oldName, string newName)
{
@@ -285,10 +217,10 @@ ORDER BY TABLE_NAME, INDEX_NAME");
}
}
public override string AddColumn { get { return "ALTER TABLE {0} ADD {1}"; } }
public override string DropIndex { get { return "DROP INDEX {1}.{0}"; } }
public override string RenameTable { get { return "sp_rename '{0}', '{1}'"; } }
}
}
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Core.Persistence.SqlSyntax
{
@@ -10,7 +9,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// Represents an SqlSyntaxProvider for Sql Server
/// </summary>
[SqlSyntaxProviderAttribute("System.Data.SqlClient")]
public class SqlServerSyntaxProvider : SqlSyntaxProviderBase<SqlServerSyntaxProvider>
public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase<SqlServerSyntaxProvider>
{
public SqlServerSyntaxProvider()
{
@@ -33,91 +32,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// Gets/sets the version of the current SQL server instance
/// </summary>
internal Lazy<SqlServerVersionName> VersionName { get; set; }
public override string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnEqualComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for = comparison with NText columns but allows this syntax
return string.Format("{0} LIKE '{1}'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnStartsWithComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '{1}%'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnEndsWithComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '%{1}'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnContainsComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '%{1}%'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
{
switch (columnType)
{
case TextColumnType.NVarchar:
return base.GetStringColumnContainsComparison(column, value, columnType);
case TextColumnType.NText:
//MSSQL doesn't allow for upper methods with NText columns
return string.Format("{0} LIKE '{1}'", column, value);
default:
throw new ArgumentOutOfRangeException("columnType");
}
}
public override string GetQuotedTableName(string tableName)
{
return string.Format("[{0}]", tableName);
}
public override string GetQuotedColumnName(string columnName)
{
return string.Format("[{0}]", columnName);
}
public override string GetQuotedName(string name)
{
return string.Format("[{0}]", name);
}
public override IEnumerable<string> GetTablesInSchema(Database db)
{
@@ -218,12 +134,11 @@ order by T.name, I.name");
get { return "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; }
}
public override string AddColumn { get { return "ALTER TABLE {0} ADD {1}"; } }
public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } }
public override string RenameColumn { get { return "sp_rename '{0}.{1}', '{2}', 'COLUMN'"; } }
public override string RenameTable { get { return "sp_rename '{0}', '{1}'"; } }
}
}
@@ -33,6 +33,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax
};
}
public string GetWildcardPlaceholder()
{
return "%";
}
public string StringLengthNonUnicodeColumnDefinitionFormat = "VARCHAR({0})";
public string StringLengthUnicodeColumnDefinitionFormat = "NVARCHAR({0})";
@@ -108,34 +113,51 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return PetaPocoExtensions.EscapeAtSymbols(val.Replace("'", "''"));
}
public virtual string GetStringColumnEqualComparison(string column, int paramIndex, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return string.Format("upper({0}) = upper(@{1})", column, paramIndex);
}
public virtual string GetStringColumnWildcardComparison(string column, int paramIndex, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return string.Format("upper({0}) LIKE upper(@{1})", column, paramIndex);
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnEqualComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return string.Format("upper({0}) = '{1}'", column, value.ToUpper());
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnStartsWithComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return string.Format("upper({0}) like '{1}%'", column, value.ToUpper());
return string.Format("upper({0}) LIKE '{1}%'", column, value.ToUpper());
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnEndsWithComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return string.Format("upper({0}) like '%{1}'", column, value.ToUpper());
return string.Format("upper({0}) LIKE '%{1}'", column, value.ToUpper());
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnContainsComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return string.Format("upper({0}) like '%{1}%'", column, value.ToUpper());
return string.Format("upper({0}) LIKE '%{1}%'", column, value.ToUpper());
}
[Obsolete("Use the overload with the parameter index instead")]
public virtual string GetStringColumnWildcardComparison(string column, string value, TextColumnType columnType)
{
//use the 'upper' method to always ensure strings are matched without case sensitivity no matter what the db setting.
return string.Format("upper({0}) like '{1}'", column, value.ToUpper());
return string.Format("upper({0}) LIKE '{1}'", column, value.ToUpper());
}
public virtual string GetQuotedTableName(string tableName)
@@ -10,12 +10,12 @@
/// <remarks>
/// See: http://issues.umbraco.org/issue/U4-3876
/// </remarks>
public static string GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery)
public static Sql GetDeleteSubquery(this ISqlSyntaxProvider sqlProvider, string tableName, string columnName, Sql subQuery)
{
return string.Format(@"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)",
sqlProvider.GetQuotedTableName(tableName),
sqlProvider.GetQuotedColumnName(columnName),
subQuery.SQL);
return new Sql(string.Format(@"DELETE FROM {0} WHERE {1} IN (SELECT {1} FROM ({2}) x)",
sqlProvider.GetQuotedTableName(tableName),
sqlProvider.GetQuotedColumnName(columnName),
subQuery.SQL), subQuery.Arguments);
}
}
+6 -1
View File
@@ -484,6 +484,10 @@ namespace Umbraco.Core.Services
public IEnumerable<IContent> GetDescendants(int id)
{
var content = GetById(id);
if (content == null)
{
return Enumerable.Empty<IContent>();
}
return GetDescendants(content);
}
@@ -496,7 +500,8 @@ namespace Umbraco.Core.Services
{
using (var repository = _repositoryFactory.CreateContentRepository(_uowProvider.GetUnitOfWork()))
{
var query = Query<IContent>.Builder.Where(x => x.Path.StartsWith(content.Path) && x.Id != content.Id);
var pathMatch = content.Path + ",";
var query = Query<IContent>.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != content.Id);
var contents = repository.GetByQuery(query);
return contents;
+2 -1
View File
@@ -252,7 +252,8 @@ namespace Umbraco.Core.Services
using (var repository = _repositoryFactory.CreateEntityRepository(_uowProvider.GetUnitOfWork()))
{
var entity = repository.Get(id);
var query = Query<IUmbracoEntity>.Builder.Where(x => x.Path.StartsWith(entity.Path) && x.Id != id);
var pathMatch = entity.Path + ",";
var query = Query<IUmbracoEntity>.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != id);
var entities = repository.GetByQuery(query);
return entities;
+7 -2
View File
@@ -402,6 +402,10 @@ namespace Umbraco.Core.Services
public IEnumerable<IMedia> GetDescendants(int id)
{
var media = GetById(id);
if (media == null)
{
return Enumerable.Empty<IMedia>();
}
return GetDescendants(media);
}
@@ -415,7 +419,8 @@ namespace Umbraco.Core.Services
var uow = _uowProvider.GetUnitOfWork();
using (var repository = _repositoryFactory.CreateMediaRepository(uow))
{
var query = Query<IMedia>.Builder.Where(x => x.Path.StartsWith(media.Path) && x.Id != media.Id);
var pathMatch = media.Path + ",";
var query = Query<IMedia>.Builder.Where(x => x.Path.StartsWith(pathMatch) && x.Id != media.Id);
var medias = repository.GetByQuery(query);
return medias;
@@ -1198,4 +1203,4 @@ namespace Umbraco.Core.Services
public static event TypedEventHandler<IMediaService, RecycleBinEventArgs> EmptiedRecycleBin;
#endregion
}
}
}
@@ -17,15 +17,17 @@ namespace Umbraco.Core.Sync
/// status. This will attempt to determine the internal umbraco base url that can be used by the current
/// server to send a request to itself if it is in a load balanced environment.
/// </summary>
/// <returns>The full base url including schema (i.e. http://myserver:80/umbraco )</returns>
public static string GetCurrentServerUmbracoBaseUrl()
/// <returns>The full base url including schema (i.e. http://myserver:80/umbraco ) - or <c>null</c> if the url
/// cannot be determined at the moment (usually because the first request has not properly completed yet).</returns>
public static string GetCurrentServerUmbracoBaseUrl(ApplicationContext appContext)
{
var status = GetStatus();
if (status == CurrentServerEnvironmentStatus.Single)
{
//if it's a single install, then the base url has to be the first url registered
return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl);
// single install, return null if no config/original url, else use config/original url as base
// use http or https as appropriate
return GetBaseUrl(appContext);
}
var servers = UmbracoSettings.DistributionServers;
@@ -33,8 +35,9 @@ namespace Umbraco.Core.Sync
var nodes = servers.SelectNodes("./server");
if (nodes == null)
{
//cannot be determined, then the base url has to be the first url registered
return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl);
// cannot be determined, return null if no config/original url, else use config/original url as base
// use http or https as appropriate
return GetBaseUrl(appContext);
}
var xmlNodes = nodes.Cast<XmlNode>().ToList();
@@ -58,11 +61,12 @@ namespace Umbraco.Core.Sync
xmlNode.InnerText,
xmlNode.AttributeValue<string>("forcePortnumber").IsNullOrWhiteSpace() ? "80" : xmlNode.AttributeValue<string>("forcePortnumber"),
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
}
}
}
//cannot be determined, then the base url has to be the first url registered
return string.Format("http://{0}", ApplicationContext.Current.OriginalRequestUrl);
// cannot be determined, return null if no config/original url, else use config/original url as base
// use http or https as appropriate
return GetBaseUrl(appContext);
}
/// <summary>
@@ -85,7 +89,7 @@ namespace Umbraco.Core.Sync
}
var master = nodes.Cast<XmlNode>().FirstOrDefault();
if (master == null)
{
return CurrentServerEnvironmentStatus.Unknown;
@@ -104,13 +108,29 @@ namespace Umbraco.Core.Sync
}
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
{
//match by appdid or server name!
return CurrentServerEnvironmentStatus.Master;
return CurrentServerEnvironmentStatus.Master;
}
return CurrentServerEnvironmentStatus.Slave;
}
private static string GetBaseUrl(ApplicationContext appContext)
{
return (
// is config empty?
UmbracoSettings.ScheduledTasksBaseUrl.IsNullOrWhiteSpace()
// is the orig req empty?
? appContext.OriginalRequestUrl.IsNullOrWhiteSpace()
// we've got nothing
? null
//the orig req url is not null, use that
: string.Format("http{0}://{1}", GlobalSettings.UseSSL ? "s" : "", appContext.OriginalRequestUrl)
// the config has been specified, use that
: string.Format("http{0}://{1}", GlobalSettings.UseSSL ? "s" : "", UmbracoSettings.ScheduledTasksBaseUrl))
.EnsureEndsWith('/');
}
}
}
+2
View File
@@ -178,6 +178,7 @@
<Compile Include="Events\SendToPublishEventArgs.cs" />
<Compile Include="IApplicationEventHandler.cs" />
<Compile Include="IDisposeOnRequestEnd.cs" />
<Compile Include="Persistence\RecordPersistenceType.cs" />
<Compile Include="IO\ResizedImage.cs" />
<Compile Include="IO\UmbracoMediaFile.cs" />
<Compile Include="Media\MediaSubfolderCounter.cs" />
@@ -233,6 +234,7 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSixTwoZero\AssignMissingPrimaryForMySqlKeys2.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSixTwoZero\ChangePasswordColumn.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSixTwoZero\UpdateToNewMemberPropertyAliases.cs" />
<Compile Include="Persistence\SqlSyntax\MicrosoftSqlSyntaxProviderBase.cs" />
<Compile Include="Persistence\Querying\SqlStringExtensions.cs" />
<Compile Include="Persistence\Querying\StringPropertyMatchType.cs" />
<Compile Include="Persistence\Querying\TextColumnType.cs" />
@@ -21,6 +21,7 @@ namespace Umbraco.Tests.BusinessLogic
/// Creates a new app tree linked to an application, then delete the application and make sure the tree is gone as well
///</summary>
[Test()]
[Ignore]
public void ApplicationTree_Make_New_Then_Delete_App()
{
//create new app
@@ -32,7 +33,7 @@ namespace Umbraco.Tests.BusinessLogic
Assert.IsNotNull(app);
//create the new app tree assigned to the new app
ApplicationTree.MakeNew(false, false, 0, app.alias, name, name, "icon.jpg", "icon.jpg", "nullassembly", "nulltype", string.Empty);
ApplicationTree.MakeNew(false, false, 0, app.alias, name, name, "icon.jpg", "icon.jpg", "", "umbraco.loadContent, umbraco", string.Empty);
var tree = ApplicationTree.getByAlias(name);
Assert.IsNotNull(tree);
@@ -0,0 +1,133 @@
//using System;
//using System.Linq;
//using NUnit.Framework;
//using Umbraco.Core.Models;
//using Umbraco.Core.Persistence;
//using Umbraco.Core.Persistence.SqlSyntax;
//using Umbraco.Tests.TestHelpers;
//using Umbraco.Tests.TestHelpers.Entities;
//namespace Umbraco.Tests.Persistence
//{
// [DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
// [TestFixture]
// public class PetaPocoDynamicQueryTests : BaseDatabaseFactoryTest
// {
// [Test]
// public void Check_Poco_Storage_Growth()
// {
// //CreateStuff();
// for (int i = 0; i < 1000; i++)
// {
// DatabaseContext.Database.Fetch<dynamic>(
// "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='" + i + "'");
// }
// //var oc11 = Database.PocoData.GetCachedPocoData().Count();
// //var oc12 = Database.PocoData.GetConverters().Count();
// //var oc13 = Database.GetAutoMappers().Count();
// //var oc14 = Database.GetMultiPocoFactories().Count();
// //for (int i = 0; i < 2; i++)
// //{
// // i1 = DatabaseContext.Database.Fetch<dynamic>("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES");
// // r1 = i1.Select(x => x.TABLE_NAME).Cast<string>().ToList();
// //}
// //var oc21 = Database.PocoData.GetCachedPocoData().Count();
// //var oc22 = Database.PocoData.GetConverters().Count();
// //var oc23 = Database.GetAutoMappers().Count();
// //var oc24 = Database.GetMultiPocoFactories().Count();
// //var roots = ServiceContext.ContentService.GetRootContent();
// //foreach (var content in roots)
// //{
// // var d = ServiceContext.ContentService.GetDescendants(content);
// //}
// //var oc31 = Database.PocoData.GetCachedPocoData().Count();
// //var oc32 = Database.PocoData.GetConverters().Count();
// //var oc33 = Database.GetAutoMappers().Count();
// //var oc34 = Database.GetMultiPocoFactories().Count();
// //for (int i = 0; i < 2; i++)
// //{
// // roots = ServiceContext.ContentService.GetRootContent();
// // foreach (var content in roots)
// // {
// // var d = ServiceContext.ContentService.GetDescendants(content);
// // }
// //}
// //var oc41 = Database.PocoData.GetCachedPocoData().Count();
// //var oc42 = Database.PocoData.GetConverters().Count();
// //var oc43 = Database.GetAutoMappers().Count();
// //var oc44 = Database.GetMultiPocoFactories().Count();
// //var i2 = DatabaseContext.Database.Fetch<dynamic>("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS");
// //var r2 =
// // i2.Select(
// // item =>
// // new ColumnInfo(item.TABLE_NAME, item.COLUMN_NAME, item.ORDINAL_POSITION, item.COLUMN_DEFAULT,
// // item.IS_NULLABLE, item.DATA_TYPE)).ToList();
// var pocoData = Database.PocoData.GetCachedPocoData();
// Console.WriteLine("GetCachedPocoData: " + pocoData.Count());
// foreach (var keyValuePair in pocoData)
// {
// Console.WriteLine(keyValuePair.Value.GetFactories().Count());
// }
// Console.WriteLine("GetConverters: " + Database.PocoData.GetConverters().Count());
// Console.WriteLine("GetAutoMappers: " + Database.GetAutoMappers().Count());
// Console.WriteLine("GetMultiPocoFactories: " + Database.GetMultiPocoFactories().Count());
// //Assert.AreEqual(oc11, oc21);
// //Assert.AreEqual(oc12, oc22);
// //Assert.AreEqual(oc13, oc23);
// //Assert.AreEqual(oc14, oc24);
// //Assert.AreEqual(oc31, oc41);
// //Assert.AreEqual(oc32, oc42);
// //Assert.AreEqual(oc33, oc43);
// //Assert.AreEqual(oc34, oc44);
// }
// public void CreateStuff()
// {
// var contentType1 = MockedContentTypes.CreateTextpageContentType("test1", "test1");
// var contentType2 = MockedContentTypes.CreateTextpageContentType("test2", "test2");
// var contentType3 = MockedContentTypes.CreateTextpageContentType("test3", "test3");
// ServiceContext.ContentTypeService.Save(new[] { contentType1, contentType2, contentType3 });
// contentType1.AllowedContentTypes = new[]
// {
// new ContentTypeSort(new Lazy<int>(() => contentType2.Id), 0, contentType2.Alias),
// new ContentTypeSort(new Lazy<int>(() => contentType3.Id), 1, contentType3.Alias)
// };
// contentType2.AllowedContentTypes = new[]
// {
// new ContentTypeSort(new Lazy<int>(() => contentType1.Id), 0, contentType1.Alias),
// new ContentTypeSort(new Lazy<int>(() => contentType3.Id), 1, contentType3.Alias)
// };
// contentType3.AllowedContentTypes = new[]
// {
// new ContentTypeSort(new Lazy<int>(() => contentType1.Id), 0, contentType1.Alias),
// new ContentTypeSort(new Lazy<int>(() => contentType2.Id), 1, contentType2.Alias)
// };
// ServiceContext.ContentTypeService.Save(new[] { contentType1, contentType2, contentType3 });
// var roots = MockedContent.CreateTextpageContent(contentType1, -1, 3);
// ServiceContext.ContentService.Save(roots);
// foreach (var root in roots)
// {
// var item1 = MockedContent.CreateTextpageContent(contentType1, root.Id, 3);
// var item2 = MockedContent.CreateTextpageContent(contentType2, root.Id, 3);
// var item3 = MockedContent.CreateTextpageContent(contentType3, root.Id, 3);
// ServiceContext.ContentService.Save(item1.Concat(item2).Concat(item3));
// }
// }
// }
//}
@@ -1,14 +1,195 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Tests.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
namespace Umbraco.Tests.Persistence
{
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
[TestFixture, NUnit.Framework.Ignore]
public class PetaPocoCachesTest : BaseServiceTest
{
/// <summary>
/// This tests the peta poco caches
/// </summary>
/// <remarks>
/// This test WILL fail. This is because we cannot stop PetaPoco from creating more cached items for queries such as
/// ContentTypeRepository.GetAll(1,2,3,4);
/// when combined with other GetAll queries that pass in an array of Ids, each query generated for different length
/// arrays will produce a unique query which then gets added to the cache.
///
/// This test confirms this, if you analyze the DIFFERENCE output below you can see why the cached queries grow.
/// </remarks>
[Test]
public void Check_Peta_Poco_Caches()
{
var result = new List<Tuple<double, int, IEnumerable<string>>>();
Database.PocoData.UseLongKeys = true;
for (int i = 0; i < 2; i++)
{
int id1, id2, id3;
string alias;
CreateStuff(out id1, out id2, out id3, out alias);
QueryStuff(id1, id2, id3, alias);
double totalBytes1;
IEnumerable<string> keys;
Console.Write(Database.PocoData.PrintDebugCacheReport(out totalBytes1, out keys));
result.Add(new Tuple<double, int, IEnumerable<string>>(totalBytes1, keys.Count(), keys));
}
for (int index = 0; index < result.Count; index++)
{
var tuple = result[index];
Console.WriteLine("Bytes: {0}, Delegates: {1}", tuple.Item1, tuple.Item2);
if (index != 0)
{
Console.WriteLine("----------------DIFFERENCE---------------------");
var diff = tuple.Item3.Except(result[index - 1].Item3);
foreach (var d in diff)
{
Console.WriteLine(d);
}
}
}
var allByteResults = result.Select(x => x.Item1).Distinct();
var totalKeys = result.Select(x => x.Item2).Distinct();
Assert.AreEqual(1, allByteResults.Count());
Assert.AreEqual(1, totalKeys.Count());
}
[Test]
public void Verify_Memory_Expires()
{
Database.PocoData.SlidingExpirationSeconds = 2;
var managedCache = new Database.ManagedCache();
int id1, id2, id3;
string alias;
CreateStuff(out id1, out id2, out id3, out alias);
QueryStuff(id1, id2, id3, alias);
var count1 = managedCache.GetCache().GetCount();
Console.WriteLine("Keys = " + count1);
Assert.Greater(count1, 0);
Thread.Sleep(10000);
var count2 = managedCache.GetCache().GetCount();
Console.WriteLine("Keys = " + count2);
Assert.Less(count2, count1);
}
private void QueryStuff(int id1, int id2, int id3, string alias1)
{
var contentService = ServiceContext.ContentService;
contentService.CountDescendants(id3);
contentService.CountChildren(id3);
contentService.Count(contentTypeAlias: alias1);
contentService.Count();
contentService.GetById(Guid.NewGuid());
contentService.GetByLevel(2);
contentService.GetChildren(id1);
contentService.GetDescendants(id2);
contentService.GetVersions(id3);
contentService.GetRootContent();
contentService.GetContentForExpiration();
contentService.GetContentForRelease();
contentService.GetContentInRecycleBin();
((ContentService)contentService).GetPublishedDescendants(new Content("Test", -1, new ContentType(-1))
{
Id = id1,
Path = "-1," + id1
});
contentService.GetByVersion(Guid.NewGuid());
}
private void CreateStuff(out int id1, out int id2, out int id3, out string alias)
{
var contentService = ServiceContext.ContentService;
var ctAlias = "umbTextpage" + Guid.NewGuid().ToString("N");
alias = ctAlias;
for (int i = 0; i < 20; i++)
{
contentService.CreateContentWithIdentity("Test", -1, "umbTextpage", 0);
}
var contentTypeService = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateSimpleContentType(ctAlias, "test Doc Type");
contentTypeService.Save(contentType);
for (int i = 0; i < 20; i++)
{
contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0);
}
var parent = contentService.CreateContentWithIdentity("Test", -1, ctAlias, 0);
id1 = parent.Id;
for (int i = 0; i < 20; i++)
{
contentService.CreateContentWithIdentity("Test", parent, ctAlias);
}
IContent current = parent;
for (int i = 0; i < 20; i++)
{
current = contentService.CreateContentWithIdentity("Test", current, ctAlias);
}
contentType = MockedContentTypes.CreateSimpleContentType("umbMandatory" + Guid.NewGuid().ToString("N"), "Mandatory Doc Type", true);
contentType.PropertyGroups.First().PropertyTypes.Add(
new PropertyType(Guid.NewGuid(), DataTypeDatabaseType.Ntext)
{
Alias = "tags",
DataTypeDefinitionId = 1041
});
contentTypeService.Save(contentType);
var content1 = MockedContent.CreateSimpleContent(contentType, "Tagged content 1", -1);
contentService.Publish(content1);
id2 = content1.Id;
var content2 = MockedContent.CreateSimpleContent(contentType, "Tagged content 2", -1);
contentService.Publish(content2);
id3 = content2.Id;
contentService.MoveToRecycleBin(content1);
}
}
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
[TestFixture]
public class PetaPocoExtensionsTest : BaseDatabaseFactoryTest
@@ -21,7 +21,7 @@ namespace Umbraco.Tests.Persistence.Querying
.InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]")
.InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]")
.InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]")
.Where("[umbracoNode].[nodeObjectType] = 'c66ba18e-eaf3-4cff-8a22-41b16d66a972'");
.Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972"));
var sql = new Sql();
sql.Select("*")
@@ -36,6 +36,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
@@ -50,8 +56,8 @@ namespace Umbraco.Tests.Persistence.Querying
.InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]")
.InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]")
.InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]")
.Where("[umbracoNode].[nodeObjectType] = 'c66ba18e-eaf3-4cff-8a22-41b16d66a972'")
.Where("[umbracoNode].[id] = 1050");
.Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972"))
.Where("[umbracoNode].[id] = @0", 1050);
var sql = new Sql();
sql.Select("*")
@@ -67,6 +73,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
@@ -82,9 +94,9 @@ namespace Umbraco.Tests.Persistence.Querying
.InnerJoin("[cmsContentVersion]").On("[cmsDocument].[versionId] = [cmsContentVersion].[VersionId]")
.InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]")
.InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]")
.Where("[umbracoNode].[nodeObjectType] = 'c66ba18e-eaf3-4cff-8a22-41b16d66a972'")
.Where("[umbracoNode].[id] = 1050")
.Where("[cmsContentVersion].[VersionId] = '2b543516-a944-4ee6-88c6-8813da7aaa07'")
.Where("[umbracoNode].[nodeObjectType] = @0", new Guid("c66ba18e-eaf3-4cff-8a22-41b16d66a972"))
.Where("[umbracoNode].[id] = @0", 1050)
.Where("[cmsContentVersion].[VersionId] = @0", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07"))
.OrderBy("[cmsContentVersion].[VersionDate] DESC");
var sql = new Sql();
@@ -102,6 +114,11 @@ namespace Umbraco.Tests.Persistence.Querying
.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
@@ -116,8 +133,8 @@ namespace Umbraco.Tests.Persistence.Querying
expected.Select("*");
expected.From("[cmsPropertyData]");
expected.InnerJoin("[cmsPropertyType]").On("[cmsPropertyData].[propertytypeid] = [cmsPropertyType].[id]");
expected.Where("[cmsPropertyData].[contentNodeId] = 1050");
expected.Where("[cmsPropertyData].[versionId] = '2b543516-a944-4ee6-88c6-8813da7aaa07'");
expected.Where("[cmsPropertyData].[contentNodeId] = @0", 1050);
expected.Where("[cmsPropertyData].[versionId] = @0", new Guid("2b543516-a944-4ee6-88c6-8813da7aaa07"));
var sql = new Sql();
sql.Select("*")
@@ -128,6 +145,11 @@ namespace Umbraco.Tests.Persistence.Querying
.Where<PropertyDataDto>(x => x.VersionId == versionId);
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
@@ -22,8 +22,8 @@ namespace Umbraco.Tests.Persistence.Querying
.On("[cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId]")
.InnerJoin("[umbracoNode]")
.On("[cmsContentType].[nodeId] = [umbracoNode].[id]")
.Where("[umbracoNode].[nodeObjectType] = 'a2cb7800-f571-4787-9638-bc48539a0efb'")
.Where("[cmsDocumentType].[IsDefault] = 1");
.Where("[umbracoNode].[nodeObjectType] = @0", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb"))
.Where("[cmsDocumentType].[IsDefault] = @0", true);
var sql = new Sql();
sql.Select("*")
@@ -37,6 +37,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
@@ -52,9 +58,9 @@ namespace Umbraco.Tests.Persistence.Querying
.On("[cmsContentType].[nodeId] = [cmsDocumentType].[contentTypeNodeId]")
.InnerJoin("[umbracoNode]")
.On("[cmsContentType].[nodeId] = [umbracoNode].[id]")
.Where("[umbracoNode].[nodeObjectType] = 'a2cb7800-f571-4787-9638-bc48539a0efb'")
.Where("[cmsDocumentType].[IsDefault] = 1")
.Where("[umbracoNode].[id] = 1050");
.Where("[umbracoNode].[nodeObjectType] = @0", new Guid("a2cb7800-f571-4787-9638-bc48539a0efb"))
.Where("[cmsDocumentType].[IsDefault] = @0", true)
.Where("[umbracoNode].[id] = @0", 1050);
var sql = new Sql();
sql.Select("*")
@@ -64,11 +70,17 @@ namespace Umbraco.Tests.Persistence.Querying
.InnerJoin<NodeDto>()
.On<ContentTypeDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectType)
.Where<DocumentTypeDto>(x => x.IsDefault == true)
.Where<DocumentTypeDto>(x => x.IsDefault)
.Where<NodeDto>(x => x.NodeId == 1050);
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
@@ -100,7 +112,7 @@ namespace Umbraco.Tests.Persistence.Querying
var expected = new Sql();
expected.Select("*")
.From("[cmsContentTypeAllowedContentType]")
.Where("[cmsContentTypeAllowedContentType].[Id] = 1050");
.Where("[cmsContentTypeAllowedContentType].[Id] = @0", 1050);
var sql = new Sql();
sql.Select("*")
@@ -109,6 +121,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
@@ -120,7 +138,7 @@ namespace Umbraco.Tests.Persistence.Querying
.From("[cmsPropertyTypeGroup]")
.RightJoin("[cmsPropertyType]").On("[cmsPropertyTypeGroup].[id] = [cmsPropertyType].[propertyTypeGroupId]")
.InnerJoin("[cmsDataType]").On("[cmsPropertyType].[dataTypeId] = [cmsDataType].[nodeId]")
.Where("[cmsPropertyType].[contentTypeId] = 1050");
.Where("[cmsPropertyType].[contentTypeId] = @0", 1050);
var sql = new Sql();
sql.Select("*")
@@ -133,6 +151,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
}
@@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Querying
expected.Select("*")
.From("[cmsDataType]")
.InnerJoin("[umbracoNode]").On("[cmsDataType].[nodeId] = [umbracoNode].[id]")
.Where("[umbracoNode].[nodeObjectType] = '30a2a501-1978-4ddb-a57b-f7efed43ba3c'");
.Where("[umbracoNode].[nodeObjectType] = @0", new Guid("30a2a501-1978-4ddb-a57b-f7efed43ba3c"));
var sql = new Sql();
sql.Select("*")
@@ -30,6 +30,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
}
@@ -23,7 +23,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("upper([umbracoNode].[path]) like '-1%'", result);
Assert.AreEqual("upper([umbracoNode].[path]) LIKE upper(@0)", result);
Assert.AreEqual("-1%", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
@@ -36,7 +37,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("[umbracoNode].[parentID] = -1", result);
Assert.AreEqual("[umbracoNode].[parentID] = @0", result);
Assert.AreEqual(-1, modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
@@ -48,7 +50,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("[umbracoUser].[userLogin] = 'hello@@world.com'", result);
Assert.AreEqual("[umbracoUser].[userLogin] = @0", result);
Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
@@ -60,7 +63,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("upper([umbracoUser].[userLogin]) = 'HELLO@@WORLD.COM'", result);
Assert.AreEqual("upper([umbracoUser].[userLogin]) = upper(@0)", result);
Assert.AreEqual("hello@world.com", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
@@ -75,7 +79,9 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Model to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("upper(`umbracoUser`.`userLogin`) = 'MYDOMAIN\\\\MYUSER'", result);
Assert.AreEqual("upper(`umbracoUser`.`userLogin`) = upper(@0)", result);
Assert.AreEqual("mydomain\\myuser", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
@@ -90,7 +96,8 @@ namespace Umbraco.Tests.Persistence.Querying
Console.WriteLine("Poco to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("upper(`umbracoUser`.`userLogin`) like 'MYDOMAIN\\\\MYUSER%'", result);
Assert.AreEqual("upper(`umbracoUser`.`userLogin`) LIKE upper(@0)", result);
Assert.AreEqual("mydomain\\myuser%", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
}
@@ -20,7 +20,7 @@ namespace Umbraco.Tests.Persistence.Querying
.From("[cmsContentVersion]")
.InnerJoin("[cmsContent]").On("[cmsContentVersion].[ContentId] = [cmsContent].[nodeId]")
.InnerJoin("[umbracoNode]").On("[cmsContent].[nodeId] = [umbracoNode].[id]")
.Where("[umbracoNode].[nodeObjectType] = 'b796f64c-1f99-4ffb-b886-4bf4bc011a9c'");
.Where("[umbracoNode].[nodeObjectType] = @0", new Guid("b796f64c-1f99-4ffb-b886-4bf4bc011a9c"));
var sql = new Sql();
sql.Select("*")
@@ -33,6 +33,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
}
@@ -19,7 +19,7 @@ namespace Umbraco.Tests.Persistence.Querying
expected.Select("*")
.From("[cmsContentType]")
.InnerJoin("[umbracoNode]").On("[cmsContentType].[nodeId] = [umbracoNode].[id]")
.Where("[umbracoNode].[nodeObjectType] = '4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e'");
.Where("[umbracoNode].[nodeObjectType] = @0", new Guid("4ea4382b-2f5a-4c2b-9587-ae9b3cf3602e"));
var sql = new Sql();
sql.Select("*")
@@ -30,6 +30,12 @@ namespace Umbraco.Tests.Persistence.Querying
Assert.That(sql.SQL, Is.EqualTo(expected.SQL));
Assert.AreEqual(expected.Arguments.Length, sql.Arguments.Length);
for (int i = 0; i < expected.Arguments.Length; i++)
{
Assert.AreEqual(expected.Arguments[i], sql.Arguments[i]);
}
Console.WriteLine(sql.SQL);
}
}
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using NUnit.Framework;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
@@ -6,12 +7,141 @@ using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Tests.TestHelpers;
using Umbraco.Core.Persistence.Querying;
namespace Umbraco.Tests.Persistence.Querying
{
[TestFixture]
public class PetaPocoSqlTests : BaseUsingSqlCeSyntax
{
//x =>
[Test]
public void Where_Clause_With_Starts_With_Additional_Parameters()
{
var content = new NodeDto() { NodeId = 123, Path = "-1,123" };
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Path.SqlStartsWith(content.Path, TextColumnType.NVarchar));
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[path]) LIKE upper(@0))", sql.SQL.Replace("\n", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(content.Path + "%", sql.Arguments[0]);
}
[Test]
public void Where_Clause_With_Starts_With_By_Variable()
{
var content = new NodeDto() {NodeId = 123, Path = "-1,123"};
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Path.StartsWith(content.Path) && x.NodeId != content.NodeId);
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[path]) LIKE upper(@0) AND [umbracoNode].[id] <> @1)", sql.SQL.Replace("\n", " "));
Assert.AreEqual(2, sql.Arguments.Length);
Assert.AreEqual(content.Path + "%", sql.Arguments[0]);
Assert.AreEqual(content.NodeId, sql.Arguments[1]);
}
[Test]
public void Where_Clause_With_Not_Starts_With()
{
var level = 1;
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Level == level && !x.Path.StartsWith("-20"));
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[level] = @0 AND NOT (upper([umbracoNode].[path]) LIKE upper(@1)))", sql.SQL.Replace("\n", " "));
Assert.AreEqual(2, sql.Arguments.Length);
Assert.AreEqual(level, sql.Arguments[0]);
Assert.AreEqual("-20%", sql.Arguments[1]);
}
[Test]
public void Where_Clause_With_Equals_Clause()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Text.Equals("Hello@world.com"));
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) = upper(@0))", sql.SQL.Replace("\n", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("Hello@world.com", sql.Arguments[0]);
}
[Test]
public void Where_Clause_With_False_Boolean()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => !x.Trashed);
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (NOT ([umbracoNode].[trashed] = @0))", sql.SQL.Replace("\n", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(true, sql.Arguments[0]);
}
[Test]
public void Where_Clause_With_Boolean()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Trashed);
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[trashed] = @0)", sql.SQL.Replace("\n", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(true, sql.Arguments[0]);
}
[Test]
public void Where_Clause_With_ToUpper()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Text.ToUpper() == "hello".ToUpper());
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) = upper(@0))", sql.SQL.Replace("\n", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("hello", sql.Arguments[0]);
}
[Test]
public void Where_Clause_With_ToString()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Text == 1.ToString());
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[text] = @0)", sql.SQL.Replace("\n", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("1", sql.Arguments[0]);
}
[Test]
public void Where_Clause_With_Wildcard()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Text.StartsWith("D"));
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE (upper([umbracoNode].[text]) LIKE upper(@0))", sql.SQL.Replace("\n", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual("D%", sql.Arguments[0]);
}
[Test]
public void Where_Clause_Single_Constant()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.NodeId == 2);
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[id] = @0)", sql.SQL.Replace("\n", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(2, sql.Arguments[0]);
}
[Test]
public void Where_Clause_And_Constant()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.NodeId != 2 && x.NodeId != 3);
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[id] <> @0 AND [umbracoNode].[id] <> @1)", sql.SQL.Replace("\n", " "));
Assert.AreEqual(2, sql.Arguments.Length);
Assert.AreEqual(2, sql.Arguments[0]);
Assert.AreEqual(3, sql.Arguments[1]);
}
[Test]
public void Where_Clause_Or_Constant()
{
var sql = new Sql("SELECT *").From<NodeDto>().Where<NodeDto>(x => x.Text == "hello" || x.NodeId == 3);
Assert.AreEqual("SELECT * FROM [umbracoNode] WHERE ([umbracoNode].[text] = @0 OR [umbracoNode].[id] = @1)", sql.SQL.Replace("\n", " "));
Assert.AreEqual(2, sql.Arguments.Length);
Assert.AreEqual("hello", sql.Arguments[0]);
Assert.AreEqual(3, sql.Arguments[1]);
}
[Test]
public void Can_Select_From_With_Type()
@@ -78,7 +208,7 @@ namespace Umbraco.Tests.Persistence.Querying
public void Can_Use_Where_Predicate()
{
var expected = new Sql();
expected.Select("*").From("[cmsContent]").Where("[cmsContent].[nodeId] = 1045");
expected.Select("*").From("[cmsContent]").Where("[cmsContent].[nodeId] = @0", 1045);
var sql = new Sql();
sql.Select("*").From<ContentDto>().Where<ContentDto>(x => x.NodeId == 1045);
@@ -94,8 +224,8 @@ namespace Umbraco.Tests.Persistence.Querying
var expected = new Sql();
expected.Select("*")
.From("[cmsContent]")
.Where("[cmsContent].[nodeId] = 1045")
.Where("[cmsContent].[contentType] = 1050");
.Where("[cmsContent].[nodeId] = @0", 1045)
.Where("[cmsContent].[contentType] = @0", 1050);
var sql = new Sql();
sql.Select("*")
@@ -12,21 +12,7 @@ namespace Umbraco.Tests.Persistence.Querying
[TestFixture]
public class QueryBuilderTests : BaseUsingSqlCeSyntax
{
[Test]
public void Dates_Formatted_Properly()
{
var sql = new Sql();
sql.Select("*").From<DocumentDto>();
var dt = new DateTime(2013, 11, 21, 13, 25, 55);
var query = Query<IContent>.Builder.Where(x => x.ExpireDate <= dt);
var translator = new SqlTranslator<IContent>(sql, query);
var result = translator.Translate();
Assert.IsTrue(result.SQL.Contains("[expireDate] <= '2013-11-21 13:25:55'"));
}
[Test]
public void Can_Build_StartsWith_Query_For_IContent()
@@ -43,11 +29,15 @@ namespace Umbraco.Tests.Persistence.Querying
var result = translator.Translate();
var strResult = result.SQL;
string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (upper([umbracoNode].[path]) like '-1%')";
string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE (upper([umbracoNode].[path]) LIKE upper(@0))";
// Assert
Assert.That(strResult, Is.Not.Empty);
Assert.That(strResult, Is.EqualTo(expectedResult));
Assert.AreEqual(1, result.Arguments.Length);
Assert.AreEqual("-1%", sql.Arguments[0]);
Console.WriteLine(strResult);
}
@@ -66,11 +56,15 @@ namespace Umbraco.Tests.Persistence.Querying
var result = translator.Translate();
var strResult = result.SQL;
string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([umbracoNode].[parentID] = -1)";
string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([umbracoNode].[parentID] = @0)";
// Assert
Assert.That(strResult, Is.Not.Empty);
Assert.That(strResult, Is.EqualTo(expectedResult));
Assert.AreEqual(1, result.Arguments.Length);
Assert.AreEqual(-1, sql.Arguments[0]);
Console.WriteLine(strResult);
}
@@ -89,11 +83,14 @@ namespace Umbraco.Tests.Persistence.Querying
var result = translator.Translate();
var strResult = result.SQL;
string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([cmsContentType].[alias] = 'umbTextpage')";
string expectedResult = "SELECT *\nFROM umbracoNode\nWHERE ([cmsContentType].[alias] = @0)";
// Assert
Assert.That(strResult, Is.Not.Empty);
Assert.That(strResult, Is.EqualTo(expectedResult));
Assert.AreEqual(1, result.Arguments.Length);
Assert.AreEqual("umbTextpage", sql.Arguments[0]);
Console.WriteLine(strResult);
}
@@ -38,7 +38,11 @@ namespace Umbraco.Tests.Persistence.SyntaxProvider
FROM [cmsContentXml]
INNER JOIN [umbracoNode]
ON [cmsContentXml].[nodeId] = [umbracoNode].[id]
WHERE ([umbracoNode].[nodeObjectType] = 'b796f64c-1f99-4ffb-b886-4bf4bc011a9c')) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "), sql.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "));
WHERE ([umbracoNode].[nodeObjectType] = @0)) x)".Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "),
sql.SQL.Replace(Environment.NewLine, " ").Replace("\n", " ").Replace("\r", " "));
Assert.AreEqual(1, sql.Arguments.Length);
Assert.AreEqual(mediaObjectType, sql.Arguments[0]);
}
[NUnit.Framework.Ignore("This doesn't actually test anything")]
@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Web.Scheduling;
namespace Umbraco.Tests.Scheduling
{
[TestFixture]
public class BackgroundTaskRunnerTests
{
[Test]
public void Startup_And_Shutdown()
{
BackgroundTaskRunner<IBackgroundTask> tManager;
using (tManager = new BackgroundTaskRunner<IBackgroundTask>(true, true))
{
tManager.StartUp();
}
NUnit.Framework.Assert.IsFalse(tManager.IsRunning);
}
[Test]
public void Startup_Starts_Automatically()
{
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.Add(new MyTask());
NUnit.Framework.Assert.IsTrue(tManager.IsRunning);
}
}
[Test]
public void Task_Runs()
{
var myTask = new MyTask();
var waitHandle = new ManualResetEvent(false);
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.TaskCompleted += (sender, task) => waitHandle.Set();
tManager.Add(myTask);
//wait for ITasks to complete
waitHandle.WaitOne();
NUnit.Framework.Assert.IsTrue(myTask.Ended != default(DateTime));
}
}
[Test]
public void Many_Tasks_Run()
{
var tasks = new Dictionary<BaseTask, ManualResetEvent>();
for (var i = 0; i < 10; i++)
{
tasks.Add(new MyTask(), new ManualResetEvent(false));
}
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set();
tasks.ForEach(t => tManager.Add(t.Key));
//wait for all ITasks to complete
WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
foreach (var task in tasks)
{
NUnit.Framework.Assert.IsTrue(task.Key.Ended != default(DateTime));
}
}
}
[Test]
public void Tasks_Can_Keep_Being_Added_And_Will_Execute()
{
Func<Dictionary<BaseTask, ManualResetEvent>> getTasks = () =>
{
var newTasks = new Dictionary<BaseTask, ManualResetEvent>();
for (var i = 0; i < 10; i++)
{
newTasks.Add(new MyTask(), new ManualResetEvent(false));
}
return newTasks;
};
IDictionary<BaseTask, ManualResetEvent> tasks = getTasks();
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set();
//execute first batch
tasks.ForEach(t => tManager.Add(t.Key));
//wait for all ITasks to complete
WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
foreach (var task in tasks)
{
NUnit.Framework.Assert.IsTrue(task.Key.Ended != default(DateTime));
}
//execute another batch after a bit
Thread.Sleep(2000);
tasks = getTasks();
tasks.ForEach(t => tManager.Add(t.Key));
//wait for all ITasks to complete
WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
foreach (var task in tasks)
{
NUnit.Framework.Assert.IsTrue(task.Key.Ended != default(DateTime));
}
}
}
[Test]
public void Task_Queue_Will_Be_Completed_Before_Shutdown()
{
var tasks = new Dictionary<BaseTask, ManualResetEvent>();
for (var i = 0; i < 10; i++)
{
tasks.Add(new MyTask(), new ManualResetEvent(false));
}
BackgroundTaskRunner<BaseTask> tManager;
using (tManager = new BackgroundTaskRunner<BaseTask>(true, true))
{
tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set();
tasks.ForEach(t => tManager.Add(t.Key));
////wait for all ITasks to complete
//WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
tManager.Stop(false);
//immediate stop will block until complete - but since we are running on
// a single thread this doesn't really matter as the above will just process
// until complete.
tManager.Stop(true);
NUnit.Framework.Assert.AreEqual(0, tManager.TaskCount);
}
}
private class MyTask : BaseTask
{
public MyTask()
{
}
public override void Run()
{
Thread.Sleep(500);
}
public override void Cancel()
{
}
}
public abstract class BaseTask : IBackgroundTask
{
public Guid UniqueId { get; protected set; }
public abstract void Run();
public abstract void Cancel();
public DateTime Queued { get; set; }
public DateTime Started { get; set; }
public DateTime Ended { get; set; }
public virtual void Dispose()
{
Ended = DateTime.Now;
}
}
}
}
@@ -76,6 +76,30 @@ namespace Umbraco.Tests.Services
Assert.That(entities.Any(x => x.Trashed), Is.False);
}
[Test]
public void EntityService_Can_Get_Children_By_ParentId()
{
var service = ServiceContext.EntityService;
var entities = service.GetChildren(folderId);
Assert.That(entities.Any(), Is.True);
Assert.That(entities.Count(), Is.EqualTo(3));
Assert.That(entities.Any(x => x.Trashed), Is.False);
}
[Test]
public void EntityService_Can_Get_Descendants_By_ParentId()
{
var service = ServiceContext.EntityService;
var entities = service.GetDescendents(folderId);
Assert.That(entities.Any(), Is.True);
Assert.That(entities.Count(), Is.EqualTo(4));
Assert.That(entities.Any(x => x.Trashed), Is.False);
}
[Test]
public void EntityService_Throws_When_Getting_All_With_Invalid_Type()
{
@@ -129,7 +153,7 @@ namespace Umbraco.Tests.Services
var entities = service.GetAll(UmbracoObjectTypes.Media);
Assert.That(entities.Any(), Is.True);
Assert.That(entities.Count(), Is.EqualTo(3));
Assert.That(entities.Count(), Is.EqualTo(5));
//Assert.That(entities.Any(x => ((UmbracoEntity)x).UmbracoFile != string.Empty), Is.True);
Assert.That(
entities.Any(
@@ -140,6 +164,8 @@ namespace Umbraco.Tests.Services
private static bool _isSetup = false;
private int folderId;
public override void CreateTestData()
{
if (_isSetup == false)
@@ -150,8 +176,9 @@ namespace Umbraco.Tests.Services
//Create and Save folder-Media -> 1050
var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031);
var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1);
var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1);
ServiceContext.MediaService.Save(folder, 0);
folderId = folder.Id;
//Create and Save image-Media -> 1051
var imageMediaType = ServiceContext.ContentTypeService.GetMediaType(1032);
@@ -162,6 +189,12 @@ namespace Umbraco.Tests.Services
var fileMediaType = ServiceContext.ContentTypeService.GetMediaType(1033);
var file = MockedMedia.CreateMediaFile(fileMediaType, folder.Id);
ServiceContext.MediaService.Save(file, 0);
var subfolder = MockedMedia.CreateMediaFolder(folderMediaType, folder.Id);
ServiceContext.MediaService.Save(subfolder, 0);
var subfolder2 = MockedMedia.CreateMediaFolder(folderMediaType, subfolder.Id);
ServiceContext.MediaService.Save(subfolder2, 0);
}
}
@@ -93,15 +93,20 @@ namespace Umbraco.Tests.Services
ServiceContext.MemberTypeService.Save(memberType);
IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test");
ServiceContext.MemberService.Save(member);
//need to test with '@' symbol in the lookup
IMember member2 = MockedMember.CreateSimpleMember(memberType, "test2", "test2@test.com", "pass", "test2@test.com");
ServiceContext.MemberService.Save(member2);
ServiceContext.MemberService.AddRole("MyTestRole1");
ServiceContext.MemberService.AddRole("MyTestRole2");
ServiceContext.MemberService.AddRole("MyTestRole3");
ServiceContext.MemberService.AssignRoles(new[] { member.Id }, new[] { "MyTestRole1", "MyTestRole2" });
ServiceContext.MemberService.AssignRoles(new[] { member.Id, member2.Id }, new[] { "MyTestRole1", "MyTestRole2" });
var memberRoles = ServiceContext.MemberService.GetAllRoles("test");
Assert.AreEqual(2, memberRoles.Count());
var memberRoles2 = ServiceContext.MemberService.GetAllRoles("test2@test.com");
Assert.AreEqual(2, memberRoles2.Count());
}
[Test]
@@ -324,9 +329,12 @@ namespace Umbraco.Tests.Services
ServiceContext.MemberTypeService.Save(memberType);
IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test");
ServiceContext.MemberService.Save(member);
IMember member2 = MockedMember.CreateSimpleMember(memberType, "test", "test2@test.com", "pass", "test2@test.com");
ServiceContext.MemberService.Save(member2);
Assert.IsTrue(ServiceContext.MemberService.Exists("test"));
Assert.IsFalse(ServiceContext.MemberService.Exists("notFound"));
Assert.IsTrue(ServiceContext.MemberService.Exists("test2@test.com"));
}
[Test]
@@ -127,9 +127,10 @@ namespace Umbraco.Tests.Services
var userType = MockedUserType.CreateUserType();
ServiceContext.UserService.SaveUserType(userType);
var user = ServiceContext.UserService.CreateUserWithIdentity("JohnDoe", "john@umbraco.io", userType);
var user2 = ServiceContext.UserService.CreateUserWithIdentity("john2@umbraco.io", "john2@umbraco.io", userType);
Assert.IsTrue(ServiceContext.UserService.Exists("JohnDoe"));
Assert.IsFalse(ServiceContext.UserService.Exists("notFound"));
Assert.IsTrue(ServiceContext.UserService.Exists("john2@umbraco.io"));
}
[Test]
+1
View File
@@ -249,6 +249,7 @@
<Compile Include="PublishedContent\StronglyTypedModels\Textpage.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\TypedModelBase.cs" />
<Compile Include="PublishedContent\StronglyTypedModels\UmbracoTemplatePage`T.cs" />
<Compile Include="Scheduling\BackgroundTaskRunnerTests.cs" />
<Compile Include="Services\LocalizationServiceTests.cs" />
<Compile Include="Services\MemberServiceTests.cs" />
<Compile Include="Services\MediaServiceTests.cs" />
+1 -1
View File
@@ -1 +1 @@
<%@ Application Codebehind="Global.asax.cs" Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %>
<%@ Application Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %>
+13 -6
View File
@@ -101,9 +101,9 @@
<Project>{07fbc26b-2927-4a22-8d96-d644c667fecc}</Project>
<Name>UmbracoExamine</Name>
</ProjectReference>
<Reference Include="ClientDependency.Core">
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="ClientDependency.Core.Mvc">
<SpecificVersion>False</SpecificVersion>
@@ -172,12 +172,15 @@
<Reference Include="System.EnterpriseServices" />
<Reference Include="System.Net.Http, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.Formatting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Client.4.0.30506.0\lib\net40\System.Net.Http.Formatting.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.WebRequest, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web">
<Name>System.Web</Name>
@@ -196,9 +199,11 @@
</Reference>
<Reference Include="System.Web.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.Core.4.0.30506.0\lib\net40\System.Web.Http.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Http.WebHost, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.AspNet.WebApi.WebHost.4.0.30506.0\lib\net40\System.Web.Http.WebHost.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Web.Mvc, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<Private>True</Private>
@@ -2657,8 +2662,10 @@
<Folder Include="Views\Partials\" />
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0</VSToolsPath>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0</VSToolsPath>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
@@ -2675,9 +2682,9 @@ xcopy "$(ProjectDir)"..\packages\SqlServerCE.4.0.0.0\x86\*.* "$(TargetDir)x86\"
<WebProjectProperties>
<UseIIS>True</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>6220</DevelopmentServerPort>
<DevelopmentServerPort>6260</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>http://localhost:6220</IISUrl>
<IISUrl>http://localhost:6260</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>False</UseCustomServer>
<CustomServerUrl>
@@ -8,7 +8,7 @@
<h1>Create User</h1>
<div class="create-hold">
<p>You can now setup a new admin user to log into Umbraco, we recommend using a stong password for this (a password which is more than 4 characters and contains a mix of letters, numbers and symbols).
<p>You can now setup a new admin user to log into Umbraco, we recommend using a strong password for this (a password which is more than 4 characters and contains a mix of letters, numbers and symbols).
Please make a note of the chosen password.</p>
<p>The password can be changed once you have completed the installation and logged into the admin interface.</p>
</div>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
<package id="ClientDependency-Mvc" version="1.7.0.4" targetFramework="net40" />
<package id="Examine" version="0.1.57.2941" targetFramework="net40" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net40" />
+8 -1
View File
@@ -46,7 +46,7 @@
<compilation debug="true" xdt:Transform="SetAttributes(debug)" />
<trust xdt:Transform="Remove" />
<trust level="Medium" originUrl=".*" xdt:Transform="Insert" />
<!--<trust level="Medium" originUrl=".*" xdt:Transform="Insert" />-->
</system.web>
<runtime>
@@ -66,6 +66,13 @@
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="1.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
</dependentAssembly>
<dependentAssembly xdt:Transform="Remove"
xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='System.Net.Http']])"/>
<dependentAssembly xdt:Transform="Insert">
<assemblyIdentity name="System.Net.Http" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0"/>
</dependentAssembly>
<dependentAssembly xdt:Transform="Remove"
xdt:Locator="Condition(_defaultNamespace:assemblyIdentity[@name='System.Web.WebPages']])"/>
@@ -191,6 +191,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
}
LogHelper.Debug<PublishedMediaCache>(
"Could not retrieve media {0} from Examine index, reverting to looking up media via legacy library.GetMedia method",
() => id);
var media = global::umbraco.library.GetMedia(id, true);
if (media != null && media.Current != null)
{
@@ -212,6 +216,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
return ConvertFromXPathNavigator(media.Current);
}
LogHelper.Debug<PublishedMediaCache>(
"Could not retrieve media {0} from Examine index or from legacy library.GetMedia method",
() => id);
return null;
}
@@ -0,0 +1,363 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Web.Hosting;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Scheduling
{
/// <summary>
/// This is used to create a background task runner which will stay alive in the background of and complete
/// any tasks that are queued. It is web aware and will ensure that it is shutdown correctly when the app domain
/// is shutdown.
/// </summary>
/// <typeparam name="T"></typeparam>
internal class BackgroundTaskRunner<T> : IDisposable, IRegisteredObject
where T : IBackgroundTask
{
private readonly bool _dedicatedThread;
private readonly bool _persistentThread;
private readonly BlockingCollection<T> _tasks = new BlockingCollection<T>();
private Task _consumer;
private volatile bool _isRunning = false;
private static readonly object Locker = new object();
private CancellationTokenSource _tokenSource;
internal event EventHandler<TaskEventArgs<T>> TaskError;
internal event EventHandler<TaskEventArgs<T>> TaskStarting;
internal event EventHandler<TaskEventArgs<T>> TaskCompleted;
internal event EventHandler<TaskEventArgs<T>> TaskCancelled;
public BackgroundTaskRunner(bool dedicatedThread = false, bool persistentThread = false)
{
_dedicatedThread = dedicatedThread;
_persistentThread = persistentThread;
HostingEnvironment.RegisterObject(this);
}
public int TaskCount
{
get { return _tasks.Count; }
}
public bool IsRunning
{
get { return _isRunning; }
}
public TaskStatus TaskStatus
{
get { return _consumer.Status; }
}
public void Add(T task)
{
//add any tasks first
LogHelper.Debug<BackgroundTaskRunner<T>>(" Task added {0}", () => task.GetType());
_tasks.Add(task);
//ensure's everything is started
StartUp();
}
public void StartUp()
{
if (!_isRunning)
{
lock (Locker)
{
//double check
if (!_isRunning)
{
_isRunning = true;
//Create a new token source since this is a new proces
_tokenSource = new CancellationTokenSource();
StartConsumer();
LogHelper.Debug<BackgroundTaskRunner<T>>("Starting");
}
}
}
}
public void ShutDown()
{
lock (Locker)
{
_isRunning = false;
try
{
if (_consumer != null)
{
//cancel all operations
_tokenSource.Cancel();
try
{
_consumer.Wait();
}
catch (AggregateException e)
{
//NOTE: We are logging Debug because we are expecting these errors
LogHelper.Debug<BackgroundTaskRunner<T>>("AggregateException thrown with the following inner exceptions:");
// Display information about each exception.
foreach (var v in e.InnerExceptions)
{
var exception = v as TaskCanceledException;
if (exception != null)
{
LogHelper.Debug<BackgroundTaskRunner<T>>(" .Net TaskCanceledException: .Net Task ID {0}", () => exception.Task.Id);
}
else
{
LogHelper.Debug<BackgroundTaskRunner<T>>(" Exception: {0}", () => v.GetType().Name);
}
}
}
}
if (_tasks.Count > 0)
{
LogHelper.Debug<BackgroundTaskRunner<T>>("Processing remaining tasks before shutdown: {0}", () => _tasks.Count);
//now we need to ensure the remaining queue is processed if there's any remaining,
// this will all be processed on the current/main thread.
T remainingTask;
while (_tasks.TryTake(out remainingTask))
{
ConsumeTaskInternal(remainingTask);
}
}
LogHelper.Debug<BackgroundTaskRunner<T>>("Shutdown");
//disposing these is really optional since they'll be disposed immediately since they are no longer running
//but we'll put this here anyways.
if (_consumer != null && (_consumer.IsCompleted || _consumer.IsCanceled))
{
_consumer.Dispose();
}
}
catch (Exception ex)
{
LogHelper.Error<BackgroundTaskRunner<T>>("Error occurred shutting down task runner", ex);
}
finally
{
HostingEnvironment.UnregisterObject(this);
}
}
}
/// <summary>
/// Starts the consumer task
/// </summary>
private void StartConsumer()
{
var token = _tokenSource.Token;
_consumer = Task.Factory.StartNew(() =>
StartThread(token),
token,
_dedicatedThread ? TaskCreationOptions.LongRunning : TaskCreationOptions.None,
TaskScheduler.Default);
//if this is not a persistent thread, wait till it's done and shut ourselves down
// thus ending the thread or giving back to the thread pool. If another task is added
// another thread will spawn or be taken from the pool to process.
if (!_persistentThread)
{
_consumer.ContinueWith(task => ShutDown());
}
}
/// <summary>
/// Invokes a new worker thread to consume tasks
/// </summary>
/// <param name="token"></param>
private void StartThread(CancellationToken token)
{
// Was cancellation already requested?
if (token.IsCancellationRequested)
{
LogHelper.Info<BackgroundTaskRunner<T>>("Thread {0} was cancelled before it got started.", () => Thread.CurrentThread.ManagedThreadId);
token.ThrowIfCancellationRequested();
}
TakeAndConsumeTask(token);
}
/// <summary>
/// Trys to get a task from the queue, if there isn't one it will wait a second and try again
/// </summary>
/// <param name="token"></param>
private void TakeAndConsumeTask(CancellationToken token)
{
if (token.IsCancellationRequested)
{
LogHelper.Info<BackgroundTaskRunner<T>>("Thread {0} was cancelled.", () => Thread.CurrentThread.ManagedThreadId);
token.ThrowIfCancellationRequested();
}
//If this is true, the thread will stay alive and just wait until there is anything in the queue
// and process it. When there is nothing in the queue, the thread will just block until there is
// something to process.
//When this is false, the thread will process what is currently in the queue and once that is
// done, the thread will end and we will shutdown the process
if (_persistentThread)
{
//This will iterate over the collection, if there is nothing to take
// the thread will block until there is something available.
//We need to pass our cancellation token so that the thread will
// cancel when we shutdown
foreach (var t in _tasks.GetConsumingEnumerable(token))
{
ConsumeTaskCancellable(t, token);
}
//recurse and keep going
TakeAndConsumeTask(token);
}
else
{
T repositoryTask;
while (_tasks.TryTake(out repositoryTask))
{
ConsumeTaskCancellable(repositoryTask, token);
}
//the task will end here
}
}
internal void ConsumeTaskCancellable(T task, CancellationToken token)
{
if (token.IsCancellationRequested)
{
OnTaskCancelled(new TaskEventArgs<T>(task));
//NOTE: Since the task hasn't started this is pretty pointless so leaving it out.
LogHelper.Info<BackgroundTaskRunner<T>>("Task {0}) was cancelled.",
() => task.GetType());
token.ThrowIfCancellationRequested();
}
ConsumeTaskInternal(task);
}
private void ConsumeTaskInternal(T task)
{
try
{
OnTaskStarting(new TaskEventArgs<T>(task));
try
{
using (task)
{
task.Run();
}
}
catch (Exception e)
{
OnTaskError(new TaskEventArgs<T>(task, e));
throw;
}
OnTaskCompleted(new TaskEventArgs<T>(task));
}
catch (Exception ex)
{
LogHelper.Error<BackgroundTaskRunner<T>>("An error occurred consuming task", ex);
}
}
protected virtual void OnTaskError(TaskEventArgs<T> e)
{
var handler = TaskError;
if (handler != null) handler(this, e);
}
protected virtual void OnTaskStarting(TaskEventArgs<T> e)
{
var handler = TaskStarting;
if (handler != null) handler(this, e);
}
protected virtual void OnTaskCompleted(TaskEventArgs<T> e)
{
var handler = TaskCompleted;
if (handler != null) handler(this, e);
}
protected virtual void OnTaskCancelled(TaskEventArgs<T> e)
{
var handler = TaskCancelled;
if (handler != null) handler(this, e);
}
#region Disposal
private readonly object _disposalLocker = new object();
public bool IsDisposed { get; private set; }
~BackgroundTaskRunner()
{
this.Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (this.IsDisposed || !disposing)
return;
lock (_disposalLocker)
{
if (IsDisposed)
return;
DisposeResources();
IsDisposed = true;
}
}
protected virtual void DisposeResources()
{
ShutDown();
}
#endregion
public void Stop(bool immediate)
{
if (immediate == false)
{
LogHelper.Debug<BackgroundTaskRunner<T>>("Application is shutting down, waiting for tasks to complete");
Dispose();
}
else
{
//NOTE: this will thread block the current operation if the manager
// is still shutting down because the Shutdown operation is also locked
// by this same lock instance. This would only matter if Stop is called by ASP.Net
// on two different threads though, otherwise the current thread will just block normally
// until the app is shutdown
lock (Locker)
{
LogHelper.Info<BackgroundTaskRunner<T>>("Application is shutting down immediately");
}
}
}
}
}
@@ -0,0 +1,9 @@
using System;
namespace Umbraco.Web.Scheduling
{
internal interface IBackgroundTask : IDisposable
{
void Run();
}
}
+21 -11
View File
@@ -1,6 +1,7 @@
using System;
using System.Net;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Sync;
@@ -8,24 +9,33 @@ namespace Umbraco.Web.Scheduling
{
internal class KeepAlive
{
public static void Start(object sender)
public static void Start(ApplicationContext appContext)
{
using (DisposableTimer.DebugDuration<KeepAlive>(() => "Keep alive executing", () => "Keep alive complete"))
{
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl();
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(appContext);
var url = string.Format("{0}/ping.aspx", umbracoBaseUrl);
try
if (string.IsNullOrWhiteSpace(umbracoBaseUrl))
{
using (var wc = new WebClient())
{
wc.DownloadString(url);
}
LogHelper.Warn<KeepAlive>("No url for service (yet), skip.");
}
catch (Exception ee)
else
{
LogHelper.Error<KeepAlive>("Error in ping", ee);
var url = string.Format("{0}ping.aspx", umbracoBaseUrl.EnsureEndsWith('/'));
try
{
using (var wc = new WebClient())
{
wc.DownloadString(url);
}
}
catch (Exception ee)
{
LogHelper.Error<KeepAlive>(
string.Format("Error in ping. The base url used in the request was: {0}, see http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks documentation for details on setting a baseUrl if this is in error", umbracoBaseUrl)
, ee);
}
}
}
+12 -37
View File
@@ -2,38 +2,20 @@ using System;
using System.Web;
using System.Web.Caching;
using umbraco.BusinessLogic;
using Umbraco.Core;
using Umbraco.Core.Logging;
namespace Umbraco.Web.Scheduling
{
//TODO: Refactor this to use a normal scheduling processor!
internal class LogScrubber
internal class LogScrubber : DisposableObject, IBackgroundTask
{
// this is a raw copy of the legacy code in all its uglyness
private readonly ApplicationContext _appContext;
CacheItemRemovedCallback _onCacheRemove;
const string LogScrubberTaskName = "ScrubLogs";
public void Start()
public LogScrubber(ApplicationContext appContext)
{
// log scrubbing
AddTask(LogScrubberTaskName, GetLogScrubbingInterval());
}
private static int GetLogScrubbingInterval()
{
int interval = 24 * 60 * 60; //24 hours
try
{
if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1)
interval = global::umbraco.UmbracoSettings.CleaningMiliseconds;
}
catch (Exception e)
{
LogHelper.Error<Scheduler>("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e);
}
return interval;
_appContext = appContext;
}
private static int GetLogScrubbingMaximumAge()
@@ -52,26 +34,19 @@ namespace Umbraco.Web.Scheduling
}
private void AddTask(string name, int seconds)
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
{
_onCacheRemove = new CacheItemRemovedCallback(CacheItemRemoved);
HttpRuntime.Cache.Insert(name, seconds, null,
DateTime.Now.AddSeconds(seconds), System.Web.Caching.Cache.NoSlidingExpiration,
CacheItemPriority.NotRemovable, _onCacheRemove);
}
public void CacheItemRemoved(string k, object v, CacheItemRemovedReason r)
public void Run()
{
if (k.Equals(LogScrubberTaskName))
using (DisposableTimer.DebugDuration<LogScrubber>(() => "Log scrubbing executing", () => "Log scrubbing complete"))
{
ScrubLogs();
Log.CleanLogs(GetLogScrubbingMaximumAge());
}
AddTask(k, Convert.ToInt32(v));
}
private static void ScrubLogs()
{
Log.CleanLogs(GetLogScrubbingMaximumAge());
}
}
}
@@ -10,35 +10,60 @@ using Umbraco.Web.Mvc;
namespace Umbraco.Web.Scheduling
{
internal class ScheduledPublishing
internal class ScheduledPublishing : DisposableObject, IBackgroundTask
{
private readonly ApplicationContext _appContext;
private static bool _isPublishingRunning = false;
public void Start(ApplicationContext appContext)
public ScheduledPublishing(ApplicationContext appContext)
{
if (appContext == null) return;
_appContext = appContext;
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
{
}
public void Run()
{
if (_appContext == null) return;
using (DisposableTimer.DebugDuration<ScheduledPublishing>(() => "Scheduled publishing executing", () => "Scheduled publishing complete"))
{
{
if (_isPublishingRunning) return;
_isPublishingRunning = true;
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl(_appContext);
try
{
var umbracoBaseUrl = ServerEnvironmentHelper.GetCurrentServerUmbracoBaseUrl();
var url = string.Format("{0}/RestServices/ScheduledPublish/Index", umbracoBaseUrl);
using (var wc = new WebClient())
if (string.IsNullOrWhiteSpace(umbracoBaseUrl))
{
//pass custom the authorization header
wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(appContext));
LogHelper.Warn<ScheduledPublishing>("No url for service (yet), skip.");
}
else
{
var url = string.Format("{0}RestServices/ScheduledPublish/Index", umbracoBaseUrl.EnsureEndsWith('/'));
using (var wc = new WebClient())
{
//pass custom the authorization header
wc.Headers.Set("Authorization", AdminTokenAuthorizeAttribute.GetAuthHeaderTokenVal(_appContext));
var result = wc.UploadString(url, "");
var result = wc.UploadString(url, "");
}
}
}
catch (Exception ee)
{
LogHelper.Error<ScheduledPublishing>("An error occurred with the scheduled publishing", ee);
LogHelper.Error<ScheduledPublishing>(
string.Format("An error occurred with the scheduled publishing. The base url used in the request was: {0}, see http://our.umbraco.org/documentation/Using-Umbraco/Config-files/umbracoSettings/#ScheduledTasks documentation for details on setting a baseUrl if this is in error", umbracoBaseUrl)
, ee);
}
finally
{
@@ -46,7 +71,5 @@ namespace Umbraco.Web.Scheduling
}
}
}
}
}
+37 -27
View File
@@ -14,39 +14,19 @@ namespace Umbraco.Web.Scheduling
// would need to be a publicly available task (URL) which isn't really very good :(
// We should really be using the AdminTokenAuthorizeAttribute for this stuff
internal class ScheduledTasks
internal class ScheduledTasks : DisposableObject, IBackgroundTask
{
private readonly ApplicationContext _appContext;
private static readonly Hashtable ScheduledTaskTimes = new Hashtable();
private static bool _isPublishingRunning = false;
public void Start(ApplicationContext appContext)
public ScheduledTasks(ApplicationContext appContext)
{
using (DisposableTimer.DebugDuration<ScheduledTasks>(() => "Scheduled tasks executing", () => "Scheduled tasks complete"))
{
if (_isPublishingRunning) return;
_isPublishingRunning = true;
try
{
ProcessTasks();
}
catch (Exception ee)
{
LogHelper.Error<ScheduledTasks>("Error executing scheduled task", ee);
}
finally
{
_isPublishingRunning = false;
}
}
_appContext = appContext;
}
private static void ProcessTasks()
private void ProcessTasks()
{
var scheduledTasks = UmbracoSettings.ScheduledTasks;
if (scheduledTasks != null)
{
@@ -80,7 +60,7 @@ namespace Umbraco.Web.Scheduling
}
}
private static bool GetTaskByHttp(string url)
private bool GetTaskByHttp(string url)
{
var myHttpWebRequest = (HttpWebRequest)WebRequest.Create(url);
@@ -98,5 +78,35 @@ namespace Umbraco.Web.Scheduling
return false;
}
/// <summary>
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
/// </summary>
protected override void DisposeResources()
{
}
public void Run()
{
using (DisposableTimer.DebugDuration<ScheduledTasks>(() => "Scheduled tasks executing", () => "Scheduled tasks complete"))
{
if (_isPublishingRunning) return;
_isPublishingRunning = true;
try
{
ProcessTasks();
}
catch (Exception ee)
{
LogHelper.Error<ScheduledTasks>("Error executing scheduled task", ee);
}
finally
{
_isPublishingRunning = false;
}
}
}
}
}
+73 -35
View File
@@ -1,4 +1,6 @@
using System.Threading;
using System;
using System.Threading;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Sync;
@@ -9,60 +11,99 @@ namespace Umbraco.Web.Scheduling
/// Used to do the scheduling for tasks, publishing, etc...
/// </summary>
/// <remarks>
///
/// TODO: Much of this code is legacy and needs to be updated, there are a few new/better ways to do scheduling
/// in a web project nowadays.
///
/// //TODO: We need a much more robust way of handing scheduled tasks and also need to take into account app shutdowns during
/// a scheduled tasks operation
/// http://haacked.com/archive/2011/10/16/the-dangers-of-implementing-recurring-background-tasks-in-asp-net.aspx/
///
/// All tasks are run in a background task runner which is web aware and will wind down the task correctly instead of killing it completely when
/// the app domain shuts down.
/// </remarks>
internal sealed class Scheduler : ApplicationEventHandler
{
private static Timer _pingTimer;
private static Timer _schedulingTimer;
private static LogScrubber _scrubber;
private static BackgroundTaskRunner<ScheduledPublishing> _publishingRunner;
private static BackgroundTaskRunner<ScheduledTasks> _tasksRunner;
private static BackgroundTaskRunner<LogScrubber> _scrubberRunner;
private static Timer _logScrubberTimer;
private static volatile bool _started = false;
private static readonly object Locker = new object();
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
if (umbracoApplication.Context == null)
return;
LogHelper.Debug<Scheduler>(() => "Initializing the scheduler");
//subscribe to app init so we can subsribe to the application events
UmbracoApplicationBase.ApplicationInit += (sender, args) =>
{
var app = (HttpApplication)sender;
// time to setup the tasks
//subscribe to the end of a successful request (a handler actually executed)
app.PostRequestHandlerExecute += (o, eventArgs) =>
{
if (_started == false)
{
lock (Locker)
{
if (_started == false)
{
_started = true;
LogHelper.Debug<Scheduler>(() => "Initializing the scheduler");
// these are the legacy tasks
// just copied over here for backward compatibility
// of course we should have a proper scheduler, see #U4-809
// time to setup the tasks
//NOTE: It is important to note that we need to use the ctor for a timer without the 'state' object specified, this is in order
// to ensure that the timer itself is not GC'd since internally .net will pass itself in as the state object and that will keep it alive.
// There's references to this here: http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
// we also make these timers static to ensure further GC safety.
//We have 3 background runners that are web aware, if the app domain dies, these tasks will wind down correctly
_publishingRunner = new BackgroundTaskRunner<ScheduledPublishing>();
_tasksRunner = new BackgroundTaskRunner<ScheduledTasks>();
_scrubberRunner = new BackgroundTaskRunner<LogScrubber>();
// ping/keepalive
_pingTimer = new Timer(KeepAlive.Start);
_pingTimer.Change(60000, 300000);
//NOTE: It is important to note that we need to use the ctor for a timer without the 'state' object specified, this is in order
// to ensure that the timer itself is not GC'd since internally .net will pass itself in as the state object and that will keep it alive.
// There's references to this here: http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
// we also make these timers static to ensure further GC safety.
// scheduled publishing/unpublishing
_schedulingTimer = new Timer(PerformScheduling);
_schedulingTimer.Change(30000, 60000);
// ping/keepalive - NOTE: we don't use a background runner for this because it does not need to be web aware, if the app domain dies, no problem
_pingTimer = new Timer(state => KeepAlive.Start(applicationContext));
_pingTimer.Change(60000, 300000);
//log scrubbing
_scrubber = new LogScrubber();
_scrubber.Start();
// scheduled publishing/unpublishing
_schedulingTimer = new Timer(state => PerformScheduling(applicationContext));
_schedulingTimer.Change(60000, 60000);
//log scrubbing
_logScrubberTimer = new Timer(state => PerformLogScrub(applicationContext));
_logScrubberTimer.Change(60000, GetLogScrubbingInterval());
}
}
}
};
};
}
private int GetLogScrubbingInterval()
{
int interval = 24 * 60 * 60; //24 hours
try
{
if (global::umbraco.UmbracoSettings.CleaningMiliseconds > -1)
interval = global::umbraco.UmbracoSettings.CleaningMiliseconds;
}
catch (Exception e)
{
LogHelper.Error<Scheduler>("Unable to locate a log scrubbing interval. Defaulting to 24 horus", e);
}
return interval;
}
private static void PerformLogScrub(ApplicationContext appContext)
{
_scrubberRunner.Add(new LogScrubber(appContext));
}
/// <summary>
/// This performs all of the scheduling on the one timer
/// </summary>
/// <param name="sender"></param>
/// <param name="appContext"></param>
/// <remarks>
/// No processing will be done if this server is a slave
/// </remarks>
private static void PerformScheduling(object sender)
private static void PerformScheduling(ApplicationContext appContext)
{
using (DisposableTimer.DebugDuration<Scheduler>(() => "Scheduling interval executing", () => "Scheduling interval complete"))
{
@@ -78,12 +119,10 @@ namespace Umbraco.Web.Scheduling
// then we will process the scheduling
//do the scheduled publishing
var scheduledPublishing = new ScheduledPublishing();
scheduledPublishing.Start(ApplicationContext.Current);
_publishingRunner.Add(new ScheduledPublishing(appContext));
//do the scheduled tasks
var scheduledTasks = new ScheduledTasks();
scheduledTasks.Start(ApplicationContext.Current);
_tasksRunner.Add(new ScheduledTasks(appContext));
break;
case CurrentServerEnvironmentStatus.Slave:
@@ -96,6 +135,5 @@ namespace Umbraco.Web.Scheduling
}
}
}
}
}
@@ -0,0 +1,22 @@
using System;
namespace Umbraco.Web.Scheduling
{
internal class TaskEventArgs<T> : EventArgs
where T : IBackgroundTask
{
public T Task { get; private set; }
public Exception Exception { get; private set; }
public TaskEventArgs(T task)
{
Task = task;
}
public TaskEventArgs(T task, Exception exception)
{
Task = task;
Exception = exception;
}
}
}
+9 -4
View File
@@ -93,9 +93,9 @@
<Project>{07fbc26b-2927-4a22-8d96-d644c667fecc}</Project>
<Name>UmbracoExamine</Name>
</ProjectReference>
<Reference Include="ClientDependency.Core">
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="CookComputing.XmlRpcV2, Version=2.5.0.0, Culture=neutral, PublicKeyToken=a7d6e17aa302004d, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
@@ -321,10 +321,13 @@
<Compile Include="Routing\ContentLastChanceFinderByNotFoundHandlers.cs" />
<Compile Include="Models\RegisterModel.cs" />
<Compile Include="Models\LoginModel.cs" />
<Compile Include="Scheduling\BackgroundTaskRunner.cs" />
<Compile Include="Scheduling\IBackgroundTask.cs" />
<Compile Include="Scheduling\KeepAlive.cs" />
<Compile Include="Scheduling\LogScrubber.cs" />
<Compile Include="Scheduling\ScheduledPublishing.cs" />
<Compile Include="Scheduling\ScheduledTasks.cs" />
<Compile Include="Scheduling\TaskEventArgs.cs" />
<Compile Include="Security\MembershipHelper.cs" />
<Compile Include="Security\Providers\MembersMembershipProvider.cs" />
<Compile Include="Security\Providers\MembersRoleProvider.cs" />
@@ -2041,8 +2044,10 @@
</ItemGroup>
<ItemGroup />
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">11.0</VisualStudioVersion>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0</VSToolsPath>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0</VSToolsPath>
<VSToolsPath Condition="exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0\WebApplications\Microsoft.WebApplication.targets')">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
<package id="Examine" version="0.1.57.2941" targetFramework="net40" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net40" />
<package id="Lucene.Net" version="2.9.4.1" targetFramework="net40" />
+55 -48
View File
@@ -1,6 +1,7 @@
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Mail;
using System.Text;
@@ -8,35 +9,31 @@ using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using Newtonsoft.Json;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Configuration;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.Cache;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Templates;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic;
using umbraco.cms.businesslogic.media;
using umbraco.cms.businesslogic.member;
using umbraco.cms.businesslogic.propertytype;
using umbraco.cms.businesslogic.relation;
using umbraco.cms.businesslogic.web;
using umbraco.cms.helpers;
using umbraco.presentation.cache;
using umbraco.scripting;
using umbraco.DataLayer;
using System.Web.Security;
using umbraco.cms.businesslogic.language;
using Umbraco.Core.IO;
using System.Collections;
using System.Collections.Generic;
using umbraco.cms.businesslogic.cache;
using umbraco.NodeFactory;
using Language = umbraco.cms.businesslogic.language.Language;
using Media = umbraco.cms.businesslogic.media.Media;
using Member = umbraco.cms.businesslogic.member.Member;
using PropertyType = umbraco.cms.businesslogic.propertytype.PropertyType;
using Relation = umbraco.cms.businesslogic.relation.Relation;
using UmbracoContext = umbraco.presentation.UmbracoContext;
using System.Linq;
namespace umbraco
{
@@ -470,46 +467,48 @@ namespace umbraco
{
if (UmbracoSettings.UmbracoLibraryCacheDuration > 0)
{
XPathNodeIterator retVal = ApplicationContext.Current.ApplicationCache.GetCacheItem(
var xml = ApplicationContext.Current.ApplicationCache.GetCacheItem(
string.Format(
"{0}_{1}_{2}", CacheKeys.MediaCacheKey, MediaId, Deep),
TimeSpan.FromSeconds(UmbracoSettings.UmbracoLibraryCacheDuration),
() => GetMediaDo(MediaId, Deep));
if (retVal != null)
return retVal;
if (xml != null)
{
//returning the root element of the Media item fixes the problem
return xml.CreateNavigator().Select("/");
}
}
else
{
return GetMediaDo(MediaId, Deep);
var xml = GetMediaDo(MediaId, Deep);
//returning the root element of the Media item fixes the problem
return xml.CreateNavigator().Select("/");
}
}
catch
catch(Exception ex)
{
LogHelper.Error<library>("An error occurred looking up media", ex);
}
XmlDocument xd = new XmlDocument();
LogHelper.Debug<library>("No media result for id {0}", () => MediaId);
var xd = new XmlDocument();
xd.LoadXml(string.Format("<error>No media is maching '{0}'</error>", MediaId));
return xd.CreateNavigator().Select("/");
}
private static XPathNodeIterator GetMediaDo(int mediaId, bool deep)
private static XElement GetMediaDo(int mediaId, bool deep)
{
var m = new Media(mediaId);
if (m.nodeObjectType == Media._objectType)
{
var mXml = new XmlDocument();
var xml = m.ToXml(mXml, deep);
//This will be null if the media isn't public (meaning it is in the trash)
if (xml == null) return null;
//TODO: This is an aweful way of loading in XML - it is very slow.
mXml.LoadXml(xml.OuterXml);
var xp = mXml.CreateNavigator();
var xpath = UmbracoSettings.UseLegacyXmlSchema ? "/node" : String.Format("/{0}", Casing.SafeAliasWithForcingCheck(m.ContentType.Alias));
return xp.Select(xpath);
}
return null;
var media = ApplicationContext.Current.Services.MediaService.GetById(mediaId);
if (media == null) return null;
var serializer = new EntityXmlSerializer();
var serialized = serializer.Serialize(
ApplicationContext.Current.Services.MediaService, ApplicationContext.Current.Services.DataTypeService,
media, deep);
return serialized;
}
/// <summary>
@@ -525,35 +524,42 @@ namespace umbraco
{
if (UmbracoSettings.UmbracoLibraryCacheDuration > 0)
{
var retVal = ApplicationContext.Current.ApplicationCache.GetCacheItem(
var xml = ApplicationContext.Current.ApplicationCache.GetCacheItem(
string.Format(
"{0}_{1}", CacheKeys.MemberLibraryCacheKey, MemberId),
TimeSpan.FromSeconds(UmbracoSettings.UmbracoLibraryCacheDuration),
() => GetMemberDo(MemberId));
if (retVal != null)
return retVal.CreateNavigator().Select("/");
if (xml != null)
{
return xml.CreateNavigator().Select("/");
}
}
else
{
return GetMemberDo(MemberId).CreateNavigator().Select("/");
}
}
catch
catch (Exception ex)
{
LogHelper.Error<library>("An error occurred looking up member", ex);
}
XmlDocument xd = new XmlDocument();
LogHelper.Debug<library>("No member result for id {0}", () => MemberId);
var xd = new XmlDocument();
xd.LoadXml(string.Format("<error>No member is maching '{0}'</error>", MemberId));
return xd.CreateNavigator().Select("/");
}
private static XmlDocument GetMemberDo(int MemberId)
private static XElement GetMemberDo(int MemberId)
{
Member m = new Member(MemberId);
XmlDocument mXml = new XmlDocument();
mXml.LoadXml(m.ToXml(mXml, false).OuterXml);
return mXml;
var member = ApplicationContext.Current.Services.MemberService.GetById(MemberId);
if (member == null) return null;
var serializer = new EntityXmlSerializer();
var serialized = serializer.Serialize(
ApplicationContext.Current.Services.DataTypeService, member);
return serialized;
}
/// <summary>
@@ -1352,8 +1358,9 @@ namespace umbraco
nav.MoveToId(HttpContext.Current.Items["pageID"].ToString());
return nav.Select(".");
}
catch
catch (Exception ex)
{
LogHelper.Error<library>("Could not retrieve current xml node", ex);
}
XmlDocument xd = new XmlDocument();
@@ -27,8 +27,9 @@ namespace umbraco.cms.presentation.Trees
//create singleton
private static readonly TreeDefinitionCollection instance = new TreeDefinitionCollection();
private static readonly ReaderWriterLockSlim Lock = new ReaderWriterLockSlim();
private static readonly object Locker = new object();
private static volatile bool _ensureTrees = false;
public static TreeDefinitionCollection Instance
{
get
@@ -127,7 +128,12 @@ namespace umbraco.cms.presentation.Trees
public void ReRegisterTrees()
{
EnsureTreesRegistered(true);
//clears the trees/flag so that they are lazily refreshed on next access
lock (Locker)
{
this.Clear();
_ensureTrees = false;
}
}
/// <summary>
@@ -136,70 +142,68 @@ namespace umbraco.cms.presentation.Trees
/// This will also store an instance of each tree object in the TreeDefinition class which should be
/// used when referencing all tree classes.
/// </summary>
private void EnsureTreesRegistered(bool clearFirst = false)
private void EnsureTreesRegistered()
{
using (var l = new UpgradeableReadLock(Lock))
{
if (clearFirst)
{
this.Clear();
}
if (_ensureTrees == false)
{
lock (Locker)
{
if (_ensureTrees == false)
{
//if we already have tree, exit
if (this.Count > 0)
return;
var foundITrees = PluginManager.Current.ResolveTrees();
l.UpgradeToWriteLock();
var objTrees = ApplicationTree.getAll();
var appTrees = new List<ApplicationTree>();
appTrees.AddRange(objTrees);
var apps = Application.getAll();
foreach (var type in foundITrees)
{
//find the Application tree's who's combination of assembly name and tree type is equal to
//the Type that was found's full name.
//Since a tree can exist in multiple applications we'll need to register them all.
//The logic of this has changed in 6.0: http://issues.umbraco.org/issue/U4-1360
// we will support the old legacy way but the normal way is to match on assembly qualified names
var appTreesForType = appTrees.FindAll(
tree =>
{
//match the type on assembly qualified name if the assembly attribute is empty or if the
// tree type contains a comma (meaning it is assembly qualified)
if (tree.AssemblyName.IsNullOrWhiteSpace() || tree.Type.Contains(","))
{
return tree.GetRuntimeType() == type;
}
//otherwise match using legacy match rules
return (string.Format("{0}.{1}", tree.AssemblyName, tree.Type).InvariantEquals(type.FullName));
}
);
foreach (var appTree in appTreesForType)
{
//find the Application object whos name is the same as our appTree ApplicationAlias
var app = apps.Find(
a => (a.alias == appTree.ApplicationAlias)
);
var def = new TreeDefinition(type, appTree, app);
this.Add(def);
}
}
//sort our trees with the sort order definition
this.Sort((t1, t2) => t1.Tree.SortOrder.CompareTo(t2.Tree.SortOrder));
_ensureTrees = true;
}
}
}
var foundITrees = PluginManager.Current.ResolveTrees();
var objTrees = ApplicationTree.getAll();
var appTrees = new List<ApplicationTree>();
appTrees.AddRange(objTrees);
var apps = Application.getAll();
foreach (var type in foundITrees)
{
//find the Application tree's who's combination of assembly name and tree type is equal to
//the Type that was found's full name.
//Since a tree can exist in multiple applications we'll need to register them all.
//The logic of this has changed in 6.0: http://issues.umbraco.org/issue/U4-1360
// we will support the old legacy way but the normal way is to match on assembly qualified names
var appTreesForType = appTrees.FindAll(
tree =>
{
//match the type on assembly qualified name if the assembly attribute is empty or if the
// tree type contains a comma (meaning it is assembly qualified)
if (tree.AssemblyName.IsNullOrWhiteSpace() || tree.Type.Contains(","))
{
return tree.GetRuntimeType() == type;
}
//otherwise match using legacy match rules
return (string.Format("{0}.{1}", tree.AssemblyName, tree.Type).InvariantEquals(type.FullName));
}
);
foreach (var appTree in appTreesForType)
{
//find the Application object whos name is the same as our appTree ApplicationAlias
var app = apps.Find(
a => (a.alias == appTree.ApplicationAlias)
);
var def = new TreeDefinition(type, appTree, app);
this.Add(def);
}
}
//sort our trees with the sort order definition
this.Sort((t1, t2) => t1.Tree.SortOrder.CompareTo(t2.Tree.SortOrder));
}
}
}
@@ -123,8 +123,8 @@ namespace UmbracoExamine.DataServices
{
try
{
var result = _applicationContext.DatabaseContext.Database.Fetch<PropertyAliasDto>("select distinct alias from cmsPropertyType order by alias");
return result.Select(r => r.Alias).ToList();
var result = _applicationContext.DatabaseContext.Database.Fetch<string>("select distinct alias from cmsPropertyType order by alias");
return result;
}
catch (Exception ex)
{
+113 -44
View File
@@ -26,6 +26,22 @@ namespace umbraco.BusinessLogic
internal const string AppConfigFileName = "applications.config";
private static string _appConfig;
private static readonly object Locker = new object();
private static IEnumerable<Application> _allAvailableSections;
private static volatile bool _isInitialized = false;
/// <summary>
/// Initializes the service with all available application plugins
/// </summary>
/// <param name="allAvailableSections">
/// All application plugins found in assemblies
/// </param>
/// <remarks>
/// This is used to populate the app.config file with any applications declared in plugins that don't exist in the file
/// </remarks>
internal static void Initialize(IEnumerable<Application> allAvailableSections)
{
_allAvailableSections = allAvailableSections;
}
/// <summary>
/// gets/sets the application.config file path
@@ -49,52 +65,75 @@ namespace umbraco.BusinessLogic
/// <summary>
/// The cache storage for all applications
/// </summary>
internal static List<Application> Apps
public static List<Application> GetSections()
{
get
{
return ApplicationContext.Current.ApplicationCache.GetCacheItem(
CacheKeys.ApplicationsCacheKey,
() =>
return ApplicationContext.Current.ApplicationCache.GetCacheItem<List<Application>>(
CacheKeys.ApplicationsCacheKey,
() =>
{
////used for unit tests
//if (_testApps != null)
// return _testApps;
var list = ReadFromXmlAndSort();
//On first access we need to do some initialization
if (_isInitialized == false)
{
lock (Locker)
{
////used for unit tests
//if (_testApps != null)
// return _testApps;
var tmp = new List<Application>();
try
if (_isInitialized == false)
{
LoadXml(doc =>
//now we can check the non-volatile flag
if (_allAvailableSections != null)
{
var hasChanges = false;
LoadXml(doc =>
{
foreach (var addElement in doc.Root.Elements("add").OrderBy(x =>
{
var sortOrderAttr = x.Attribute("sortOrder");
return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0;
}))
//Now, load in the xml structure and update it with anything that is not declared there and save the file.
//NOTE: On the first iteration here, it will lazily scan all apps, etc... this is because this ienumerable is lazy
// based on the ApplicationRegistrar - and as noted there this is not an ideal way to do things but were stuck like this
// currently because of the legacy assemblies and types not in the Core.
//Get all the trees not registered in the config
var unregistered = _allAvailableSections
.Where(x => list.Any(l => l.alias == x.alias) == false)
.ToArray();
hasChanges = unregistered.Any();
var count = 0;
foreach (var attr in unregistered)
{
var sortOrderAttr = addElement.Attribute("sortOrder");
tmp.Add(new Application(addElement.Attribute("name").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("icon").Value,
sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0));
doc.Root.Add(new XElement("add",
new XAttribute("alias", attr.alias),
new XAttribute("name", attr.name),
new XAttribute("icon", attr.icon),
new XAttribute("sortOrder", attr.sortOrder)));
count++;
}
}, false);
return tmp;
}
catch
{
//this is a bit of a hack that just ensures the application doesn't crash when the
//installer is run and there is no database or connection string defined.
//the reason this method may get called during the installation is that the
//SqlHelper of this class is shared amongst everything "Application" wide.
//don't save if there's no changes
return count > 0;
}, true);
//TODO: Perhaps we should log something here??
return null;
if (hasChanges)
{
//If there were changes, we need to re-read the structures from the XML
list = ReadFromXmlAndSort();
}
}
_isInitialized = true;
}
});
}
}
}
return list;
});
}
/// <summary>
@@ -200,7 +239,7 @@ namespace umbraco.BusinessLogic
[MethodImpl(MethodImplOptions.Synchronized)]
public static void MakeNew(string name, string alias, string icon)
{
MakeNew(name, alias, icon, Apps.Max(x => x.sortOrder) + 1);
MakeNew(name, alias, icon, GetSections().Max(x => x.sortOrder) + 1);
}
/// <summary>
@@ -224,6 +263,9 @@ namespace umbraco.BusinessLogic
new XAttribute("name", name),
new XAttribute("icon", icon),
new XAttribute("sortOrder", sortOrder)));
return true;
}, true);
//raise event
@@ -238,7 +280,7 @@ namespace umbraco.BusinessLogic
/// <returns></returns>
public static Application getByAlias(string appAlias)
{
return Apps.Find(t => t.alias == appAlias);
return GetSections().Find(t => t.alias == appAlias);
}
/// <summary>
@@ -259,6 +301,9 @@ namespace umbraco.BusinessLogic
LoadXml(doc =>
{
doc.Root.Elements("add").Where(x => x.Attribute("alias") != null && x.Attribute("alias").Value == this.alias).Remove();
return true;
}, true);
//raise event
@@ -271,7 +316,7 @@ namespace umbraco.BusinessLogic
/// <returns>Returns a Application Array</returns>
public static List<Application> getAll()
{
return Apps;
return GetSections();
}
/// <summary>
@@ -282,8 +327,32 @@ namespace umbraco.BusinessLogic
{
ApplicationStartupHandler.RegisterHandlers();
}
internal static void LoadXml(Action<XDocument> callback, bool saveAfterCallback)
private static List<Application> ReadFromXmlAndSort()
{
var tmp = new List<Application>();
LoadXml(doc =>
{
foreach (var addElement in doc.Root.Elements("add").OrderBy(x =>
{
var sortOrderAttr = x.Attribute("sortOrder");
return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0;
}))
{
var sortOrderAttr = addElement.Attribute("sortOrder");
tmp.Add(new Application(addElement.Attribute("name").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("icon").Value,
sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0));
}
return false;
}, false);
return tmp;
}
internal static void LoadXml(Func<XDocument, bool> callback, bool saveAfterCallbackIfChanged)
{
lock (Locker)
{
@@ -293,9 +362,9 @@ namespace umbraco.BusinessLogic
if (doc.Root != null)
{
callback.Invoke(doc);
var changed = callback.Invoke(doc);
if (saveAfterCallback)
if (saveAfterCallbackIfChanged && changed)
{
//ensure the folder is created!
Directory.CreateDirectory(Path.GetDirectoryName(AppConfigFilePath));
@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Linq;
@@ -12,8 +14,21 @@ using umbraco.interfaces;
namespace umbraco.BusinessLogic
{
public class ApplicationRegistrar : IApplicationStartupHandler
/// <summary>
/// A startup handler for putting the app config in the config file based on attributes found
/// </summary>
/// /// <remarks>
/// TODO: This is really not a very ideal process but the code is found here because tree plugins are in the Web project or the legacy business logic project.
/// Moving forward we can put the base tree plugin classes in the core and then this can all just be taken care of normally within the service.
/// </remarks>
public class ApplicationRegistrar : ApplicationEventHandler
{
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
//initialize the section service with a lazy collection of found app plugins
Application.Initialize(new LazyEnumerableSections());
}
private ISqlHelper _sqlHelper;
protected ISqlHelper SqlHelper
{
@@ -32,55 +47,51 @@ namespace umbraco.BusinessLogic
}
}
public ApplicationRegistrar()
/// <summary>
/// This class is here so that we can provide lazy access to tree scanning for when it is needed
/// </summary>
private class LazyEnumerableSections : IEnumerable<Application>
{
//don't do anything if the application is not configured!
if (!ApplicationContext.Current.IsConfigured)
return;
// Load all Applications by attribute and add them to the XML config
var types = PluginManager.Current.ResolveApplications();
//since applications don't populate their metadata from the attribute and because it is an interface,
//we need to interrogate the attributes for the data. Would be better to have a base class that contains
//metadata populated by the attribute. Oh well i guess.
var attrs = types.Select(x => x.GetCustomAttributes<ApplicationAttribute>(false).Single())
.Where(x => Application.getByAlias(x.Alias) == null);
var allAliases = Application.getAll().Select(x => x.alias).Concat(attrs.Select(x => x.Alias));
var inString = "'" + string.Join("','", allAliases) + "'";
Application.LoadXml(doc =>
public LazyEnumerableSections()
{
_lazySections = new Lazy<IEnumerable<Application>>(() =>
{
foreach (var attr in attrs)
{
doc.Root.Add(new XElement("add",
new XAttribute("alias", attr.Alias),
new XAttribute("name", attr.Name),
new XAttribute("icon", attr.Icon),
new XAttribute("sortOrder", attr.SortOrder)));
}
var db = ApplicationContext.Current.DatabaseContext.Database;
var exist = db.TableExist("umbracoApp");
if (exist)
{
var dbApps = SqlHelper.ExecuteReader("SELECT * FROM umbracoApp WHERE appAlias NOT IN (" + inString + ")");
while (dbApps.Read())
{
doc.Root.Add(new XElement("add",
new XAttribute("alias", dbApps.GetString("appAlias")),
new XAttribute("name", dbApps.GetString("appName")),
new XAttribute("icon", dbApps.GetString("appIcon")),
new XAttribute("sortOrder", dbApps.GetByte("sortOrder"))));
}
}
// Load all Applications by attribute and add them to the XML config
var types = PluginManager.Current.ResolveApplications();
}, true);
//TODO Shouldn't this be enabled and then delete the whole table?
//SqlHelper.ExecuteNonQuery("DELETE FROM umbracoApp");
//since applications don't populate their metadata from the attribute and because it is an interface,
//we need to interrogate the attributes for the data. Would be better to have a base class that contains
//metadata populated by the attribute. Oh well i guess.
var attrs = types.Select(x => x.GetCustomAttributes<ApplicationAttribute>(false).Single());
return attrs.Select(x => new Application(x.Name, x.Alias, x.Icon, x.SortOrder)).ToArray();
});
}
private readonly Lazy<IEnumerable<Application>> _lazySections;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<Application> GetEnumerator()
{
return _lazySections.Value.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}
+165 -48
View File
@@ -9,6 +9,7 @@ using Umbraco.Core.Cache;
using Umbraco.Core.IO;
using Umbraco.Core.Events;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using umbraco.DataLayer;
namespace umbraco.BusinessLogic
@@ -24,6 +25,23 @@ namespace umbraco.BusinessLogic
internal const string TreeConfigFileName = "trees.config";
private static string _treeConfig;
private static readonly object Locker = new object();
private static volatile bool _isInitialized = false;
private static IEnumerable<ApplicationTree> _allAvailableTrees;
/// <summary>
/// Initializes the service with any trees found in plugins
/// </summary>
/// <param name="allAvailableTrees">
/// A collection of all available tree found in assemblies in the application
/// </param>
/// <remarks>
/// This will update the trees.config with the found tree plugins that are not currently listed in the file when the first
/// access is made to resolve the tree collection
/// </remarks>
internal static void Intitialize(IEnumerable<ApplicationTree> allAvailableTrees)
{
_allAvailableTrees = allAvailableTrees;
}
/// <summary>
/// gets/sets the trees.config file path
@@ -45,58 +63,136 @@ namespace umbraco.BusinessLogic
}
/// <summary>
/// The cache storage for all application trees
/// The main entry point to get application trees
/// </summary>
private static List<ApplicationTree> AppTrees
/// <remarks>
/// This lazily on first access will scan for plugin trees and ensure the trees.config is up-to-date with the plugins. If plugins
/// haven't changed on disk then the file will not be saved. The trees are all then loaded from this config file into cache and returned.
/// </remarks>
private static List<ApplicationTree> GetAppTrees()
{
get
{
return ApplicationContext.Current.ApplicationCache.GetCacheItem(
CacheKeys.ApplicationTreeCacheKey,
() =>
return ApplicationContext.Current.ApplicationCache.GetCacheItem<List<ApplicationTree>>(
CacheKeys.ApplicationTreeCacheKey,
() =>
{
var list = ReadFromXmlAndSort();
//On first access we need to do some initialization
if (_isInitialized == false)
{
lock (Locker)
{
var list = new List<ApplicationTree>();
LoadXml(doc =>
if (_isInitialized == false)
{
foreach (var addElement in doc.Root.Elements("add").OrderBy(x =>
{
var sortOrderAttr = x.Attribute("sortOrder");
return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0;
}))
//now we can check the non-volatile flag
if (_allAvailableTrees != null)
{
var hasChanges = false;
var applicationAlias = (string)addElement.Attribute("application");
var type = (string)addElement.Attribute("type");
var assembly = (string)addElement.Attribute("assembly");
//check if the tree definition (applicationAlias + type + assembly) is already in the list
if (!list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias)
&& tree.Type.InvariantEquals(type)
&& tree.AssemblyName.InvariantEquals(assembly)))
LoadXml(doc =>
{
list.Add(new ApplicationTree(
addElement.Attribute("silent") != null ? Convert.ToBoolean(addElement.Attribute("silent").Value) : false,
addElement.Attribute("initialize") != null ? Convert.ToBoolean(addElement.Attribute("initialize").Value) : true,
addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0,
addElement.Attribute("application").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("title").Value,
addElement.Attribute("iconClosed").Value,
addElement.Attribute("iconOpen").Value,
(string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360
addElement.Attribute("type").Value,
addElement.Attribute("action") != null ? addElement.Attribute("action").Value : ""));
//Now, load in the xml structure and update it with anything that is not declared there and save the file.
//NOTE: On the first iteration here, it will lazily scan all trees, etc... this is because this ienumerable is lazy
// based on the ApplicationTreeRegistrar - and as noted there this is not an ideal way to do things but were stuck like this
// currently because of the legacy assemblies and types not in the Core.
//Get all the trees not registered in the config
var unregistered = _allAvailableTrees
.Where(x => list.Any(l => l.Alias == x.Alias) == false)
.ToArray();
hasChanges = unregistered.Any();
if (hasChanges == false) return false;
//add the unregistered ones to the list and re-save the file if any changes were found
var count = 0;
foreach (var tree in unregistered)
{
doc.Root.Add(new XElement("add",
new XAttribute("initialize", tree.Initialize),
new XAttribute("sortOrder", tree.SortOrder),
new XAttribute("alias", tree.Alias),
new XAttribute("application", tree.ApplicationAlias),
new XAttribute("title", tree.Title),
new XAttribute("iconClosed", tree.IconClosed),
new XAttribute("iconOpen", tree.IconOpened),
new XAttribute("type", tree.Type)));
count++;
}
//don't save if there's no changes
return count > 0;
}, true);
if (hasChanges)
{
//If there were changes, we need to re-read the structures from the XML
list = ReadFromXmlAndSort();
}
}
}, false);
return list;
});
}
_isInitialized = true;
}
}
}
return list;
});
}
private static List<ApplicationTree> ReadFromXmlAndSort()
{
var list = new List<ApplicationTree>();
//read in the xml file containing trees and convert them all to ApplicationTree instances
LoadXml(doc =>
{
foreach (var addElement in doc.Root.Elements("add").OrderBy(x =>
{
var sortOrderAttr = x.Attribute("sortOrder");
return sortOrderAttr != null ? Convert.ToInt32(sortOrderAttr.Value) : 0;
}))
{
var applicationAlias = (string)addElement.Attribute("application");
var type = (string)addElement.Attribute("type");
var assembly = (string)addElement.Attribute("assembly");
var clrType = System.Type.GetType(type);
if (clrType == null)
{
LogHelper.Warn(typeof(ApplicationTree), "The tree definition: " + addElement.ToString() + " could not be resolved to a .Net object type");
continue;
}
//check if the tree definition (applicationAlias + type + assembly) is already in the list
if (list.Any(tree => tree.ApplicationAlias.InvariantEquals(applicationAlias) && tree.GetRuntimeType() == clrType) == false)
{
list.Add(new ApplicationTree(
addElement.Attribute("silent") != null && Convert.ToBoolean(addElement.Attribute("silent").Value),
addElement.Attribute("initialize") == null || Convert.ToBoolean(addElement.Attribute("initialize").Value),
addElement.Attribute("sortOrder") != null ? Convert.ToByte(addElement.Attribute("sortOrder").Value) : (byte)0,
addElement.Attribute("application").Value,
addElement.Attribute("alias").Value,
addElement.Attribute("title").Value,
addElement.Attribute("iconClosed").Value,
addElement.Attribute("iconOpen").Value,
(string)addElement.Attribute("assembly"), //this could be empty: http://issues.umbraco.org/issue/U4-1360
addElement.Attribute("type").Value,
addElement.Attribute("action") != null ? addElement.Attribute("action").Value : ""));
}
}
return false;
}, false);
return list;
}
/// <summary>
@@ -256,6 +352,9 @@ namespace umbraco.BusinessLogic
new XAttribute("type", type),
new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
}
return true;
}, true);
OnNew(new ApplicationTree(silent, initialize, sortOrder, applicationAlias, alias, title, iconClosed, iconOpened, assemblyName, type, action), new EventArgs());
@@ -287,6 +386,8 @@ namespace umbraco.BusinessLogic
el.Add(new XAttribute("action", string.IsNullOrEmpty(this.Action) ? "" : this.Action));
}
return true;
}, true);
OnUpdated(this, new EventArgs());
@@ -304,6 +405,9 @@ namespace umbraco.BusinessLogic
{
doc.Root.Elements("add").Where(x => x.Attribute("application") != null && x.Attribute("application").Value == this.ApplicationAlias &&
x.Attribute("alias") != null && x.Attribute("alias").Value == this.Alias).Remove();
return true;
}, true);
OnDeleted(this, new EventArgs());
@@ -317,7 +421,7 @@ namespace umbraco.BusinessLogic
/// <returns>An ApplicationTree instance</returns>
public static ApplicationTree getByAlias(string treeAlias)
{
return AppTrees.Find(t => (t.Alias == treeAlias));
return GetAppTrees().Find(t => (t.Alias == treeAlias));
}
@@ -327,7 +431,7 @@ namespace umbraco.BusinessLogic
/// <returns>Returns a ApplicationTree Array</returns>
public static ApplicationTree[] getAll()
{
return AppTrees.OrderBy(x => x.SortOrder).ToArray();
return GetAppTrees().OrderBy(x => x.SortOrder).ToArray();
}
/// <summary>
@@ -348,7 +452,7 @@ namespace umbraco.BusinessLogic
/// <returns>Returns a ApplicationTree Array</returns>
public static ApplicationTree[] getApplicationTree(string applicationAlias, bool onlyInitializedApplications)
{
var list = AppTrees.FindAll(
var list = GetAppTrees().FindAll(
t =>
{
if (onlyInitializedApplications)
@@ -360,21 +464,34 @@ namespace umbraco.BusinessLogic
return list.OrderBy(x => x.SortOrder).ToArray();
}
internal static void LoadXml(Action<XDocument> callback, bool saveAfterCallback)
/// <summary>
/// Loads in the xml structure from disk if one is found, otherwise loads in an empty xml structure, calls the
/// callback with the xml document and saves the structure back to disk if saveAfterCallback is true.
/// </summary>
/// <param name="callback"></param>
/// <param name="saveAfterCallbackIfChanges"></param>
internal static void LoadXml(Func<XDocument, bool> callback, bool saveAfterCallbackIfChanges)
{
lock (Locker)
{
var doc = File.Exists(TreeConfigFilePath)
? XDocument.Load(TreeConfigFilePath)
: XDocument.Parse("<?xml version=\"1.0\"?><trees />");
if (doc.Root != null)
{
callback.Invoke(doc);
var hasChanges = callback.Invoke(doc);
if (saveAfterCallback)
if (saveAfterCallbackIfChanges && hasChanges
//Don't save it if it is empty, in some very rare cases if the app domain get's killed in the middle of this process
// in some insane way the file saved will be empty. I'm pretty sure it's not actually anything to do with the xml doc and
// more about the IO trying to save the XML doc, but it doesn't hurt to check.
&& doc.Root != null && doc.Root.Elements().Any())
{
//ensures the folder exists
Directory.CreateDirectory(Path.GetDirectoryName(TreeConfigFilePath));
//saves it
doc.Save(TreeConfigFilePath);
//remove the cache now that it has changed SD: I'm leaving this here even though it
@@ -1,4 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Umbraco.Core;
@@ -9,81 +11,84 @@ using umbraco.interfaces;
namespace umbraco.BusinessLogic
{
public class ApplicationTreeRegistrar : IApplicationStartupHandler
public class ApplicationTreeRegistrar : ApplicationEventHandler
{
public ApplicationTreeRegistrar()
protected override void ApplicationStarted(UmbracoApplicationBase umbracoApplication, ApplicationContext applicationContext)
{
//don't do anything if the application or database is not configured!
if (ApplicationContext.Current == null
|| !ApplicationContext.Current.IsConfigured
|| !ApplicationContext.Current.DatabaseContext.IsDatabaseConfigured)
return;
//Call initialize on the tree service with the lazy enumerable class below, when the tree service needs to resolve,
// it will lazily do the scanning and comparing so it's not actually done on app start.
ApplicationTree.Intitialize(new LazyEnumerableTrees());
}
// Load all Trees by attribute and add them to the XML config
var types = PluginManager.Current.ResolveAttributedTrees();
var items = types
.Select(x =>
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
.Where(x => ApplicationTree.getByAlias(x.Item2.Alias) == null);
var allAliases = ApplicationTree.getAll().Select(x => x.Alias).Concat(items.Select(x => x.Item2.Alias));
var inString = "'" + string.Join("','", allAliases) + "'";
ApplicationTree.LoadXml(doc =>
/// <summary>
/// This class is here so that we can provide lazy access to tree scanning for when it is needed
/// </summary>
private class LazyEnumerableTrees : IEnumerable<ApplicationTree>
{
public LazyEnumerableTrees()
{
foreach (var tuple in items)
_lazyTrees = new Lazy<IEnumerable<ApplicationTree>>(() =>
{
var type = tuple.Item1;
var attr = tuple.Item2;
//Add the new tree that doesn't exist in the config that was found by type finding
var added = new List<string>();
doc.Root.Add(new XElement("add",
new XAttribute("silent", attr.Silent),
new XAttribute("initialize", attr.Initialize),
new XAttribute("sortOrder", attr.SortOrder),
new XAttribute("alias", attr.Alias),
new XAttribute("application", attr.ApplicationAlias),
new XAttribute("title", attr.Title),
new XAttribute("iconClosed", attr.IconClosed),
new XAttribute("iconOpen", attr.IconOpen),
// don't add the assembly, we don't need this:
// http://issues.umbraco.org/issue/U4-1360
//new XAttribute("assembly", assemblyName),
//new XAttribute("type", typeName),
// instead, store the assembly type name
new XAttribute("type", type.GetFullNameWithAssembly()),
new XAttribute("action", attr.Action)));
}
// Load all Controller Trees by attribute
var types = PluginManager.Current.ResolveAttributedTrees();
//convert them to ApplicationTree instances
var items = types
.Select(x =>
new Tuple<Type, TreeAttribute>(x, x.GetCustomAttributes<TreeAttribute>(false).Single()))
.Select(x => new ApplicationTree(
x.Item2.Silent, x.Item2.Initialize, (byte) x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen,
"",
x.Item1.GetFullNameWithAssembly(),
x.Item2.Action))
.ToArray();
//add any trees that were found in the database that don't exist in the config
added.AddRange(items.Select(x => x.Alias));
var db = ApplicationContext.Current.DatabaseContext.Database;
var exist = db.TableExist("umbracoAppTree");
if (exist)
{
var appTrees = db.Fetch<AppTreeDto>("WHERE treeAlias NOT IN (" + inString + ")");
foreach (var appTree in appTrees)
{
var action = appTree.Action;
//find the legacy trees
var legacyTreeTypes = PluginManager.Current.ResolveAttributedTrees();
//convert them to ApplicationTree instances
var legacyItems = legacyTreeTypes
.Select(x =>
new Tuple<Type, global::umbraco.businesslogic.TreeAttribute>(
x,
x.GetCustomAttributes<global::umbraco.businesslogic.TreeAttribute>(false).SingleOrDefault()))
.Where(x => x.Item2 != null)
//make sure the legacy tree isn't added on top of the controller tree!
.Where(x => added.InvariantContains(x.Item2.Alias) == false)
.Select(x => new ApplicationTree(x.Item2.Silent, x.Item2.Initialize, (byte) x.Item2.SortOrder, x.Item2.ApplicationAlias, x.Item2.Alias, x.Item2.Title, x.Item2.IconClosed, x.Item2.IconOpen,
"",
x.Item1.GetFullNameWithAssembly(),
x.Item2.Action));
doc.Root.Add(new XElement("add",
new XAttribute("silent", appTree.Silent),
new XAttribute("initialize", appTree.Initialize),
new XAttribute("sortOrder", appTree.SortOrder),
new XAttribute("alias", appTree.Alias),
new XAttribute("application", appTree.AppAlias),
new XAttribute("title", appTree.Title),
new XAttribute("iconClosed", appTree.IconClosed),
new XAttribute("iconOpen", appTree.IconOpen),
new XAttribute("assembly", appTree.HandlerAssembly),
new XAttribute("type", appTree.HandlerType),
new XAttribute("action", string.IsNullOrEmpty(action) ? "" : action)));
}
}
return items.Concat(legacyItems).ToArray();
});
}
}, true);
private readonly Lazy<IEnumerable<ApplicationTree>> _lazyTrees;
/// <summary>
/// Returns an enumerator that iterates through the collection.
/// </summary>
/// <returns>
/// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection.
/// </returns>
public IEnumerator<ApplicationTree> GetEnumerator()
{
return _lazyTrees.Value.GetEnumerator();
}
/// <summary>
/// Returns an enumerator that iterates through a collection.
/// </summary>
/// <returns>
/// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection.
/// </returns>
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
}
+2 -4
View File
@@ -93,7 +93,6 @@ namespace umbraco.cms.businesslogic
/// </summary>
public class DictionaryItem
{
private string _key;
internal Guid UniqueId { get; private set; }
@@ -118,13 +117,12 @@ namespace umbraco.cms.businesslogic
{
EnsureCache();
var item = DictionaryItems.Values.SingleOrDefault(x => x.key == key);
if (item == null)
if (!DictionaryItems.ContainsKey(key))
{
throw new ArgumentException("No key " + key + " exists in dictionary");
}
var item = DictionaryItems[key];
this.id = item.id;
this._key = item.key;
this.ParentId = item.ParentId;
+8 -1
View File
@@ -494,7 +494,14 @@ namespace umbraco.cms.businesslogic.web
return hasAccess;
}
[Obsolete("This method has been replaced because of a spelling mistake. Use the HasAccess method instead.", false)]
public static bool HasAccces(int documentId, object memberId)
{
// Call the correctly named version of this method
return HasAccess(documentId, memberId);
}
public static bool HasAccess(int documentId, object memberId)
{
bool hasAccess = false;
var node = new CMSNode(documentId);
@@ -511,7 +518,7 @@ namespace umbraco.cms.businesslogic.web
if (member != null)
{
foreach (string role in Roles.GetRolesForUser())
foreach (string role in Roles.GetRolesForUser(member.UserName))
{
if (currentNode.SelectSingleNode("./group [@id='" + role + "']") != null)
{
+1 -1
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
<package id="HtmlAgilityPack" version="1.4.6" targetFramework="net40" />
<package id="SharpZipLib" version="0.86.0" targetFramework="net40" />
<package id="Tidy.Net" version="1.0.0" targetFramework="net40" />
+2 -2
View File
@@ -104,9 +104,9 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="ClientDependency.Core">
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="HtmlAgilityPack, Version=1.4.6.0, Culture=neutral, PublicKeyToken=bd319b19eaf3b43a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
</packages>
+3 -3
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@@ -66,9 +66,9 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="ClientDependency.Core">
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.configuration" />
+1 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ClientDependency" version="1.7.1.2" targetFramework="net40" />
<package id="ClientDependency" version="1.8.2.1" targetFramework="net40" />
</packages>
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
<PropertyGroup>
<ProjectType>Local</ProjectType>
@@ -112,9 +112,9 @@
<Project>{651E1350-91B6-44B7-BD60-7207006D7003}</Project>
<Name>Umbraco.Web</Name>
</ProjectReference>
<Reference Include="ClientDependency.Core">
<Reference Include="ClientDependency.Core, Version=1.8.2.1, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\ClientDependency.1.7.1.2\lib\ClientDependency.Core.dll</HintPath>
<HintPath>..\packages\ClientDependency.1.8.2.1\lib\net40\ClientDependency.Core.dll</HintPath>
</Reference>
<Reference Include="System">
<Name>System</Name>

Some files were not shown because too many files have changed in this diff Show More