Compare commits
141 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 | |||
| 33ccf490ab | |||
| 03ba8c9e14 | |||
| 13b57c7638 | |||
| a8b297903d | |||
| 89b5774106 | |||
| 774f201514 | |||
| 12e2aa679b | |||
| 1978930fa2 | |||
| c4e4f9579b | |||
| 9782f66507 | |||
| e9938afc83 | |||
| fcda9821d0 | |||
| bdc234b18a | |||
| 908afbd264 | |||
| c548ad6a98 | |||
| 99b2a2da38 | |||
| 15ed458ad3 | |||
| 6ce303a918 | |||
| 62d3dc78fa | |||
| 3374f9e023 | |||
| 2675992d82 | |||
| ce29f54663 | |||
| 7261162d8a | |||
| b5f900e789 | |||
| fdab6d5de5 | |||
| ff94eba3a7 | |||
| e200e2fc48 | |||
| 22dde5a421 | |||
| 7f510c107a | |||
| 2ebfd8f3d4 | |||
| fd7930256b | |||
| c24775adb0 | |||
| 217f591db0 | |||
| d453aedd0d | |||
| 4693fb23c8 | |||
| 7848a3095d | |||
| 388eea95e5 | |||
| 6167d1584a | |||
| bfd0c5e309 | |||
| 43669e62b4 | |||
| 6b128ea7d4 | |||
| d5993a4783 | |||
| 86c2a6e8a9 | |||
| 874f464a2b | |||
| b3c41e051f | |||
| a88637910f | |||
| 431e93000e | |||
| 1a821b4073 | |||
| a89b9e9da6 | |||
| 8917701fa3 | |||
| 68b17bff08 | |||
| 7a12060c37 | |||
| 525280d466 | |||
| 496beca8f2 | |||
| 637a22293d | |||
| 3c8517edcb | |||
| 45e6127d00 | |||
| 6eb5d3f7ef | |||
| e6986432df | |||
| 80103e6155 | |||
| 392634eb76 | |||
| 259bc863f0 | |||
| 582bafdefd | |||
| 9924ef2381 | |||
| 859fbaaa05 | |||
| 72991334c4 | |||
| c659d5b85e | |||
| a4bf4b0977 | |||
| bef6ce66f1 | |||
| a18a2198a8 | |||
| 7cf7ac687c | |||
| 7ce0272169 | |||
| acb934e3f1 | |||
| 3aa486a463 | |||
| 5aa0621c90 | |||
| 625b8f7e65 | |||
| e27163f3a7 | |||
| 65ea6f94d3 | |||
| 9158ea1ace | |||
| e8f7f77bb6 | |||
| 75e003185a | |||
| 9d12e1c7df | |||
| b8575ef158 | |||
| d3bb750f98 | |||
| 02eff33d29 | |||
| b43f39a0cb | |||
| d0cb27d1c1 | |||
| 16a1720bf7 | |||
| ea32776174 | |||
| 74806984b3 | |||
| c8c278c7bc | |||
| a8e4c3eb9a | |||
| 113b866485 | |||
| 6c6ef8d144 | |||
| 99250cefbb | |||
| dc651a1c42 | |||
| 4e9fd5a223 | |||
| e2c36d7212 | |||
| 695cc07912 | |||
| 46f57a3115 |
+1
-1
@@ -1,5 +1,5 @@
|
||||
@ECHO OFF
|
||||
SET release=6.2.0
|
||||
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.0</version>
|
||||
<version>6.2.3</version>
|
||||
<title>Umbraco Cms Core Binaries</title>
|
||||
<authors>Umbraco HQ</authors>
|
||||
<owners>Umbraco HQ</owners>
|
||||
@@ -15,20 +15,23 @@
|
||||
<language>en-US</language>
|
||||
<tags>umbraco</tags>
|
||||
<dependencies>
|
||||
<dependency id="Microsoft.AspNet.Mvc" version="[4.0.0,5.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.Mvc" version="[4.0.30506.0,5.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.WebApi" version="[4.0.30506,5.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.WebApi.WebHost" version="[4.0.30506, 5.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.WebApi.Core" version="[4.0.30506, 5.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.WebApi.Client" version="[4.0.30506, 5.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.0" />
|
||||
<dependency id="Microsoft.Net.Http" version="[2.0.0, 3.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.Mvc.FixedDisplayModes" version="1.0.1" />
|
||||
<dependency id="Microsoft.Net.Http" version="[2.0.20710.0, 3.0.0)" />
|
||||
<dependency id="MiniProfiler" version="[2.1.0, 3.0.0)" />
|
||||
<dependency id="HtmlAgilityPack" version="[1.4.6, 2.0.0)" />
|
||||
<dependency id="Lucene.Net" version="[2.9.4.1, 3.0.0.0)" />
|
||||
<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)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
@@ -38,7 +41,6 @@
|
||||
<file src="..\_BuildOutput\WebApp\bin\cms.xml" target="lib\cms.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\controls.dll" target="lib\controls.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\controls.xml" target="lib\controls.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Examine.dll" target="lib\Examine.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\interfaces.dll" target="lib\interfaces.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\interfaces.xml" target="lib\interfaces.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\log4net.dll" target="lib\log4net.dll" />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
|
||||
<metadata>
|
||||
<id>UmbracoCms</id>
|
||||
<version>6.1.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.0</UmbracoVersion>
|
||||
<UmbracoVersion>6.2.6</UmbracoVersion>
|
||||
</PropertyGroup>
|
||||
<Target Name="CopyUmbracoFilesToWebRoot" BeforeTargets="AfterBuild">
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="Sql\Total.sql" />
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
<None Include="Sql\Version4_1_Upgrade.sql" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -1,218 +1,204 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
internal abstract class DictionaryCacheProviderBase : ICacheProvider
|
||||
{
|
||||
protected static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
protected abstract DictionaryCacheWrapper DictionaryCache { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Clears everything in umbraco's runtime cache
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not clear other stuff the user has put in httpruntime.cache!
|
||||
/// </remarks>
|
||||
public virtual void ClearAllCache()
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
var keysToRemove = DictionaryCache.Cast<object>()
|
||||
.Select(item => new DictionaryItemWrapper(item))
|
||||
.Where(c => c.Key is string && ((string)c.Key).StartsWith(CacheItemPrefix) && DictionaryCache[c.Key.ToString()] != null)
|
||||
.Select(c => c.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var k in keysToRemove)
|
||||
{
|
||||
DictionaryCache.Remove(k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears the item in umbraco's runtime cache with the given key
|
||||
/// </summary>
|
||||
/// <param name="key">Key</param>
|
||||
public virtual void ClearCacheItem(string key)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
if (DictionaryCache[GetCacheKey(key)] == null) return;
|
||||
DictionaryCache.Remove(GetCacheKey(key)); ;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all objects in the System.Web.Cache with the System.Type name as the
|
||||
/// input parameter. (using [object].GetType())
|
||||
/// </summary>
|
||||
/// <param name="typeName">The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument"</param>
|
||||
public virtual void ClearCacheObjectTypes(string typeName)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
var keysToRemove = DictionaryCache
|
||||
.Cast<object>()
|
||||
.Select(item => new DictionaryItemWrapper(item))
|
||||
.Where(c =>
|
||||
{
|
||||
var k = c.Key.ToString();
|
||||
var v = DictionaryCache[k];
|
||||
return v != null && v.GetType().ToString().InvariantEquals(typeName);
|
||||
})
|
||||
.Select(c => c.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var k in keysToRemove)
|
||||
DictionaryCache.Remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheObjectTypes<T>()
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
var typeOfT = typeof(T);
|
||||
var keysToRemove = DictionaryCache
|
||||
.Cast<object>()
|
||||
.Select(item => new DictionaryItemWrapper(item))
|
||||
.Where(c =>
|
||||
{
|
||||
var k = c.Key.ToString();
|
||||
var v = DictionaryCache[k];
|
||||
return v != null && v.GetType() == typeOfT;
|
||||
})
|
||||
.Select(c => c.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var k in keysToRemove)
|
||||
DictionaryCache.Remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
{
|
||||
var typeOfT = typeof(T);
|
||||
var keysToRemove = DictionaryCache
|
||||
.Cast<object>()
|
||||
.Select(item => new DictionaryItemWrapper(item))
|
||||
.Where(c =>
|
||||
{
|
||||
var k = c.Key.ToString();
|
||||
var v = DictionaryCache[k];
|
||||
return v != null && v.GetType() == typeOfT && predicate(k, (T)v);
|
||||
})
|
||||
.Select(c => c.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var k in keysToRemove)
|
||||
DictionaryCache.Remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all cache items that starts with the key passed.
|
||||
/// </summary>
|
||||
/// <param name="keyStartsWith">The start of the key</param>
|
||||
public virtual void ClearCacheByKeySearch(string keyStartsWith)
|
||||
{
|
||||
var keysToRemove = DictionaryCache.Cast<object>()
|
||||
.Select(item => new DictionaryItemWrapper(item))
|
||||
.Where(c => c.Key is string && ((string)c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith)))
|
||||
.Select(c => c.Key)
|
||||
.ToList();
|
||||
|
||||
foreach (var k in keysToRemove)
|
||||
{
|
||||
DictionaryCache.Remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all cache items that have a key that matches the regular expression
|
||||
/// </summary>
|
||||
/// <param name="regexString"></param>
|
||||
public virtual void ClearCacheByKeyExpression(string regexString)
|
||||
{
|
||||
var keysToRemove = new List<object>();
|
||||
foreach (var item in DictionaryCache)
|
||||
{
|
||||
var c = new DictionaryItemWrapper(item);
|
||||
var s = c.Key as string;
|
||||
if (s != null)
|
||||
{
|
||||
var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix));
|
||||
if (Regex.IsMatch(withoutPrefix, regexString))
|
||||
{
|
||||
keysToRemove.Add(c.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var k in keysToRemove)
|
||||
{
|
||||
DictionaryCache.Remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
|
||||
{
|
||||
return (from object item in DictionaryCache
|
||||
select new DictionaryItemWrapper(item)
|
||||
into c
|
||||
where c.Key is string && ((string) c.Key).InvariantStartsWith(string.Format("{0}-{1}", CacheItemPrefix, keyStartsWith))
|
||||
select c.Value).ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<object> GetCacheItemsByKeyExpression(string regexString)
|
||||
{
|
||||
var found = new List<object>();
|
||||
foreach (var item in DictionaryCache)
|
||||
{
|
||||
var c = new DictionaryItemWrapper(item);
|
||||
var s = c.Key as string;
|
||||
if (s != null)
|
||||
{
|
||||
var withoutPrefix = s.TrimStart(string.Format("{0}-", CacheItemPrefix));
|
||||
if (Regex.IsMatch(withoutPrefix, regexString))
|
||||
{
|
||||
found.Add(c.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a cache item by key, does not update the cache if it isn't there.
|
||||
/// </summary>
|
||||
/// <param name="cacheKey"></param>
|
||||
/// <returns></returns>
|
||||
public virtual object GetCacheItem(string cacheKey)
|
||||
{
|
||||
var result = DictionaryCache.Get(GetCacheKey(cacheKey));
|
||||
return result;
|
||||
}
|
||||
|
||||
public abstract object GetCacheItem(string cacheKey, Func<object> getCacheItem);
|
||||
|
||||
/// <summary>
|
||||
/// We prefix all cache keys with this so that we know which ones this class has created when
|
||||
/// using the HttpRuntime cache so that when we clear it we don't clear other entries we didn't create.
|
||||
/// </summary>
|
||||
// prefix cache keys so we know which one are ours
|
||||
protected const string CacheItemPrefix = "umbrtmche";
|
||||
|
||||
// an object that represent a value that has not been created yet
|
||||
protected readonly object ValueNotCreated = new object();
|
||||
|
||||
// manupulate the underlying cache entries
|
||||
// these *must* be called from within the appropriate locks
|
||||
// and use the full prefixed cache keys
|
||||
protected abstract IEnumerable<DictionaryEntry> GetDictionaryEntries();
|
||||
protected abstract void RemoveEntry(string key);
|
||||
protected abstract object GetEntry(string key);
|
||||
|
||||
// read-write lock the underlying cache
|
||||
protected abstract IDisposable ReadLock { get; }
|
||||
protected abstract IDisposable WriteLock { get; }
|
||||
|
||||
protected string GetCacheKey(string key)
|
||||
{
|
||||
return string.Format("{0}-{1}", CacheItemPrefix, key);
|
||||
}
|
||||
|
||||
protected object GetSafeLazyValue(Lazy<object> lazy, bool onlyIfValueIsCreated = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// if onlyIfValueIsCreated, do not trigger value creation
|
||||
// must return something, though, to differenciate from null values
|
||||
if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
|
||||
return lazy.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Clear
|
||||
|
||||
public virtual void ClearAllCache()
|
||||
{
|
||||
using (WriteLock)
|
||||
{
|
||||
foreach (var entry in GetDictionaryEntries()
|
||||
.ToArray())
|
||||
RemoveEntry((string) entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheItem(string key)
|
||||
{
|
||||
var cacheKey = GetCacheKey(key);
|
||||
using (WriteLock)
|
||||
{
|
||||
RemoveEntry(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheObjectTypes(string typeName)
|
||||
{
|
||||
using (WriteLock)
|
||||
{
|
||||
foreach (var entry in GetDictionaryEntries()
|
||||
.Where(x =>
|
||||
{
|
||||
// entry.Value is Lazy<object> and not null, its value may be null
|
||||
// remove null values as well, does not hurt
|
||||
// get non-created as NonCreatedValue & exceptions as null
|
||||
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
return value == null || value.GetType().ToString().InvariantEquals(typeName);
|
||||
})
|
||||
.ToArray())
|
||||
RemoveEntry((string) entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheObjectTypes<T>()
|
||||
{
|
||||
var typeOfT = typeof(T);
|
||||
using (WriteLock)
|
||||
{
|
||||
foreach (var entry in GetDictionaryEntries()
|
||||
.Where(x =>
|
||||
{
|
||||
// entry.Value is Lazy<object> and not null, its value may be null
|
||||
// remove null values as well, does not hurt
|
||||
// compare on exact type, don't use "is"
|
||||
// get non-created as NonCreatedValue & exceptions as null
|
||||
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
return value == null || value.GetType() == typeOfT;
|
||||
})
|
||||
.ToArray())
|
||||
RemoveEntry((string) entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
|
||||
{
|
||||
var typeOfT = typeof(T);
|
||||
var plen = CacheItemPrefix.Length + 1;
|
||||
using (WriteLock)
|
||||
{
|
||||
foreach (var entry in GetDictionaryEntries()
|
||||
.Where(x =>
|
||||
{
|
||||
// entry.Value is Lazy<object> and not null, its value may be null
|
||||
// remove null values as well, does not hurt
|
||||
// compare on exact type, don't use "is"
|
||||
// get non-created as NonCreatedValue & exceptions as null
|
||||
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
if (value == null) return true;
|
||||
return value.GetType() == typeOfT
|
||||
// run predicate on the 'public key' part only, ie without prefix
|
||||
&& predicate(((string)x.Key).Substring(plen), (T)value);
|
||||
}))
|
||||
RemoveEntry((string) entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheByKeySearch(string keyStartsWith)
|
||||
{
|
||||
var plen = CacheItemPrefix.Length + 1;
|
||||
using (WriteLock)
|
||||
{
|
||||
foreach (var entry in GetDictionaryEntries()
|
||||
.Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
|
||||
.ToArray())
|
||||
RemoveEntry((string) entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheByKeyExpression(string regexString)
|
||||
{
|
||||
var plen = CacheItemPrefix.Length + 1;
|
||||
using (WriteLock)
|
||||
{
|
||||
foreach (var entry in GetDictionaryEntries()
|
||||
.Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
|
||||
.ToArray())
|
||||
RemoveEntry((string) entry.Key);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Get
|
||||
|
||||
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
|
||||
{
|
||||
var plen = CacheItemPrefix.Length + 1;
|
||||
IEnumerable<DictionaryEntry> entries;
|
||||
using (ReadLock)
|
||||
{
|
||||
entries = GetDictionaryEntries()
|
||||
.Where(x => ((string)x.Key).Substring(plen).InvariantStartsWith(keyStartsWith))
|
||||
.ToArray(); // evaluate while locked
|
||||
}
|
||||
return entries
|
||||
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
|
||||
.Where(x => x != null); // backward compat, don't store null values in the cache
|
||||
}
|
||||
|
||||
public virtual IEnumerable<object> GetCacheItemsByKeyExpression(string regexString)
|
||||
{
|
||||
const string prefix = CacheItemPrefix + "-";
|
||||
var plen = prefix.Length;
|
||||
IEnumerable<DictionaryEntry> entries;
|
||||
using (ReadLock)
|
||||
{
|
||||
entries = GetDictionaryEntries()
|
||||
.Where(x => Regex.IsMatch(((string)x.Key).Substring(plen), regexString))
|
||||
.ToArray(); // evaluate while locked
|
||||
}
|
||||
return entries
|
||||
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
|
||||
.Where(x => x != null); // backward compat, don't store null values in the cache
|
||||
}
|
||||
|
||||
public virtual object GetCacheItem(string cacheKey)
|
||||
{
|
||||
cacheKey = GetCacheKey(cacheKey);
|
||||
Lazy<object> result;
|
||||
using (ReadLock)
|
||||
{
|
||||
result = GetEntry(cacheKey) as Lazy<object>; // null if key not found
|
||||
}
|
||||
return result == null ? null : GetSafeLazyValue(result); // return exceptions as null
|
||||
}
|
||||
|
||||
public abstract object GetCacheItem(string cacheKey, Func<object> getCacheItem);
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
internal class DictionaryCacheWrapper : IEnumerable
|
||||
{
|
||||
private readonly IEnumerable _inner;
|
||||
private readonly Func<object, object> _get;
|
||||
private readonly Action<object> _remove;
|
||||
|
||||
public DictionaryCacheWrapper(
|
||||
IEnumerable inner,
|
||||
Func<object, object> get,
|
||||
Action<object> remove)
|
||||
{
|
||||
_inner = inner;
|
||||
_get = get;
|
||||
_remove = remove;
|
||||
}
|
||||
|
||||
public object this[object key]
|
||||
{
|
||||
get
|
||||
{
|
||||
return Get(key);
|
||||
}
|
||||
}
|
||||
|
||||
public object Get(object key)
|
||||
{
|
||||
return _get(key);
|
||||
}
|
||||
|
||||
public void Remove(object key)
|
||||
{
|
||||
_remove(key);
|
||||
}
|
||||
|
||||
public IEnumerator GetEnumerator()
|
||||
{
|
||||
return _inner.GetEnumerator();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
internal class DictionaryItemWrapper
|
||||
{
|
||||
public DictionaryItemWrapper(dynamic item)
|
||||
{
|
||||
Key = item.Key;
|
||||
Value = item.Value;
|
||||
}
|
||||
|
||||
public object Key { get; private set; }
|
||||
public object Value { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
@@ -12,36 +11,115 @@ namespace Umbraco.Core.Cache
|
||||
/// </summary>
|
||||
internal class HttpRequestCacheProvider : DictionaryCacheProviderBase
|
||||
{
|
||||
private readonly Func<HttpContextBase> _context;
|
||||
// context provider
|
||||
// the idea is that there is only one, application-wide HttpRequestCacheProvider instance,
|
||||
// that is initialized with a method that returns the "current" context.
|
||||
// NOTE
|
||||
// but then it is initialized with () => new HttpContextWrapper(HttpContent.Current)
|
||||
// which is higly inefficient because it creates a new wrapper each time we refer to _context()
|
||||
// so replace it with _context1 and _context2 below + a way to get context.Items.
|
||||
//private readonly Func<HttpContextBase> _context;
|
||||
|
||||
public HttpRequestCacheProvider(HttpContext context)
|
||||
// NOTE
|
||||
// and then in almost 100% cases _context2 will be () => HttpContext.Current
|
||||
// so why not bring that logic in here and fallback on to HttpContext.Current when
|
||||
// _context1 is null?
|
||||
//private readonly HttpContextBase _context1;
|
||||
//private readonly Func<HttpContext> _context2;
|
||||
private readonly HttpContextBase _context;
|
||||
|
||||
private IDictionary ContextItems
|
||||
{
|
||||
_context = () => new HttpContextWrapper(context);
|
||||
//get { return _context1 != null ? _context1.Items : _context2().Items; }
|
||||
get { return _context != null ? _context.Items : HttpContext.Current.Items; }
|
||||
}
|
||||
|
||||
public HttpRequestCacheProvider(Func<HttpContextBase> context)
|
||||
// for unit tests
|
||||
public HttpRequestCacheProvider(HttpContextBase context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
protected override DictionaryCacheWrapper DictionaryCache
|
||||
// main constructor
|
||||
// will use HttpContext.Current
|
||||
public HttpRequestCacheProvider(/*Func<HttpContext> context*/)
|
||||
{
|
||||
//_context2 = context;
|
||||
}
|
||||
|
||||
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
|
||||
{
|
||||
const string prefix = CacheItemPrefix + "-";
|
||||
return ContextItems.Cast<DictionaryEntry>()
|
||||
.Where(x => x.Key is string && ((string)x.Key).StartsWith(prefix));
|
||||
}
|
||||
|
||||
protected override void RemoveEntry(string key)
|
||||
{
|
||||
ContextItems.Remove(key);
|
||||
}
|
||||
|
||||
protected override object GetEntry(string key)
|
||||
{
|
||||
return ContextItems[key];
|
||||
}
|
||||
|
||||
#region Lock
|
||||
|
||||
protected override IDisposable ReadLock
|
||||
{
|
||||
// there's no difference between ReadLock and WriteLock here
|
||||
get { return WriteLock; }
|
||||
}
|
||||
|
||||
protected override IDisposable WriteLock
|
||||
{
|
||||
// NOTE
|
||||
// could think about just overriding base.Locker to return a different
|
||||
// object but then we'd create a ReaderWriterLockSlim per request,
|
||||
// which is less efficient than just using a basic monitor lock.
|
||||
|
||||
get
|
||||
{
|
||||
var ctx = _context();
|
||||
return new DictionaryCacheWrapper(
|
||||
ctx.Items,
|
||||
o => ctx.Items[o],
|
||||
o => ctx.Items.Remove(o));
|
||||
return new MonitorLock(ContextItems.SyncRoot);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Get
|
||||
|
||||
public override object GetCacheItem(string cacheKey, Func<object> getCacheItem)
|
||||
{
|
||||
var ctx = _context();
|
||||
var ck = GetCacheKey(cacheKey);
|
||||
return ctx.Items[ck] ?? (ctx.Items[ck] = getCacheItem());
|
||||
cacheKey = GetCacheKey(cacheKey);
|
||||
|
||||
Lazy<object> result;
|
||||
|
||||
using (WriteLock)
|
||||
{
|
||||
result = ContextItems[cacheKey] as Lazy<object>; // null if key not found
|
||||
|
||||
// cannot create value within the lock, so if result.IsValueCreated is false, just
|
||||
// do nothing here - means that if creation throws, a race condition could cause
|
||||
// more than one thread to reach the return statement below and throw - accepted.
|
||||
|
||||
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
||||
{
|
||||
result = new Lazy<object>(getCacheItem);
|
||||
ContextItems[cacheKey] = result;
|
||||
}
|
||||
}
|
||||
|
||||
// this may throw if getCacheItem throws, but this is the only place where
|
||||
// it would throw as everywhere else we use GetLazySaveValue() to hide exceptions
|
||||
// and pretend exceptions were never inserted into cache to begin with.
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Insert
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Caching;
|
||||
using Umbraco.Core.Logging;
|
||||
using CacheItemPriority = System.Web.Caching.CacheItemPriority;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
@@ -14,47 +13,51 @@ namespace Umbraco.Core.Cache
|
||||
/// </summary>
|
||||
internal class HttpRuntimeCacheProvider : DictionaryCacheProviderBase, IRuntimeCacheProvider
|
||||
{
|
||||
// locker object that supports upgradeable read locking
|
||||
// does not need to support recursion if we implement the cache correctly and ensure
|
||||
// that methods cannot be reentrant, ie we do NOT create values while holding a lock.
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
|
||||
|
||||
private readonly System.Web.Caching.Cache _cache;
|
||||
private readonly DictionaryCacheWrapper _wrapper;
|
||||
|
||||
|
||||
public HttpRuntimeCacheProvider(System.Web.Caching.Cache cache)
|
||||
{
|
||||
_cache = cache;
|
||||
_wrapper = new DictionaryCacheWrapper(_cache, s => _cache.Get(s.ToString()), o => _cache.Remove(o.ToString()));
|
||||
}
|
||||
|
||||
protected override DictionaryCacheWrapper DictionaryCache
|
||||
protected override IEnumerable<DictionaryEntry> GetDictionaryEntries()
|
||||
{
|
||||
get { return _wrapper; }
|
||||
const string prefix = CacheItemPrefix + "-";
|
||||
return _cache.Cast<DictionaryEntry>()
|
||||
.Where(x => x.Key is string && ((string) x.Key).StartsWith(prefix));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all objects in the System.Web.Cache with the System.Type specified that satisfy the predicate
|
||||
/// </summary>
|
||||
public override void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
|
||||
protected override void RemoveEntry(string key)
|
||||
{
|
||||
try
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
foreach (DictionaryEntry c in _cache)
|
||||
{
|
||||
var key = c.Key.ToString();
|
||||
if (_cache[key] != null
|
||||
&& _cache[key] is T
|
||||
&& predicate(key, (T)_cache[key]))
|
||||
{
|
||||
_cache.Remove(c.Key.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LogHelper.Error<CacheHelper>("Cache clearing error", e);
|
||||
}
|
||||
_cache.Remove(key);
|
||||
}
|
||||
|
||||
protected override object GetEntry(string key)
|
||||
{
|
||||
return _cache.Get(key);
|
||||
}
|
||||
|
||||
#region Lock
|
||||
|
||||
protected override IDisposable ReadLock
|
||||
{
|
||||
get { return new ReadLock(_locker); }
|
||||
}
|
||||
|
||||
protected override IDisposable WriteLock
|
||||
{
|
||||
get { return new WriteLock(_locker); }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Get
|
||||
|
||||
/// <summary>
|
||||
/// Gets (and adds if necessary) an item from the cache with all of the default parameters
|
||||
/// </summary>
|
||||
@@ -81,25 +84,64 @@ namespace Umbraco.Core.Cache
|
||||
{
|
||||
cacheKey = GetCacheKey(cacheKey);
|
||||
|
||||
using (var lck = new UpgradeableReadLock(Locker))
|
||||
// NOTE - because we don't know what getCacheItem does, how long it will take and whether it will hang,
|
||||
// getCacheItem should run OUTSIDE of the global application lock else we run into lock contention and
|
||||
// nasty performance issues.
|
||||
|
||||
// So.... we insert a Lazy<object> in the cache while holding the global application lock, and then rely
|
||||
// on the Lazy lock to ensure that getCacheItem runs once and everybody waits on it, while the global
|
||||
// application lock has been released.
|
||||
|
||||
// NOTE
|
||||
// The Lazy value creation may produce a null value.
|
||||
// Must make sure (for backward compatibility) that we pretend they are not in the cache.
|
||||
// So if we find an entry in the cache that already has its value created and is null,
|
||||
// pretend it was not there. If value is not already created, wait... and return null, that's
|
||||
// what prior code did.
|
||||
|
||||
// NOTE
|
||||
// The Lazy value creation may throw.
|
||||
|
||||
// So... the null value _will_ be in the cache but never returned
|
||||
|
||||
Lazy<object> result;
|
||||
|
||||
// Fast!
|
||||
// Only one thread can enter an UpgradeableReadLock at a time, but it does not prevent other
|
||||
// threads to enter a ReadLock in the meantime -- only upgrading to WriteLock will prevent all
|
||||
// reads. We first try with a normal ReadLock for maximum concurrency and take the penalty of
|
||||
// having to re-lock in case there's no value. Would need to benchmark to figure out whether
|
||||
// it's worth it, though...
|
||||
using (new ReadLock(_locker))
|
||||
{
|
||||
var result = DictionaryCache.Get(cacheKey);
|
||||
if (result == null)
|
||||
{
|
||||
lck.UpgradeToWriteLock();
|
||||
|
||||
result = getCacheItem();
|
||||
if (result != null)
|
||||
{
|
||||
var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
|
||||
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
|
||||
|
||||
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
|
||||
}
|
||||
|
||||
}
|
||||
return result;
|
||||
result = _cache.Get(cacheKey) as Lazy<object>; // null if key not found
|
||||
}
|
||||
var value = result == null ? null : GetSafeLazyValue(result);
|
||||
if (value != null) return value;
|
||||
|
||||
using (var lck = new UpgradeableReadLock(_locker))
|
||||
{
|
||||
result = _cache.Get(cacheKey) as Lazy<object>; // null if key not found
|
||||
|
||||
// cannot create value within the lock, so if result.IsValueCreated is false, just
|
||||
// do nothing here - means that if creation throws, a race condition could cause
|
||||
// more than one thread to reach the return statement below and throw - accepted.
|
||||
|
||||
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
||||
{
|
||||
result = new Lazy<object>(getCacheItem);
|
||||
var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
|
||||
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
|
||||
|
||||
lck.UpgradeToWriteLock();
|
||||
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
// this may throw if getCacheItem throws, but this is the only place where
|
||||
// it would throw as everywhere else we use GetLazySaveValue() to hide exceptions
|
||||
// and pretend exceptions were never inserted into cache to begin with.
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
public object GetCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
@@ -112,6 +154,10 @@ namespace Umbraco.Core.Cache
|
||||
return GetCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Insert
|
||||
|
||||
/// <summary>
|
||||
/// This overload is here for legacy purposes
|
||||
/// </summary>
|
||||
@@ -124,15 +170,22 @@ namespace Umbraco.Core.Cache
|
||||
/// <param name="dependency"></param>
|
||||
internal void InsertCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null)
|
||||
{
|
||||
var result = getCacheItem();
|
||||
if (result == null) return;
|
||||
// NOTE - here also we must insert a Lazy<object> but we can evaluate it right now
|
||||
// and make sure we don't store a null value.
|
||||
|
||||
var result = new Lazy<object>(getCacheItem);
|
||||
var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
|
||||
if (value == null) return; // do not store null values (backward compat)
|
||||
|
||||
cacheKey = GetCacheKey(cacheKey);
|
||||
|
||||
var absolute = isSliding ? System.Web.Caching.Cache.NoAbsoluteExpiration : (timeout == null ? System.Web.Caching.Cache.NoAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
|
||||
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
|
||||
|
||||
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
|
||||
using (new WriteLock(_locker))
|
||||
{
|
||||
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
|
||||
}
|
||||
}
|
||||
|
||||
public void InsertCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
@@ -144,5 +197,7 @@ namespace Umbraco.Core.Cache
|
||||
}
|
||||
InsertCacheItem(cacheKey, getCacheItem, timeout, isSliding, priority, removedCallback, dependency);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,37 @@ namespace Umbraco.Core.Cache
|
||||
/// </summary>
|
||||
internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
|
||||
{
|
||||
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
|
||||
internal ObjectCache MemoryCache;
|
||||
|
||||
// an object that represent a value that has not been created yet
|
||||
protected readonly object ValueNotCreated = new object();
|
||||
|
||||
public ObjectCacheRuntimeCacheProvider()
|
||||
{
|
||||
MemoryCache = new MemoryCache("in-memory");
|
||||
}
|
||||
|
||||
protected object GetSafeLazyValue(Lazy<object> lazy, bool onlyIfValueIsCreated = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
// if onlyIfValueIsCreated, do not trigger value creation
|
||||
// must return something, though, to differenciate from null values
|
||||
if (onlyIfValueIsCreated && lazy.IsValueCreated == false) return ValueNotCreated;
|
||||
return lazy.Value;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#region Clear
|
||||
|
||||
public virtual void ClearAllCache()
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
using (new WriteLock(_locker))
|
||||
{
|
||||
MemoryCache.DisposeIfDisposable();
|
||||
MemoryCache = new MemoryCache("in-memory");
|
||||
@@ -35,7 +55,7 @@ namespace Umbraco.Core.Cache
|
||||
|
||||
public virtual void ClearCacheItem(string key)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
using (new WriteLock(_locker))
|
||||
{
|
||||
if (MemoryCache[key] == null) return;
|
||||
MemoryCache.Remove(key);
|
||||
@@ -44,131 +64,179 @@ namespace Umbraco.Core.Cache
|
||||
|
||||
public virtual void ClearCacheObjectTypes(string typeName)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
using (new WriteLock(_locker))
|
||||
{
|
||||
var keysToRemove = MemoryCache
|
||||
.Where(c => c.Value != null && c.Value.GetType().ToString().InvariantEquals(typeName))
|
||||
.Select(c => c.Key)
|
||||
.ToArray();
|
||||
foreach (var k in keysToRemove)
|
||||
MemoryCache.Remove(k);
|
||||
foreach (var key in MemoryCache
|
||||
.Where(x =>
|
||||
{
|
||||
// x.Value is Lazy<object> and not null, its value may be null
|
||||
// remove null values as well, does not hurt
|
||||
// get non-created as NonCreatedValue & exceptions as null
|
||||
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
return value == null || value.GetType().ToString().InvariantEquals(typeName);
|
||||
})
|
||||
.Select(x => x.Key)
|
||||
.ToArray()) // ToArray required to remove
|
||||
MemoryCache.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheObjectTypes<T>()
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
using (new WriteLock(_locker))
|
||||
{
|
||||
var typeOfT = typeof (T);
|
||||
var keysToRemove = MemoryCache
|
||||
.Where(c => c.Value != null && c.Value.GetType() == typeOfT)
|
||||
.Select(c => c.Key)
|
||||
.ToArray();
|
||||
foreach (var k in keysToRemove)
|
||||
MemoryCache.Remove(k);
|
||||
foreach (var key in MemoryCache
|
||||
.Where(x =>
|
||||
{
|
||||
// x.Value is Lazy<object> and not null, its value may be null
|
||||
// remove null values as well, does not hurt
|
||||
// get non-created as NonCreatedValue & exceptions as null
|
||||
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
return value == null || value.GetType() == typeOfT;
|
||||
})
|
||||
.Select(x => x.Key)
|
||||
.ToArray()) // ToArray required to remove
|
||||
MemoryCache.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheObjectTypes<T>(Func<string, T, bool> predicate)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
using (new WriteLock(_locker))
|
||||
{
|
||||
var typeOfT = typeof(T);
|
||||
var keysToRemove = MemoryCache
|
||||
.Where(c => c.Value != null && c.Value.GetType() == typeOfT && predicate(c.Key, (T)c.Value))
|
||||
.Select(c => c.Key)
|
||||
.ToArray();
|
||||
foreach (var k in keysToRemove)
|
||||
MemoryCache.Remove(k);
|
||||
foreach (var key in MemoryCache
|
||||
.Where(x =>
|
||||
{
|
||||
// x.Value is Lazy<object> and not null, its value may be null
|
||||
// remove null values as well, does not hurt
|
||||
// get non-created as NonCreatedValue & exceptions as null
|
||||
var value = GetSafeLazyValue((Lazy<object>)x.Value, true);
|
||||
if (value == null) return true;
|
||||
return value.GetType() == typeOfT
|
||||
&& predicate(x.Key, (T) value);
|
||||
})
|
||||
.Select(x => x.Key)
|
||||
.ToArray()) // ToArray required to remove
|
||||
MemoryCache.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheByKeySearch(string keyStartsWith)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
using (new WriteLock(_locker))
|
||||
{
|
||||
var keysToRemove = (from c in MemoryCache where c.Key.InvariantStartsWith(keyStartsWith) select c.Key).ToList();
|
||||
foreach (var k in keysToRemove)
|
||||
{
|
||||
MemoryCache.Remove(k);
|
||||
}
|
||||
foreach (var key in MemoryCache
|
||||
.Where(x => x.Key.InvariantStartsWith(keyStartsWith))
|
||||
.Select(x => x.Key)
|
||||
.ToArray()) // ToArray required to remove
|
||||
MemoryCache.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void ClearCacheByKeyExpression(string regexString)
|
||||
{
|
||||
using (new WriteLock(Locker))
|
||||
using (new WriteLock(_locker))
|
||||
{
|
||||
var keysToRemove = (from c in MemoryCache where Regex.IsMatch(c.Key, regexString) select c.Key).ToList();
|
||||
foreach (var k in keysToRemove)
|
||||
{
|
||||
MemoryCache.Remove(k);
|
||||
}
|
||||
foreach (var key in MemoryCache
|
||||
.Where(x => Regex.IsMatch(x.Key, regexString))
|
||||
.Select(x => x.Key)
|
||||
.ToArray()) // ToArray required to remove
|
||||
MemoryCache.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
public virtual IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
|
||||
#endregion
|
||||
|
||||
#region Get
|
||||
|
||||
public IEnumerable<object> GetCacheItemsByKeySearch(string keyStartsWith)
|
||||
{
|
||||
return (from c in MemoryCache
|
||||
where c.Key.InvariantStartsWith(keyStartsWith)
|
||||
select c.Value).ToList();
|
||||
KeyValuePair<string, object>[] entries;
|
||||
using (new ReadLock(_locker))
|
||||
{
|
||||
entries = MemoryCache
|
||||
.Where(x => x.Key.InvariantStartsWith(keyStartsWith))
|
||||
.ToArray(); // evaluate while locked
|
||||
}
|
||||
return entries
|
||||
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
|
||||
.Where(x => x != null) // backward compat, don't store null values in the cache
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public IEnumerable<object> GetCacheItemsByKeyExpression(string regexString)
|
||||
{
|
||||
return (from c in MemoryCache
|
||||
where Regex.IsMatch(c.Key, regexString)
|
||||
select c.Value).ToList();
|
||||
KeyValuePair<string, object>[] entries;
|
||||
using (new ReadLock(_locker))
|
||||
{
|
||||
entries = MemoryCache
|
||||
.Where(x => Regex.IsMatch(x.Key, regexString))
|
||||
.ToArray(); // evaluate while locked
|
||||
}
|
||||
return entries
|
||||
.Select(x => GetSafeLazyValue((Lazy<object>)x.Value)) // return exceptions as null
|
||||
.Where(x => x != null) // backward compat, don't store null values in the cache
|
||||
.ToList();
|
||||
}
|
||||
|
||||
public virtual object GetCacheItem(string cacheKey)
|
||||
public object GetCacheItem(string cacheKey)
|
||||
{
|
||||
var result = MemoryCache.Get(cacheKey);
|
||||
return result;
|
||||
Lazy<object> result;
|
||||
using (new ReadLock(_locker))
|
||||
{
|
||||
result = MemoryCache.Get(cacheKey) as Lazy<object>; // null if key not found
|
||||
}
|
||||
return result == null ? null : GetSafeLazyValue(result); // return exceptions as null
|
||||
}
|
||||
|
||||
public virtual object GetCacheItem(string cacheKey, Func<object> getCacheItem)
|
||||
public object GetCacheItem(string cacheKey, Func<object> getCacheItem)
|
||||
{
|
||||
return GetCacheItem(cacheKey, getCacheItem, null);
|
||||
}
|
||||
|
||||
public object GetCacheItem(
|
||||
string cacheKey,
|
||||
Func<object> getCacheItem,
|
||||
TimeSpan? timeout,
|
||||
bool isSliding = false,
|
||||
CacheItemPriority priority = CacheItemPriority.Normal,
|
||||
CacheItemRemovedCallback removedCallback = null,
|
||||
string[] dependentFiles = null)
|
||||
public object GetCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal,CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
{
|
||||
using (var lck = new UpgradeableReadLock(Locker))
|
||||
{
|
||||
var result = MemoryCache.Get(cacheKey);
|
||||
if (result == null)
|
||||
{
|
||||
lck.UpgradeToWriteLock();
|
||||
// see notes in HttpRuntimeCacheProvider
|
||||
|
||||
result = getCacheItem();
|
||||
if (result != null)
|
||||
{
|
||||
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
|
||||
MemoryCache.Set(cacheKey, result, policy);
|
||||
}
|
||||
Lazy<object> result;
|
||||
|
||||
using (var lck = new UpgradeableReadLock(_locker))
|
||||
{
|
||||
result = MemoryCache.Get(cacheKey) as Lazy<object>;
|
||||
if (result == null || GetSafeLazyValue(result, true) == null) // get non-created as NonCreatedValue & exceptions as null
|
||||
{
|
||||
result = new Lazy<object>(getCacheItem);
|
||||
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
|
||||
|
||||
lck.UpgradeToWriteLock();
|
||||
MemoryCache.Set(cacheKey, result, policy);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return result.Value;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Insert
|
||||
|
||||
public void InsertCacheItem(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
{
|
||||
object result = getCacheItem();
|
||||
if (result != null)
|
||||
{
|
||||
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
|
||||
MemoryCache.Set(cacheKey, result, policy);
|
||||
}
|
||||
// NOTE - here also we must insert a Lazy<object> but we can evaluate it right now
|
||||
// and make sure we don't store a null value.
|
||||
|
||||
var result = new Lazy<object>(getCacheItem);
|
||||
var value = result.Value; // force evaluation now
|
||||
if (value == null) return; // do not store null values (backward compat)
|
||||
|
||||
var policy = GetPolicy(timeout, isSliding, removedCallback, dependentFiles);
|
||||
MemoryCache.Set(cacheKey, result, policy);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static CacheItemPolicy GetPolicy(TimeSpan? timeout = null, bool isSliding = false, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
{
|
||||
var absolute = isSliding ? ObjectCache.InfiniteAbsoluteExpiration : (timeout == null ? ObjectCache.InfiniteAbsoluteExpiration : DateTime.Now.Add(timeout.Value));
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace Umbraco.Core
|
||||
: this(
|
||||
new HttpRuntimeCacheProvider(HttpRuntime.Cache),
|
||||
new StaticCacheProvider(),
|
||||
new HttpRequestCacheProvider(() => new HttpContextWrapper(HttpContext.Current)))
|
||||
new HttpRequestCacheProvider())
|
||||
{
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Umbraco.Core
|
||||
: this(
|
||||
new HttpRuntimeCacheProvider(cache),
|
||||
new StaticCacheProvider(),
|
||||
new HttpRequestCacheProvider(() => new HttpContextWrapper(HttpContext.Current)))
|
||||
new HttpRequestCacheProvider())
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -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.0");
|
||||
private static readonly Version Version = new Version("6.2.6");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current version of Umbraco.
|
||||
|
||||
@@ -15,43 +15,13 @@ namespace Umbraco.Core
|
||||
public static IEnumerable<IEnumerable<T>> InGroupsOf<T>(this IEnumerable<T> source, int groupSize)
|
||||
{
|
||||
if (source == null)
|
||||
throw new NullReferenceException("source");
|
||||
throw new ArgumentNullException("source");
|
||||
if (groupSize <= 0)
|
||||
throw new ArgumentException("Must be greater than zero.", "groupSize");
|
||||
|
||||
// enumerate the source only once!
|
||||
return new InGroupsEnumerator<T>(source, groupSize).Groups();
|
||||
}
|
||||
|
||||
// this class makes sure that the source is enumerated only ONCE
|
||||
// which means that when it is enumerated, the actual groups content
|
||||
// has to be evaluated at the same time, and stored in an array.
|
||||
private class InGroupsEnumerator<T>
|
||||
{
|
||||
private readonly IEnumerator<T> _source;
|
||||
private readonly int _count;
|
||||
private bool _mightHaveNext;
|
||||
|
||||
public InGroupsEnumerator(IEnumerable<T> source, int count)
|
||||
{
|
||||
_source = source.GetEnumerator();
|
||||
_count = count;
|
||||
_mightHaveNext = true;
|
||||
}
|
||||
|
||||
public IEnumerable<IEnumerable<T>> Groups()
|
||||
{
|
||||
while (_mightHaveNext && _source.MoveNext())
|
||||
yield return Group().ToArray(); // see note above
|
||||
}
|
||||
|
||||
private IEnumerable<T> Group()
|
||||
{
|
||||
var c = 0;
|
||||
do
|
||||
{
|
||||
yield return _source.Current;
|
||||
} while (++c < _count && _source.MoveNext());
|
||||
_mightHaveNext = c == _count;
|
||||
}
|
||||
return source
|
||||
.Select((x, i) => Tuple.Create(i / groupSize, x))
|
||||
.GroupBy(t => t.Item1, t => t.Item2);
|
||||
}
|
||||
|
||||
/// <summary>The distinct by.</summary>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -19,7 +19,10 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public static void CopyFile(this IFileSystem fs, string path, string newPath)
|
||||
{
|
||||
fs.AddFile(newPath, fs.OpenFile(path));
|
||||
using (var stream = fs.OpenFile(path))
|
||||
{
|
||||
fs.AddFile(newPath, stream);
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetExtension(this IFileSystem fs, string path)
|
||||
|
||||
@@ -93,8 +93,9 @@ namespace Umbraco.Core.IO
|
||||
|
||||
public void AddFile(string path, Stream stream, bool overrideIfExists)
|
||||
{
|
||||
if (FileExists(path) && !overrideIfExists) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
|
||||
var exists = FileExists(path);
|
||||
if (exists && overrideIfExists == false) throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
|
||||
EnsureDirectory(Path.GetDirectoryName(path));
|
||||
|
||||
if (stream.CanSeek)
|
||||
|
||||
@@ -133,16 +133,23 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
if (_size == null)
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
if (_fs.FileExists(Path))
|
||||
{
|
||||
EnsureFileSupportsResizing();
|
||||
|
||||
var fs = _fs.OpenFile(Path);
|
||||
var image = Image.FromStream(fs);
|
||||
var fileWidth = image.Width;
|
||||
var fileHeight = image.Height;
|
||||
fs.Close();
|
||||
image.Dispose();
|
||||
using (var fs = _fs.OpenFile(Path))
|
||||
using (var image = Image.FromStream(fs))
|
||||
{
|
||||
|
||||
_size = new Size(fileWidth, fileHeight);
|
||||
var fileWidth = image.Width;
|
||||
var fileHeight = image.Height;
|
||||
_size = new Size(fileWidth, fileHeight);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_size = new Size(-1, -1);
|
||||
}
|
||||
}
|
||||
return _size.Value;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -222,6 +222,28 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Removes characters that are not valide XML characters from all entity properties
|
||||
/// of type string. See: http://stackoverflow.com/a/961504/5018
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// If this is not done then the xml cache can get corrupt and it will throw YSODs upon reading it.
|
||||
/// </remarks>
|
||||
/// <param name="entity"></param>
|
||||
public static void SanitizeEntityPropertiesForXmlStorage(this IContentBase entity)
|
||||
{
|
||||
entity.Name = entity.Name.ToValidXmlString();
|
||||
foreach (var property in entity.Properties)
|
||||
{
|
||||
if (property.Value is string)
|
||||
{
|
||||
var value = (string)property.Value;
|
||||
property.Value = value.ToValidXmlString();
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Checks if the IContentBase has children
|
||||
/// </summary>
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@@ -27,6 +28,11 @@ namespace Umbraco.Core.Models
|
||||
|
||||
public static class DeepCloneHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to avoid constant reflection (perf)
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Type, PropertyInfo[]> PropCache = new ConcurrentDictionary<Type, PropertyInfo[]>();
|
||||
|
||||
/// <summary>
|
||||
/// Used to deep clone any reference properties on the object (should be done after a MemberwiseClone for which the outcome is 'output')
|
||||
/// </summary>
|
||||
@@ -43,16 +49,18 @@ namespace Umbraco.Core.Models
|
||||
throw new InvalidOperationException("Both the input and output types must be the same");
|
||||
}
|
||||
|
||||
var refProperties = inputType.GetProperties()
|
||||
.Where(x =>
|
||||
//is not attributed with the ignore clone attribute
|
||||
var refProperties = PropCache.GetOrAdd(inputType, type =>
|
||||
inputType.GetProperties()
|
||||
.Where(x =>
|
||||
//is not attributed with the ignore clone attribute
|
||||
Attribute.GetCustomAttribute(x, typeof(DoNotCloneAttribute)) == null
|
||||
//reference type but not string
|
||||
&& x.PropertyType.IsValueType == false && x.PropertyType != typeof (string)
|
||||
//settable
|
||||
&& x.CanWrite
|
||||
//non-indexed
|
||||
&& x.GetIndexParameters().Any() == false);
|
||||
//reference type but not string
|
||||
&& x.PropertyType.IsValueType == false && x.PropertyType != typeof (string)
|
||||
//settable
|
||||
&& x.CanWrite
|
||||
//non-indexed
|
||||
&& x.GetIndexParameters().Any() == false)
|
||||
.ToArray());
|
||||
|
||||
foreach (var propertyInfo in refProperties)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -23,5 +20,5 @@ namespace Umbraco.Core.Models
|
||||
var dirty = (IRememberBeingDirty)entity;
|
||||
return dirty.WasPropertyDirty("Id");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,27 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
namespace Umbraco.Core.Models.Membership
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Type for a Backoffice User
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Should be internal until a proper user/membership implementation
|
||||
/// is part of the roadmap.
|
||||
/// </remarks>
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
internal class UserType : Entity, IUserType
|
||||
{
|
||||
[DataMember]
|
||||
public string Alias { get; set; }
|
||||
private string _alias;
|
||||
private string _name;
|
||||
private IEnumerable<string> _permissions;
|
||||
|
||||
private static readonly PropertyInfo NameSelector = ExpressionHelper.GetPropertyInfo<UserType, string>(x => x.Name);
|
||||
private static readonly PropertyInfo AliasSelector = ExpressionHelper.GetPropertyInfo<UserType, string>(x => x.Alias);
|
||||
private static readonly PropertyInfo PermissionsSelector = ExpressionHelper.GetPropertyInfo<UserType, IEnumerable<string>>(x => x.Permissions);
|
||||
|
||||
[DataMember]
|
||||
public string Name { get; set; }
|
||||
public string Alias
|
||||
{
|
||||
get { return _alias; }
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(o =>
|
||||
{
|
||||
_alias = value.ToCleanString(CleanStringType.Alias | CleanStringType.UmbracoCase);
|
||||
return _alias;
|
||||
}, _alias, AliasSelector);
|
||||
}
|
||||
}
|
||||
|
||||
[DataMember]
|
||||
public string Name
|
||||
{
|
||||
get { return _name; }
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(o =>
|
||||
{
|
||||
_name = value;
|
||||
return _name;
|
||||
}, _name, NameSelector);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The set of default permissions for the user type
|
||||
@@ -30,6 +58,17 @@ namespace Umbraco.Core.Models.Membership
|
||||
/// By default each permission is simply a single char but we've made this an enumerable{string} to support a more flexible permissions structure in the future.
|
||||
/// </remarks>
|
||||
[DataMember]
|
||||
public IEnumerable<string> Permissions { get; set; }
|
||||
public IEnumerable<string> Permissions
|
||||
{
|
||||
get { return _permissions; }
|
||||
set
|
||||
{
|
||||
SetPropertyValueAndDetectChanges(o =>
|
||||
{
|
||||
_permissions = value;
|
||||
return _permissions;
|
||||
}, _permissions, PermissionsSelector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides an equivalent to the c# lock statement, to be used in a using block.
|
||||
/// </summary>
|
||||
/// <remarks>Ie replace <c>lock (o) {...}</c> by <c>using (new MonitorLock(o)) { ... }</c></remarks>
|
||||
public class MonitorLock : IDisposable
|
||||
{
|
||||
private readonly object _locker;
|
||||
private readonly bool _entered;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MonitorLock"/> class with an object to lock.
|
||||
/// </summary>
|
||||
/// <param name="locker">The object to lock.</param>
|
||||
/// <remarks>Should always be used within a using block.</remarks>
|
||||
public MonitorLock(object locker)
|
||||
{
|
||||
_locker = locker;
|
||||
_entered = false;
|
||||
System.Threading.Monitor.Enter(_locker, ref _entered);
|
||||
}
|
||||
|
||||
void IDisposable.Dispose()
|
||||
{
|
||||
if (_entered)
|
||||
System.Threading.Monitor.Exit(_locker);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,11 @@ namespace Umbraco.Core.ObjectResolution
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE - the ugly code below exists only because of umbraco.BusinessLogic.Actions.Action.ReRegisterActionsAndHandlers
|
||||
// which wants to re-register actions and handlers instead of properly restarting the application. Don't even think
|
||||
// about using it for anything else. Also, while the backdoor is open, the resolution system is locked so nothing
|
||||
// can work properly => deadlocks. Therefore, open the backdoor, do resolution changes EXCLUSIVELY, and close the door!
|
||||
|
||||
/// <summary>
|
||||
/// Returns a disposable object that reprents dirty access to temporarily unfrozen resolution configuration.
|
||||
/// </summary>
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
ComputerName = entity.ComputerName
|
||||
};
|
||||
if (entity.HasIdentity)
|
||||
dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture));
|
||||
dto.Id = int.Parse(entity.Id.ToString(CultureInfo.InvariantCulture));
|
||||
|
||||
return dto;
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -221,6 +312,7 @@ namespace Umbraco.Core.Persistence
|
||||
if (overwrite && tableExist)
|
||||
{
|
||||
db.DropTable(tableName);
|
||||
tableExist = false;
|
||||
}
|
||||
|
||||
if (tableExist == false)
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,6 +245,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
//Ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
|
||||
|
||||
//Ensure that strings don't contain characters that are invalid in XML
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
var factory = new ContentFactory(NodeObjectTypeId, entity.Id);
|
||||
var dto = factory.BuildDto(entity);
|
||||
@@ -350,6 +353,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
|
||||
|
||||
//Ensure that strings don't contain characters that are invalid in XML
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
|
||||
if (((ICanBeDirty)entity).IsPropertyDirty("ParentId"))
|
||||
{
|
||||
@@ -569,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>
|
||||
@@ -591,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);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -144,6 +144,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
((DictionaryItem)entity).AddingEntity();
|
||||
|
||||
foreach (var translation in entity.Translations)
|
||||
translation.Value = translation.Value.ToValidXmlString();
|
||||
|
||||
var factory = new DictionaryItemFactory();
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
@@ -165,6 +168,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
((Entity)entity).UpdatingEntity();
|
||||
|
||||
foreach (var translation in entity.Translations)
|
||||
translation.Value = translation.Value.ToValidXmlString();
|
||||
|
||||
var factory = new DictionaryItemFactory();
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,17 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
public interface IRepositoryVersionable<TId, TEntity> : IRepositoryQueryable<TId, TEntity>
|
||||
where TEntity : IAggregateRoot
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the total count of entities
|
||||
/// </summary>
|
||||
/// <param name="contentTypeAlias"></param>
|
||||
/// <returns></returns>
|
||||
int Count(string contentTypeAlias = null);
|
||||
|
||||
int CountChildren(int parentId, string contentTypeAlias = null);
|
||||
|
||||
int CountDescendants(int parentId, string contentTypeAlias = null);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of all versions for an <see cref="TEntity"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
var sql = GetBaseQuery(false);
|
||||
sql.Where(GetBaseWhereClause(), new { Id = id });
|
||||
|
||||
var languageDto = Database.First<LanguageDto>(sql);
|
||||
var languageDto = Database.FirstOrDefault<LanguageDto>(sql);
|
||||
if (languageDto == null)
|
||||
return null;
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -213,6 +207,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name);
|
||||
|
||||
//Ensure that strings don't contain characters that are invalid in XML
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
var factory = new MediaFactory(NodeObjectTypeId, entity.Id);
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
@@ -280,6 +277,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
|
||||
|
||||
//Ensure that strings don't contain characters that are invalid in XML
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
|
||||
if (((ICanBeDirty)entity).IsPropertyDirty("ParentId"))
|
||||
{
|
||||
@@ -413,4 +413,4 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return currentName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -204,6 +204,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
((Member)entity).AddingEntity();
|
||||
|
||||
//Ensure that strings don't contain characters that are invalid in XML
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
var factory = new MemberFactory(NodeObjectTypeId, entity.Id);
|
||||
var dto = factory.BuildDto(entity);
|
||||
|
||||
@@ -277,6 +280,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
//Updates Modified date
|
||||
((Member)entity).UpdatingEntity();
|
||||
|
||||
//Ensure that strings don't contain characters that are invalid in XML
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
var dirtyEntity = (ICanBeDirty)entity;
|
||||
|
||||
//Look up parent to get and set the correct Path and update SortOrder if ParentId has changed
|
||||
@@ -514,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;
|
||||
}
|
||||
@@ -603,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)
|
||||
|
||||
@@ -130,6 +130,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <returns></returns>
|
||||
public IEnumerable<TEntity> GetAll(params TId[] ids)
|
||||
{
|
||||
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
|
||||
ids = ids.Distinct().ToArray();
|
||||
|
||||
if (ids.Any())
|
||||
{
|
||||
var entities = _cache.GetByIds(
|
||||
|
||||
@@ -64,6 +64,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public override IEnumerable<Script> GetAll(params string[] ids)
|
||||
{
|
||||
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
|
||||
ids = ids.Distinct().ToArray();
|
||||
|
||||
if (ids.Any())
|
||||
{
|
||||
foreach (var id in ids)
|
||||
|
||||
@@ -85,6 +85,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
public override IEnumerable<Stylesheet> GetAll(params string[] ids)
|
||||
{
|
||||
//ensure they are de-duplicated, easy win if people don't do this as this can cause many excess queries
|
||||
ids = ids.Distinct().ToArray();
|
||||
|
||||
if (ids.Any())
|
||||
{
|
||||
foreach (var id in ids)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -93,6 +93,90 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
#endregion
|
||||
|
||||
public int CountDescendants(int parentId, string contentTypeAlias = null)
|
||||
{
|
||||
var pathMatch = parentId == -1
|
||||
? "-1,"
|
||||
: "," + parentId + ",";
|
||||
var sql = new Sql();
|
||||
if (contentTypeAlias.IsNullOrWhiteSpace())
|
||||
{
|
||||
sql.Select("COUNT(*)")
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
|
||||
.Where<NodeDto>(x => x.Path.Contains(pathMatch));
|
||||
}
|
||||
else
|
||||
{
|
||||
sql.Select("COUNT(*)")
|
||||
.From<NodeDto>()
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<ContentTypeDto>()
|
||||
.On<ContentTypeDto, ContentDto>(left => left.NodeId, right => right.ContentTypeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
|
||||
.Where<NodeDto>(x => x.Path.Contains(pathMatch))
|
||||
.Where<ContentTypeDto>(x => x.Alias == contentTypeAlias);
|
||||
}
|
||||
|
||||
return Database.ExecuteScalar<int>(sql);
|
||||
}
|
||||
|
||||
public int CountChildren(int parentId, string contentTypeAlias = null)
|
||||
{
|
||||
var sql = new Sql();
|
||||
if (contentTypeAlias.IsNullOrWhiteSpace())
|
||||
{
|
||||
sql.Select("COUNT(*)")
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
|
||||
.Where<NodeDto>(x => x.ParentId == parentId);
|
||||
}
|
||||
else
|
||||
{
|
||||
sql.Select("COUNT(*)")
|
||||
.From<NodeDto>()
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<ContentTypeDto>()
|
||||
.On<ContentTypeDto, ContentDto>(left => left.NodeId, right => right.ContentTypeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
|
||||
.Where<NodeDto>(x => x.ParentId == parentId)
|
||||
.Where<ContentTypeDto>(x => x.Alias == contentTypeAlias);
|
||||
}
|
||||
|
||||
return Database.ExecuteScalar<int>(sql);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the total count of entities
|
||||
/// </summary>
|
||||
/// <param name="contentTypeAlias"></param>
|
||||
/// <returns></returns>
|
||||
public int Count(string contentTypeAlias = null)
|
||||
{
|
||||
var sql = new Sql();
|
||||
if (contentTypeAlias.IsNullOrWhiteSpace())
|
||||
{
|
||||
sql.Select("COUNT(*)")
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
|
||||
}
|
||||
else
|
||||
{
|
||||
sql.Select("COUNT(*)")
|
||||
.From<NodeDto>()
|
||||
.InnerJoin<ContentDto>()
|
||||
.On<NodeDto, ContentDto>(left => left.NodeId, right => right.NodeId)
|
||||
.InnerJoin<ContentTypeDto>()
|
||||
.On<ContentTypeDto, ContentDto>(left => left.NodeId, right => right.ContentTypeId)
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
|
||||
.Where<ContentTypeDto>(x => x.Alias == contentTypeAlias);
|
||||
}
|
||||
|
||||
return Database.ExecuteScalar<int>(sql);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a fix for U4-1407 - when property types are added to a content type - the property of the entity are not actually created
|
||||
/// and we get YSODs
|
||||
@@ -104,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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Publishing
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to perform scheduled publishing/unpublishing
|
||||
/// </summary>
|
||||
internal class ScheduledPublisher
|
||||
{
|
||||
private readonly IContentService _contentService;
|
||||
|
||||
public ScheduledPublisher(IContentService contentService)
|
||||
{
|
||||
_contentService = contentService;
|
||||
}
|
||||
|
||||
public void CheckPendingAndProcess()
|
||||
{
|
||||
foreach (var d in _contentService.GetContentForRelease())
|
||||
{
|
||||
try
|
||||
{
|
||||
d.ReleaseDate = null;
|
||||
var result = _contentService.SaveAndPublishWithStatus(d, (int)d.GetWriterProfile().Id);
|
||||
if (result.Success == false)
|
||||
{
|
||||
if (result.Exception != null)
|
||||
{
|
||||
LogHelper.Error<ScheduledPublisher>("Could not published the document (" + d.Id + ") based on it's scheduled release, status result: " + result.Result.StatusType, result.Exception);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogHelper.Warn<ScheduledPublisher>("Could not published the document (" + d.Id + ") based on it's scheduled release. Status result: " + result.Result.StatusType);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
LogHelper.Error<ScheduledPublisher>(string.Format("Error publishing node {0}", d.Id), ee);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
foreach (var d in _contentService.GetContentForExpiration())
|
||||
{
|
||||
try
|
||||
{
|
||||
d.ExpireDate = null;
|
||||
_contentService.UnPublish(d, (int)d.GetWriterProfile().Id);
|
||||
}
|
||||
catch (Exception ee)
|
||||
{
|
||||
LogHelper.Error<ScheduledPublisher>(string.Format("Error unpublishing node {0}", d.Id), ee);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -211,7 +211,7 @@ namespace Umbraco.Core.Security
|
||||
_enablePasswordReset = config.GetValue("enablePasswordReset", false);
|
||||
_requiresQuestionAndAnswer = config.GetValue("requiresQuestionAndAnswer", false);
|
||||
_requiresUniqueEmail = config.GetValue("requiresUniqueEmail", true);
|
||||
_maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 5, false, 0);
|
||||
_maxInvalidPasswordAttempts = GetIntValue(config, "maxInvalidPasswordAttempts", 20, false, 0);
|
||||
_passwordAttemptWindow = GetIntValue(config, "passwordAttemptWindow", 10, false, 0);
|
||||
_minRequiredPasswordLength = GetIntValue(config, "minRequiredPasswordLength", DefaultMinPasswordLength, true, 0x80);
|
||||
_minRequiredNonAlphanumericCharacters = GetIntValue(config, "minRequiredNonalphanumericCharacters", DefaultMinNonAlphanumericChars, true, 0x80);
|
||||
|
||||
@@ -62,7 +62,12 @@ namespace Umbraco.Core.Security
|
||||
return Membership.Providers[UmbracoSettings.DefaultBackofficeProvider];
|
||||
}
|
||||
|
||||
public static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider)
|
||||
/// <summary>
|
||||
/// Returns the currently logged in MembershipUser and flags them as being online - use sparingly (i.e. login)
|
||||
/// </summary>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
public static MembershipUser GetCurrentUserOnline(this MembershipProvider membershipProvider)
|
||||
{
|
||||
var username = membershipProvider.GetCurrentUserName();
|
||||
return username.IsNullOrWhiteSpace()
|
||||
@@ -70,6 +75,19 @@ namespace Umbraco.Core.Security
|
||||
: membershipProvider.GetUser(username, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently logged in MembershipUser
|
||||
/// </summary>
|
||||
/// <param name="membershipProvider"></param>
|
||||
/// <returns></returns>
|
||||
public static MembershipUser GetCurrentUser(this MembershipProvider membershipProvider)
|
||||
{
|
||||
var username = membershipProvider.GetCurrentUserName();
|
||||
return username.IsNullOrWhiteSpace()
|
||||
? null
|
||||
: membershipProvider.GetUser(username, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Just returns the current user's login name (just a wrapper).
|
||||
/// </summary>
|
||||
|
||||
@@ -74,6 +74,33 @@ namespace Umbraco.Core.Services
|
||||
_dataTypeService = dataTypeService;
|
||||
}
|
||||
|
||||
public int Count(string contentTypeAlias = null)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateContentRepository(uow))
|
||||
{
|
||||
return repository.Count(contentTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
public int CountChildren(int parentId, string contentTypeAlias = null)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateContentRepository(uow))
|
||||
{
|
||||
return repository.CountChildren(parentId, contentTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
public int CountDescendants(int parentId, string contentTypeAlias = null)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateContentRepository(uow))
|
||||
{
|
||||
return repository.CountDescendants(parentId, contentTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a single permission to the current content item for the specified user ids
|
||||
/// </summary>
|
||||
@@ -457,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);
|
||||
}
|
||||
|
||||
@@ -469,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;
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Umbraco.Core.Services
|
||||
/// </summary>
|
||||
public interface IContentService : IService
|
||||
{
|
||||
int Count(string contentTypeAlias = null);
|
||||
int CountChildren(int parentId, string contentTypeAlias = null);
|
||||
int CountDescendants(int parentId, string contentTypeAlias = null);
|
||||
|
||||
/// <summary>
|
||||
/// Assigns a single permission to the current content item for the specified user ids
|
||||
/// </summary>
|
||||
|
||||
@@ -9,6 +9,10 @@ namespace Umbraco.Core.Services
|
||||
/// </summary>
|
||||
public interface IMediaService : IService
|
||||
{
|
||||
int Count(string contentTypeAlias = null);
|
||||
int CountChildren(int parentId, string contentTypeAlias = null);
|
||||
int CountDescendants(int parentId, string contentTypeAlias = null);
|
||||
|
||||
IEnumerable<IMedia> GetByIds(IEnumerable<int> ids);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace Umbraco.Core.Services
|
||||
/// </summary>
|
||||
public interface IMemberService : IMembershipMemberService
|
||||
{
|
||||
int Count(string contentTypeAlias = null);
|
||||
|
||||
IMember CreateMember(string username, string email, string name, string memberTypeAlias);
|
||||
IMember CreateMember(string username, string email, string name, IMemberType memberType);
|
||||
IMember CreateMemberWithIdentity(string username, string email, string name, string memberTypeAlias);
|
||||
|
||||
@@ -237,6 +237,15 @@ namespace Umbraco.Core.Services
|
||||
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
|
||||
bool AreRelated(IUmbracoEntity parent, IUmbracoEntity child, string relationTypeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether two items are related
|
||||
/// </summary>
|
||||
/// <param name="parentId">Id of the Parent relation</param>
|
||||
/// <param name="childId">Id of the Child relation</param>
|
||||
/// <param name="relationTypeAlias">Alias of the type of relation to create</param>
|
||||
/// <returns>Returns <c>True</c> if any relations exist between the entities, otherwise <c>False</c></returns>
|
||||
bool AreRelated(int parentId, int childId, string relationTypeAlias);
|
||||
|
||||
/// <summary>
|
||||
/// Saves a <see cref="Relation"/>
|
||||
/// </summary>
|
||||
|
||||
@@ -251,6 +251,33 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public int Count(string contentTypeAlias = null)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateMediaRepository(uow))
|
||||
{
|
||||
return repository.Count(contentTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
public int CountChildren(int parentId, string contentTypeAlias = null)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateMediaRepository(uow))
|
||||
{
|
||||
return repository.CountChildren(parentId, contentTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
public int CountDescendants(int parentId, string contentTypeAlias = null)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateMediaRepository(uow))
|
||||
{
|
||||
return repository.CountDescendants(parentId, contentTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="IMedia"/> object by Id
|
||||
/// </summary>
|
||||
@@ -375,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);
|
||||
}
|
||||
|
||||
@@ -388,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;
|
||||
@@ -1171,4 +1203,4 @@ namespace Umbraco.Core.Services
|
||||
public static event TypedEventHandler<IMediaService, RecycleBinEventArgs> EmptiedRecycleBin;
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -642,6 +642,15 @@ namespace Umbraco.Core.Services
|
||||
}
|
||||
}
|
||||
|
||||
public int Count(string contentTypeAlias = null)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var repository = _repositoryFactory.CreateMemberRepository(uow))
|
||||
{
|
||||
return repository.Count(contentTypeAlias);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a member object
|
||||
/// </summary>
|
||||
|
||||
@@ -773,6 +773,7 @@ namespace Umbraco.Core.Services
|
||||
var items = new List<IDictionaryItem>();
|
||||
foreach (var dictionaryItemElement in dictionaryItemElementList.Elements("DictionaryItem"))
|
||||
items.AddRange(ImportDictionaryItem(dictionaryItemElement, languages, parentId));
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
@@ -788,6 +789,7 @@ namespace Umbraco.Core.Services
|
||||
dictionaryItem = CreateNewDictionaryItem(key, dictionaryItemElement, languages, parentId);
|
||||
_localizationService.Save(dictionaryItem);
|
||||
items.Add(dictionaryItem);
|
||||
|
||||
items.AddRange(ImportDictionaryItems(dictionaryItemElement, languages, dictionaryItem.Key));
|
||||
return items;
|
||||
}
|
||||
@@ -817,7 +819,8 @@ namespace Umbraco.Core.Services
|
||||
private static bool DictionaryValueIsNew(IEnumerable<IDictionaryTranslation> translations, XElement valueElement)
|
||||
{
|
||||
return translations.All(t =>
|
||||
String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value, StringComparison.InvariantCultureIgnoreCase) != 0
|
||||
String.Compare(t.Language.IsoCode, valueElement.Attribute("LanguageCultureAlias").Value,
|
||||
StringComparison.InvariantCultureIgnoreCase) != 0
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -30,13 +30,14 @@ namespace Umbraco.Core
|
||||
|
||||
private static readonly char[] ToCSharpHexDigitLower = "0123456789abcdef".ToCharArray();
|
||||
private static readonly char[] ToCSharpEscapeChars;
|
||||
|
||||
|
||||
static StringExtensions()
|
||||
{
|
||||
var escapes = new[] { "\aa", "\bb", "\ff", "\nn", "\rr", "\tt", "\vv", "\"\"", "\\\\", "??", "\00" };
|
||||
ToCSharpEscapeChars = new char[escapes.Max(e => e[0]) + 1];
|
||||
foreach (var escape in escapes)
|
||||
ToCSharpEscapeChars[escape[0]] = escape[1];
|
||||
|
||||
}
|
||||
|
||||
internal static string ReplaceNonAlphanumericChars(this string input, char replacement)
|
||||
@@ -73,65 +74,66 @@ namespace Umbraco.Core
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the string using the MachineKey in medium trust
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to be encrypted.</param>
|
||||
/// <returns>The encrypted string.</returns>
|
||||
public static string EncryptWithMachineKey(this string value)
|
||||
|
||||
/// <summary>
|
||||
/// Encrypt the string using the MachineKey in medium trust
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to be encrypted.</param>
|
||||
/// <returns>The encrypted string.</returns>
|
||||
public static string EncryptWithMachineKey(this string value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
string valueToEncrypt = value;
|
||||
List<string> parts = new List<string>();
|
||||
string valueToEncrypt = value;
|
||||
List<string> parts = new List<string>();
|
||||
|
||||
const int EncrpytBlockSize = 500;
|
||||
const int EncrpytBlockSize = 500;
|
||||
|
||||
while (valueToEncrypt.Length > EncrpytBlockSize)
|
||||
{
|
||||
parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize));
|
||||
valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize);
|
||||
}
|
||||
while (valueToEncrypt.Length > EncrpytBlockSize)
|
||||
{
|
||||
parts.Add(valueToEncrypt.Substring(0, EncrpytBlockSize));
|
||||
valueToEncrypt = valueToEncrypt.Remove(0, EncrpytBlockSize);
|
||||
}
|
||||
|
||||
if (valueToEncrypt.Length > 0)
|
||||
{
|
||||
parts.Add(valueToEncrypt);
|
||||
}
|
||||
if (valueToEncrypt.Length > 0)
|
||||
{
|
||||
parts.Add(valueToEncrypt);
|
||||
}
|
||||
|
||||
StringBuilder encrpytedValue = new StringBuilder();
|
||||
StringBuilder encrpytedValue = new StringBuilder();
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part));
|
||||
encrpytedValue.AppendLine(encrpytedBlock);
|
||||
}
|
||||
foreach (var part in parts)
|
||||
{
|
||||
var encrpytedBlock = FormsAuthentication.Encrypt(new FormsAuthenticationTicket(0, string.Empty, DateTime.Now, DateTime.MaxValue, false, part));
|
||||
encrpytedValue.AppendLine(encrpytedBlock);
|
||||
}
|
||||
|
||||
return encrpytedValue.ToString().TrimEnd();
|
||||
return encrpytedValue.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decrypt the encrypted string using the Machine key in medium trust
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to be decrypted</param>
|
||||
/// <returns>The decrypted string.</returns>
|
||||
public static string DecryptWithMachineKey(this string value)
|
||||
/// Decrypt the encrypted string using the Machine key in medium trust
|
||||
/// </summary>
|
||||
/// <param name="value">The string value to be decrypted</param>
|
||||
/// <returns>The decrypted string.</returns>
|
||||
public static string DecryptWithMachineKey(this string value)
|
||||
{
|
||||
if (value == null)
|
||||
return null;
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
string[] parts = value.Split('\n');
|
||||
string[] parts = value.Split('\n');
|
||||
|
||||
StringBuilder decryptedValue = new StringBuilder();
|
||||
StringBuilder decryptedValue = new StringBuilder();
|
||||
|
||||
foreach (var part in parts)
|
||||
{
|
||||
decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData);
|
||||
}
|
||||
foreach (var part in parts)
|
||||
{
|
||||
decryptedValue.Append(FormsAuthentication.Decrypt(part.TrimEnd()).UserData);
|
||||
}
|
||||
|
||||
return decryptedValue.ToString();
|
||||
return decryptedValue.ToString();
|
||||
}
|
||||
|
||||
|
||||
//this is from SqlMetal and just makes it a bit of fun to allow pluralisation
|
||||
public static string MakePluralName(this string name)
|
||||
{
|
||||
@@ -877,8 +879,8 @@ namespace Umbraco.Core
|
||||
{
|
||||
var helper = ShortStringHelper;
|
||||
var legacy = helper as LegacyShortStringHelper;
|
||||
return legacy != null
|
||||
? legacy.LegacyToUrlAlias(value, charReplacements, replaceDoubleDashes, stripNonAscii, urlEncode)
|
||||
return legacy != null
|
||||
? legacy.LegacyToUrlAlias(value, charReplacements, replaceDoubleDashes, stripNonAscii, urlEncode)
|
||||
: helper.CleanStringForUrlSegment(value);
|
||||
}
|
||||
|
||||
@@ -972,7 +974,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
// the new methods to get a url segment
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Cleans a string to produce a string that can safely be used in an url segment.
|
||||
/// </summary>
|
||||
@@ -1110,7 +1112,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
return ShortStringHelper.CleanStringForSafeFileName(text, culture);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An extension method that returns a new string in which all occurrences of a
|
||||
/// specified string in the current instance are replaced with another specified string.
|
||||
@@ -1129,7 +1131,7 @@ namespace Umbraco.Core
|
||||
int index = -1 * newString.Length;
|
||||
|
||||
// Determine if there are any matches left in source, starting from just after the result of replacing the last match.
|
||||
while((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0)
|
||||
while ((index = source.IndexOf(oldString, index + newString.Length, stringComparison)) >= 0)
|
||||
{
|
||||
// Remove the old text.
|
||||
source = source.Remove(index, oldString.Length);
|
||||
@@ -1200,5 +1202,28 @@ namespace Umbraco.Core
|
||||
var idCheckList = csv.Split(new[] { "," }, StringSplitOptions.RemoveEmptyEntries);
|
||||
return idCheckList.Contains(value);
|
||||
}
|
||||
|
||||
// From: http://stackoverflow.com/a/961504/5018
|
||||
// filters control characters but allows only properly-formed surrogate sequences
|
||||
private static readonly Regex InvalidXmlChars =
|
||||
new Regex(
|
||||
@"(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\uFEFF\uFFFE\uFFFF]",
|
||||
RegexOptions.Compiled);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An extension method that returns a new string in which all occurrences of an
|
||||
/// unicode characters that are invalid in XML files are replaced with an empty string.
|
||||
/// </summary>
|
||||
/// <param name="text">Current instance of the string</param>
|
||||
/// <returns>Updated string</returns>
|
||||
///
|
||||
/// <summary>
|
||||
/// removes any unusual unicode characters that can't be encoded into XML
|
||||
/// </summary>
|
||||
internal static string ToValidXmlString(this string text)
|
||||
{
|
||||
return string.IsNullOrEmpty(text) ? text : InvalidXmlChars.Replace(text, "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// The current status of the server in the Umbraco environment
|
||||
/// </summary>
|
||||
internal enum CurrentServerEnvironmentStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// If the current server is detected as the 'master' server when configured in a load balanced scenario
|
||||
/// </summary>
|
||||
Master,
|
||||
|
||||
/// <summary>
|
||||
/// If the current server is detected as a 'slave' server when configured in a load balanced scenario
|
||||
/// </summary>
|
||||
Slave,
|
||||
|
||||
/// <summary>
|
||||
/// If the current server cannot be detected as a 'slave' or 'master' when configured in a load balanced scenario
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// If load balancing is not enabled and this is the only server in the umbraco environment
|
||||
/// </summary>
|
||||
Single
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading;
|
||||
using System.Web;
|
||||
using System.Web.Script.Serialization;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -19,18 +20,19 @@ namespace Umbraco.Core.Sync
|
||||
{
|
||||
private readonly Func<Tuple<string, string>> _getUserNamePasswordDelegate;
|
||||
private volatile bool _hasResolvedDelegate = false;
|
||||
private readonly object _locker = new object();
|
||||
private bool _useDistributedCalls;
|
||||
private readonly object _locker = new object();
|
||||
|
||||
protected string Login { get; private set; }
|
||||
protected string Password{ get; private set; }
|
||||
|
||||
protected bool UseDistributedCalls { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Without a username/password all distribuion will be disabled
|
||||
/// </summary>
|
||||
internal DefaultServerMessenger()
|
||||
{
|
||||
_useDistributedCalls = false;
|
||||
UseDistributedCalls = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -54,7 +56,7 @@ namespace Umbraco.Core.Sync
|
||||
if (login == null) throw new ArgumentNullException("login");
|
||||
if (password == null) throw new ArgumentNullException("password");
|
||||
|
||||
_useDistributedCalls = useDistributedCalls;
|
||||
UseDistributedCalls = useDistributedCalls;
|
||||
Login = login;
|
||||
Password = password;
|
||||
}
|
||||
@@ -220,13 +222,13 @@ namespace Umbraco.Core.Sync
|
||||
{
|
||||
Login = null;
|
||||
Password = null;
|
||||
_useDistributedCalls = false;
|
||||
UseDistributedCalls = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
Login = result.Item1;
|
||||
Password = result.Item2;
|
||||
_useDistributedCalls = UmbracoSettings.UseDistributedCalls;
|
||||
UseDistributedCalls = UmbracoSettings.UseDistributedCalls;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -234,7 +236,7 @@ namespace Umbraco.Core.Sync
|
||||
LogHelper.Error<DefaultServerMessenger>("Could not resolve username/password delegate, server distribution will be disabled", ex);
|
||||
Login = null;
|
||||
Password = null;
|
||||
_useDistributedCalls = false;
|
||||
UseDistributedCalls = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -313,7 +315,7 @@ namespace Umbraco.Core.Sync
|
||||
|
||||
//Now, check if we are using Distrubuted calls. If there are no servers in the list then we
|
||||
// can definitely not distribute.
|
||||
if (!_useDistributedCalls || !servers.Any())
|
||||
if (!UseDistributedCalls || !servers.Any())
|
||||
{
|
||||
//if we are not, then just invoke the call on the cache refresher
|
||||
InvokeMethodOnRefresherInstance(refresher, dispatchType, getId, instances);
|
||||
@@ -324,7 +326,7 @@ namespace Umbraco.Core.Sync
|
||||
MessageSeversForIdsOrJson(servers, refresher, dispatchType, instances.Select(getId));
|
||||
}
|
||||
|
||||
private void MessageSeversForIdsOrJson(
|
||||
protected virtual void MessageSeversForIdsOrJson(
|
||||
IEnumerable<IServerAddress> servers,
|
||||
ICacheRefresher refresher,
|
||||
MessageType dispatchType,
|
||||
@@ -344,7 +346,7 @@ namespace Umbraco.Core.Sync
|
||||
|
||||
//Now, check if we are using Distrubuted calls. If there are no servers in the list then we
|
||||
// can definitely not distribute.
|
||||
if (!_useDistributedCalls || !servers.Any())
|
||||
if (!UseDistributedCalls || !servers.Any())
|
||||
{
|
||||
//if we are not, then just invoke the call on the cache refresher
|
||||
InvokeMethodOnRefresherInstance(refresher, dispatchType, ids, jsonPayload);
|
||||
@@ -455,16 +457,16 @@ namespace Umbraco.Core.Sync
|
||||
}
|
||||
}
|
||||
|
||||
List<WaitHandle> waitHandlesList;
|
||||
var asyncResults = GetAsyncResults(asyncResultsList, out waitHandlesList);
|
||||
|
||||
var waitHandlesList = asyncResultsList.Select(x => x.AsyncWaitHandle).ToArray();
|
||||
|
||||
var errorCount = 0;
|
||||
|
||||
// Once for each WaitHandle that we have, wait for a response and log it
|
||||
// We're previously submitted all these requests effectively in parallel and will now retrieve responses on a FIFO basis
|
||||
foreach (var t in asyncResults)
|
||||
//Wait for all requests to complete
|
||||
WaitHandle.WaitAll(waitHandlesList.ToArray());
|
||||
|
||||
foreach (var t in asyncResultsList)
|
||||
{
|
||||
var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15));
|
||||
//var handleIndex = WaitHandle.WaitAny(waitHandlesList.ToArray(), TimeSpan.FromSeconds(15));
|
||||
|
||||
try
|
||||
{
|
||||
@@ -519,18 +521,7 @@ namespace Umbraco.Core.Sync
|
||||
LogDispatchBatchError(ee);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IAsyncResult> GetAsyncResults(List<IAsyncResult> asyncResultsList, out List<WaitHandle> waitHandlesList)
|
||||
{
|
||||
var asyncResults = asyncResultsList.ToArray();
|
||||
waitHandlesList = new List<WaitHandle>();
|
||||
foreach (var asyncResult in asyncResults)
|
||||
{
|
||||
waitHandlesList.Add(asyncResult.AsyncWaitHandle);
|
||||
}
|
||||
return asyncResults;
|
||||
}
|
||||
|
||||
|
||||
private void LogDispatchBatchError(Exception ee)
|
||||
{
|
||||
LogHelper.Error<DefaultServerMessenger>("Error refreshing distributed list", ee);
|
||||
|
||||
@@ -8,5 +8,7 @@ namespace Umbraco.Core.Sync
|
||||
internal interface IServerAddress
|
||||
{
|
||||
string ServerAddress { get; }
|
||||
|
||||
//TODO : Should probably add things like port, protocol, server name, app id
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
[Serializable]
|
||||
public class RefreshInstruction
|
||||
{
|
||||
public RefreshMethodType RefreshType { get; set; }
|
||||
public Guid RefresherId { get; set; }
|
||||
public Guid GuidId { get; set; }
|
||||
public int IntId { get; set; }
|
||||
public string JsonIds { get; set; }
|
||||
public string JsonPayload { get; set; }
|
||||
|
||||
[Serializable]
|
||||
public enum RefreshMethodType
|
||||
{
|
||||
RefreshAll,
|
||||
RefreshByGuid,
|
||||
RefreshById,
|
||||
RefreshByIds,
|
||||
RefreshByJson,
|
||||
RemoveById
|
||||
}
|
||||
|
||||
protected bool Equals(RefreshInstruction other)
|
||||
{
|
||||
return RefreshType == other.RefreshType && RefresherId.Equals(other.RefresherId) && GuidId.Equals(other.GuidId) && IntId == other.IntId && string.Equals(JsonIds, other.JsonIds) && string.Equals(JsonPayload, other.JsonPayload);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((RefreshInstruction) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = (int) RefreshType;
|
||||
hashCode = (hashCode*397) ^ RefresherId.GetHashCode();
|
||||
hashCode = (hashCode*397) ^ GuidId.GetHashCode();
|
||||
hashCode = (hashCode*397) ^ IntId;
|
||||
hashCode = (hashCode*397) ^ (JsonIds != null ? JsonIds.GetHashCode() : 0);
|
||||
hashCode = (hashCode*397) ^ (JsonPayload != null ? JsonPayload.GetHashCode() : 0);
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(RefreshInstruction left, RefreshInstruction right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(RefreshInstruction left, RefreshInstruction right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
/// <summary>
|
||||
/// A helper used to determine the current server environment status
|
||||
/// </summary>
|
||||
internal static class ServerEnvironmentHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the current umbraco base url for the current server depending on it's environment
|
||||
/// 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 ) - 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)
|
||||
{
|
||||
// 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;
|
||||
|
||||
var nodes = servers.SelectNodes("./server");
|
||||
if (nodes == null)
|
||||
{
|
||||
// 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();
|
||||
|
||||
foreach (var xmlNode in xmlNodes)
|
||||
{
|
||||
var appId = xmlNode.AttributeValue<string>("appId");
|
||||
var serverName = xmlNode.AttributeValue<string>("serverName");
|
||||
|
||||
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|
||||
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
|
||||
{
|
||||
//match by appId or computer name! return the url configured
|
||||
return string.Format("{0}://{1}:{2}/{3}",
|
||||
xmlNode.AttributeValue<string>("forceProtocol").IsNullOrWhiteSpace() ? "http" : xmlNode.AttributeValue<string>("forceProtocol"),
|
||||
xmlNode.InnerText,
|
||||
xmlNode.AttributeValue<string>("forcePortnumber").IsNullOrWhiteSpace() ? "80" : xmlNode.AttributeValue<string>("forcePortnumber"),
|
||||
IOHelper.ResolveUrl(SystemDirectories.Umbraco).TrimStart('/'));
|
||||
}
|
||||
}
|
||||
|
||||
// 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>
|
||||
/// Returns the current environment status for the current server
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static CurrentServerEnvironmentStatus GetStatus()
|
||||
{
|
||||
if (UmbracoSettings.UseDistributedCalls == false)
|
||||
{
|
||||
return CurrentServerEnvironmentStatus.Single;
|
||||
}
|
||||
|
||||
var servers = UmbracoSettings.DistributionServers;
|
||||
|
||||
var nodes = servers.SelectNodes("./server");
|
||||
if (nodes == null)
|
||||
{
|
||||
return CurrentServerEnvironmentStatus.Unknown;
|
||||
}
|
||||
|
||||
var master = nodes.Cast<XmlNode>().FirstOrDefault();
|
||||
|
||||
if (master == null)
|
||||
{
|
||||
return CurrentServerEnvironmentStatus.Unknown;
|
||||
}
|
||||
|
||||
//we determine master/slave based on the first server registered
|
||||
//TODO: In v7 we have publicized ServerRegisterResolver - we won't be able to determine this based on that
|
||||
// but we'd need to change the IServerAddress interfaces which is breaking.
|
||||
|
||||
var appId = master.AttributeValue<string>("appId");
|
||||
var serverName = master.AttributeValue<string>("serverName");
|
||||
|
||||
if (appId.IsNullOrWhiteSpace() && serverName.IsNullOrWhiteSpace())
|
||||
{
|
||||
return CurrentServerEnvironmentStatus.Unknown;
|
||||
}
|
||||
|
||||
if ((appId.IsNullOrWhiteSpace() == false && appId.Trim().InvariantEquals(HttpRuntime.AppDomainAppId))
|
||||
|| (serverName.IsNullOrWhiteSpace() == false && serverName.Trim().InvariantEquals(NetworkHelper.MachineName)))
|
||||
{
|
||||
//match by appdid or server name!
|
||||
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('/');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,6 @@ using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Sync
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// The client Soap service for making distrubuted cache calls between servers
|
||||
/// </summary>
|
||||
@@ -21,6 +20,33 @@ namespace Umbraco.Core.Sync
|
||||
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/BulkRefresh", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
|
||||
public void BulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password)
|
||||
{
|
||||
this.Invoke("BulkRefresh", new object[] {
|
||||
instructions,
|
||||
appId,
|
||||
login,
|
||||
password});
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
public System.IAsyncResult BeginBulkRefresh(RefreshInstruction[] instructions, string appId, string login, string password, System.AsyncCallback callback, object asyncState)
|
||||
{
|
||||
return this.BeginInvoke("BulkRefresh", new object[] {
|
||||
instructions,
|
||||
appId,
|
||||
login,
|
||||
password}, callback, asyncState);
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
public void EndBulkRefresh(System.IAsyncResult asyncResult)
|
||||
{
|
||||
this.EndInvoke(asyncResult);
|
||||
}
|
||||
|
||||
/// <remarks/>
|
||||
[System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://umbraco.org/webservices/RefreshAll", RequestNamespace = "http://umbraco.org/webservices/", ResponseNamespace = "http://umbraco.org/webservices/", Use = System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle = System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
|
||||
public void RefreshAll(System.Guid uniqueIdentifier, string Login, string Password)
|
||||
|
||||
@@ -82,7 +82,9 @@
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\Microsoft.AspNet.Razor.2.0.30506.0\lib\net40\System.Web.Razor.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.Services" />
|
||||
<Reference Include="System.Web.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<SpecificVersion>False</SpecificVersion>
|
||||
</Reference>
|
||||
<Reference Include="System.Web.WebPages, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
|
||||
<Private>True</Private>
|
||||
<HintPath>..\packages\Microsoft.AspNet.WebPages.2.0.30506.0\lib\net40\System.Web.WebPages.dll</HintPath>
|
||||
@@ -121,8 +123,6 @@
|
||||
<Compile Include="Cache\CacheRefresherBase.cs" />
|
||||
<Compile Include="Cache\CacheRefresherEventArgs.cs" />
|
||||
<Compile Include="Cache\DictionaryCacheProviderBase.cs" />
|
||||
<Compile Include="Cache\DictionaryCacheWrapper.cs" />
|
||||
<Compile Include="Cache\DictionaryItemWrapper.cs" />
|
||||
<Compile Include="Cache\HttpRequestCacheProvider.cs" />
|
||||
<Compile Include="Cache\HttpRuntimeCacheProvider.cs" />
|
||||
<Compile Include="Cache\ICacheProvider.cs" />
|
||||
@@ -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" />
|
||||
@@ -217,6 +218,7 @@
|
||||
<Compile Include="Models\PublishedContent\PublishedContentModel.cs" />
|
||||
<Compile Include="Models\PublishedContent\PublishedContentModelFactoryResolver.cs" />
|
||||
<Compile Include="Models\TemplateNode.cs" />
|
||||
<Compile Include="MonitorLock.cs" />
|
||||
<Compile Include="Packaging\Models\InstallAction.cs" />
|
||||
<Compile Include="Packaging\Models\InstallationSummary.cs" />
|
||||
<Compile Include="Packaging\Models\MetaData.cs" />
|
||||
@@ -232,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" />
|
||||
@@ -770,6 +773,7 @@
|
||||
<Compile Include="Publishing\PublishingStrategy.cs" />
|
||||
<Compile Include="Publishing\PublishStatus.cs" />
|
||||
<Compile Include="Publishing\PublishStatusType.cs" />
|
||||
<Compile Include="Publishing\ScheduledPublisher.cs" />
|
||||
<Compile Include="RenderingEngine.cs" />
|
||||
<Compile Include="Security\AuthenticationExtensions.cs" />
|
||||
<Compile Include="Security\IUsersMembershipProvider.cs" />
|
||||
@@ -830,6 +834,9 @@
|
||||
<Compile Include="Standalone\StandaloneCoreBootManager.cs" />
|
||||
<Compile Include="Strings\ContentBaseExtensions.cs" />
|
||||
<Compile Include="Strings\Diff.cs" />
|
||||
<Compile Include="Sync\CurrentServerEnvironmentStatus.cs" />
|
||||
<Compile Include="Sync\RefreshInstruction.cs" />
|
||||
<Compile Include="Sync\ServerEnvironmentHelper.cs" />
|
||||
<Compile Include="TopologicalSorter.cs" />
|
||||
<Compile Include="Strings\DefaultUrlSegmentProvider.cs" />
|
||||
<Compile Include="Strings\IUrlSegmentProvider.cs" />
|
||||
@@ -924,6 +931,7 @@
|
||||
<Compile Include="Xml\XPathVariable.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="app.config" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a performance tweak to check if this is a .css, .js or .ico, .jpg, .jpeg, .png, .gif file request since
|
||||
/// This is a performance tweak to check if this not an ASP.Net server file
|
||||
/// .Net will pass these requests through to the module when in integrated mode.
|
||||
/// We want to ignore all of these requests immediately.
|
||||
/// </summary>
|
||||
@@ -142,10 +142,10 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
internal static bool IsClientSideRequest(this Uri url)
|
||||
{
|
||||
// fixme - IsClientSideRequest should not use an hard-coded list of extensions
|
||||
// a client-side request is anything that has an extension that is not .aspx?
|
||||
var toIgnore = new[] { ".js", ".css", ".ico", ".png", ".jpg", ".jpeg", ".gif", ".html", ".svg" };
|
||||
return toIgnore.Any(x => Path.GetExtension(url.LocalPath).InvariantEquals(x));
|
||||
var ext = Path.GetExtension(url.LocalPath);
|
||||
if (ext.IsNullOrWhiteSpace()) return false;
|
||||
var toInclude = new[] { ".aspx", ".ashx", ".asmx", ".axd", ".svc" };
|
||||
return toInclude.Any(ext.InvariantEquals) == false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<runtime>
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-4.0.0.0" newVersion="4.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
@@ -121,6 +121,14 @@
|
||||
|
||||
</dependentAssembly>
|
||||
|
||||
<dependentAssembly>
|
||||
|
||||
<assemblyIdentity name="NuGet.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.0.11220.104" newVersion="1.0.11220.104" />
|
||||
|
||||
</dependentAssembly>
|
||||
|
||||
</assemblyBinding>
|
||||
|
||||
</runtime>
|
||||
|
||||
@@ -15,6 +15,8 @@ using log4net.Repository;
|
||||
|
||||
namespace Umbraco.Tests
|
||||
{
|
||||
//Ignore this test, it fails sometimes on the build server - pretty sure it's a threading issue with this test class
|
||||
[Ignore]
|
||||
[TestFixture]
|
||||
public class AsynchronousRollingFileAppenderTests
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Tests.Cache
|
||||
{
|
||||
base.Setup();
|
||||
_ctx = new FakeHttpContextFactory("http://localhost/test");
|
||||
_provider = new HttpRequestCacheProvider(() => _ctx.HttpContext);
|
||||
_provider = new HttpRequestCacheProvider(_ctx.HttpContext);
|
||||
}
|
||||
|
||||
internal override ICacheProvider Provider
|
||||
|
||||
@@ -51,24 +51,20 @@ namespace Umbraco.Tests.Membership
|
||||
{
|
||||
var date = DateTime.Now;
|
||||
|
||||
var m = Mock.Of<MembershipUser>(
|
||||
user => user.UserName == "test username"
|
||||
&& user.Comment == "test comment"
|
||||
&& user.IsApproved == true
|
||||
&& user.IsLockedOut == false
|
||||
&& user.CreationDate == date
|
||||
&& user.Email == "test@email.com"
|
||||
&& user.LastActivityDate == date.AddMinutes(1)
|
||||
&& user.LastLockoutDate == date.AddMinutes(2)
|
||||
&& user.LastLoginDate == date.AddMinutes(3)
|
||||
&& user.LastPasswordChangedDate == date.AddMinutes(4)
|
||||
&& user.PasswordQuestion == "test question");
|
||||
var member = new Member("test name", "test@email.com", "test username", "test password",
|
||||
GetMemberType());
|
||||
member.Comments = "test comment";
|
||||
member.IsApproved = true;
|
||||
member.IsLockedOut = false;
|
||||
member.CreateDate = date;
|
||||
member.LastLoginDate = date.AddMinutes(1);
|
||||
member.LastLockoutDate = date.AddMinutes(2);
|
||||
//NOTE: Last activity date is always the same as last login date since we don't have a place to store that data
|
||||
//member.LastLoginDate = date.AddMinutes(3);
|
||||
member.LastPasswordChangeDate = date.AddMinutes(4);
|
||||
member.PasswordQuestion = "test question";
|
||||
|
||||
|
||||
var mpc = new MemberPublishedContent(
|
||||
new Member("test name", "test@email.com", "test username", "test password",
|
||||
Mock.Of<IMemberType>(type => type.Alias == "Member")),
|
||||
m);
|
||||
var mpc = new MemberPublishedContent(member);
|
||||
|
||||
var d = mpc.AsDynamic();
|
||||
|
||||
@@ -79,7 +75,7 @@ namespace Umbraco.Tests.Membership
|
||||
Assert.AreEqual(false, d.IsLockedOut);
|
||||
Assert.AreEqual(date.AddMinutes(1), d.LastActivityDate);
|
||||
Assert.AreEqual(date.AddMinutes(2), d.LastLockoutDate);
|
||||
Assert.AreEqual(date.AddMinutes(3), d.LastLoginDate);
|
||||
Assert.AreEqual(date.AddMinutes(1), d.LastLoginDate);
|
||||
Assert.AreEqual(date.AddMinutes(4), d.LastPasswordChangedDate);
|
||||
Assert.AreEqual("test name", d.Name);
|
||||
Assert.AreEqual("test question", d.PasswordQuestion);
|
||||
@@ -92,24 +88,20 @@ namespace Umbraco.Tests.Membership
|
||||
{
|
||||
var date = DateTime.Now;
|
||||
|
||||
var m = Mock.Of<MembershipUser>(
|
||||
user => user.UserName == "test username"
|
||||
&& user.Comment == "test comment"
|
||||
&& user.IsApproved == true
|
||||
&& user.IsLockedOut == false
|
||||
&& user.CreationDate == date
|
||||
&& user.Email == "test@email.com"
|
||||
&& user.LastActivityDate == date.AddMinutes(1)
|
||||
&& user.LastLockoutDate == date.AddMinutes(2)
|
||||
&& user.LastLoginDate == date.AddMinutes(3)
|
||||
&& user.LastPasswordChangedDate == date.AddMinutes(4)
|
||||
&& user.PasswordQuestion == "test question");
|
||||
var member = new Member("test name", "test@email.com", "test username", "test password",
|
||||
GetMemberType());
|
||||
member.Comments = "test comment";
|
||||
member.IsApproved = true;
|
||||
member.IsLockedOut = false;
|
||||
member.CreateDate = date;
|
||||
member.LastLoginDate = date.AddMinutes(1);
|
||||
member.LastLockoutDate = date.AddMinutes(2);
|
||||
//NOTE: Last activity date is always the same as last login date since we don't have a place to store that data
|
||||
//member.LastLoginDate = date.AddMinutes(3);
|
||||
member.LastPasswordChangeDate = date.AddMinutes(4);
|
||||
member.PasswordQuestion = "test question";
|
||||
|
||||
|
||||
var mpc = new MemberPublishedContent(
|
||||
new Member("test name", "test@email.com", "test username", "test password",
|
||||
Mock.Of<IMemberType>(type => type.Alias == "Member")) ,
|
||||
m);
|
||||
var mpc = new MemberPublishedContent(member);
|
||||
|
||||
var d = mpc.AsDynamic();
|
||||
|
||||
@@ -120,7 +112,7 @@ namespace Umbraco.Tests.Membership
|
||||
Assert.AreEqual(false, d.isLockedOut);
|
||||
Assert.AreEqual(date.AddMinutes(1), d.lastActivityDate);
|
||||
Assert.AreEqual(date.AddMinutes(2), d.lastLockoutDate);
|
||||
Assert.AreEqual(date.AddMinutes(3), d.lastLoginDate);
|
||||
Assert.AreEqual(date.AddMinutes(1), d.lastLoginDate);
|
||||
Assert.AreEqual(date.AddMinutes(4), d.lastPasswordChangedDate);
|
||||
Assert.AreEqual("test name", d.name);
|
||||
Assert.AreEqual("test question", d.passwordQuestion);
|
||||
@@ -133,25 +125,23 @@ namespace Umbraco.Tests.Membership
|
||||
{
|
||||
var date = DateTime.Now;
|
||||
|
||||
var m = Mock.Of<MembershipUser>(
|
||||
user => user.UserName == "test username"
|
||||
&& user.Comment == "test comment"
|
||||
&& user.IsApproved == true
|
||||
&& user.IsLockedOut == false
|
||||
&& user.CreationDate == date
|
||||
&& user.Email == "test@email.com"
|
||||
&& user.LastActivityDate == date.AddMinutes(1)
|
||||
&& user.LastLockoutDate == date.AddMinutes(2)
|
||||
&& user.LastLoginDate == date.AddMinutes(3)
|
||||
&& user.LastPasswordChangedDate == date.AddMinutes(4)
|
||||
&& user.PasswordQuestion == "test question");
|
||||
|
||||
var memberType = MockedContentTypes.CreateSimpleMemberType("Member", "Member");
|
||||
var member = MockedMember.CreateSimpleMember(memberType, "test name", "test@email.com", "test password", "test username");
|
||||
member.Comments = "test comment";
|
||||
member.IsApproved = true;
|
||||
member.IsLockedOut = false;
|
||||
member.CreateDate = date;
|
||||
member.LastLoginDate = date.AddMinutes(1);
|
||||
member.LastLockoutDate = date.AddMinutes(2);
|
||||
//NOTE: Last activity date is always the same as last login date since we don't have a place to store that data
|
||||
//member.LastLoginDate = date.AddMinutes(3);
|
||||
member.LastPasswordChangeDate = date.AddMinutes(4);
|
||||
member.PasswordQuestion = "test question";
|
||||
|
||||
member.Properties["title"].Value = "Test Value 1";
|
||||
member.Properties["bodyText"].Value = "Test Value 2";
|
||||
member.Properties["author"].Value = "Test Value 3";
|
||||
var mpc = new MemberPublishedContent(member, m);
|
||||
var mpc = new MemberPublishedContent(member);
|
||||
|
||||
var d = mpc.AsDynamic();
|
||||
|
||||
@@ -165,6 +155,21 @@ namespace Umbraco.Tests.Membership
|
||||
|
||||
}
|
||||
|
||||
private IMemberType GetMemberType()
|
||||
{
|
||||
var entity = new MemberType(-1)
|
||||
{
|
||||
Alias = "Member"
|
||||
};
|
||||
|
||||
entity.AddPropertyGroup(Umbraco.Core.Constants.Conventions.Member.StandardPropertiesGroupName);
|
||||
var standardPropertyTypes = Umbraco.Core.Constants.Conventions.Member.GetStandardPropertyTypeStubs();
|
||||
foreach (var standardPropertyType in standardPropertyTypes)
|
||||
{
|
||||
entity.AddPropertyType(standardPropertyType.Value, Umbraco.Core.Constants.Conventions.Member.StandardPropertiesGroupName);
|
||||
}
|
||||
return entity;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user