Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 026805e4c1 | |||
| a7dad09f44 | |||
| e4aa7c068b | |||
| cc211ab3bd | |||
| cb72e6344a | |||
| bf3ddd9384 | |||
| 8a6ece4a6d | |||
| d2662c17f7 | |||
| 18c2652f67 | |||
| da0d97c108 | |||
| f672a323b0 | |||
| a59dd32dd1 | |||
| d4d5a355e3 | |||
| f4a0bcfc96 | |||
| aadae6334b | |||
| 7590bd86dd | |||
| 6b25e5299f | |||
| b30555f5a4 | |||
| d4d77611eb | |||
| 662ed03f0a | |||
| 3c01dcb30c | |||
| aa439cacda | |||
| e643f2a0ba | |||
| a32d38f7ea | |||
| 864994a56e | |||
| 3405c5001b | |||
| 4d8732d925 | |||
| 4eb9a54fa5 | |||
| 71ca90d74b | |||
| fb42472f31 | |||
| b6695b6953 | |||
| 1eab26c0f6 | |||
| 0543c620eb | |||
| 646e5f173d | |||
| b5594517bd | |||
| 246dfba8ac | |||
| 7d189a6fb8 | |||
| 71242430da | |||
| 4ee4f296fc | |||
| b9e4950e36 | |||
| ba52f9235e |
+1
-1
@@ -1,5 +1,5 @@
|
||||
@ECHO OFF
|
||||
SET release=6.2.2
|
||||
SET release=6.2.6
|
||||
SET comment=
|
||||
SET version=%release%
|
||||
|
||||
|
||||
@@ -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)" />
|
||||
|
||||
@@ -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,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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -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]
|
||||
|
||||
@@ -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 @@
|
||||
<%@ Application Codebehind="Global.asax.cs" Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %>
|
||||
<%@ Application Inherits="Umbraco.Web.UmbracoApplication" Language="C#" %>
|
||||
|
||||
@@ -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,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" />
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,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" />
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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,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" />
|
||||
|
||||
@@ -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,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>
|
||||
<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,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
Reference in New Issue
Block a user