Compare commits
252 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0074f57970 | |||
| 9b4a6f2761 | |||
| f889b5206c | |||
| 507d7dd4f0 | |||
| 55c5b41693 | |||
| b58d8677b3 | |||
| f30d7fc68c | |||
| d504d8f54e | |||
| b519d68e1a | |||
| deb8d652c5 | |||
| 12dd01dd29 | |||
| c64e3a8908 | |||
| 74e8390bda | |||
| 4f9bbdf7f7 | |||
| 67fc8b6215 | |||
| 3840ac2fed | |||
| 7c64f1aa0d | |||
| 771f8d92f2 | |||
| 82c18955b0 | |||
| 0dc0e68174 | |||
| 5226762528 | |||
| 7889f82a9a | |||
| aaa9c8f194 | |||
| 3d1a44b32c | |||
| 724ec9ab73 | |||
| aa0eaf590c | |||
| 2b82520067 | |||
| 5aed6334c4 | |||
| 91157caf0e | |||
| d27fee8a5b | |||
| 7aef8cbb67 | |||
| a7e28a97d9 | |||
| 308a61895e | |||
| 8e5c57eb60 | |||
| 57945fcec6 | |||
| ade3c4ee6c | |||
| 8fe904d780 | |||
| 1901fbb6ed | |||
| 1de52482e3 | |||
| 1c0d4001ab | |||
| f04d70f2d9 | |||
| 55098deac0 | |||
| 58c5618d30 | |||
| d0cbeb4a0a | |||
| ce0821e2d6 | |||
| b6e9db7363 | |||
| 9f710a957d | |||
| b7578e2b98 | |||
| 97e8973cbd | |||
| 2dc45c3b9f | |||
| 49c2ae3673 | |||
| 5f22c81994 | |||
| f13427f5fb | |||
| 2785eaeda5 | |||
| bb31eb7c35 | |||
| a31cfb86f8 | |||
| 4feb876463 | |||
| e4802b592f | |||
| a8914dcfdb | |||
| 548f9fd01f | |||
| 843796c9a4 | |||
| 7648c76f6f | |||
| 308ed3bba7 | |||
| 7bcb2b11f6 | |||
| e06cda98e9 | |||
| fb02a5b06d | |||
| c060856782 | |||
| 311edd3279 | |||
| 1c89f8c8dd | |||
| 857063ebd8 | |||
| 24063c403a | |||
| 0f930d7342 | |||
| c36000c4dd | |||
| 999de5ae1d | |||
| 00982d4278 | |||
| cda47ed536 | |||
| f4f1f6774d | |||
| 92acc3d331 | |||
| ed4db61288 | |||
| ba6705a706 | |||
| dc980bd78e | |||
| c9f99d7f42 | |||
| 73e219b23c | |||
| 07e0de5824 | |||
| 79a103662f | |||
| 2ba5627e2c | |||
| 2669c68df0 | |||
| 389b29599b | |||
| 912a6a52cc | |||
| 925c343a30 | |||
| 351796fd6b | |||
| e58d9a9535 | |||
| 3fa7c4d7ef | |||
| b6b2a53361 | |||
| 4647ed2551 | |||
| e7e6ad6d0b | |||
| a71ec8dd81 | |||
| f816a63b54 | |||
| 138991d951 | |||
| 92ce064d30 | |||
| ab3bfa4e92 | |||
| fa8f3985b8 | |||
| 175c785705 | |||
| 72aebd3ecf | |||
| c8408760df | |||
| 5834649abc | |||
| 2eda70542f | |||
| a2a17faca1 | |||
| a6c2844d12 | |||
| 802cbb82c4 | |||
| 6f16b98b32 | |||
| 42f5507fe8 | |||
| 72910cb35e | |||
| 7be30ad6ac | |||
| 5a8e37f5b4 | |||
| f87ccc0a53 | |||
| 40ed77103f | |||
| 483762a984 | |||
| a3c7b1fcfe | |||
| 145ca54dcf | |||
| 02785eea42 | |||
| 1aecb66167 | |||
| 96244f9f7b | |||
| e0cc59d212 | |||
| dd2946419b | |||
| d0ea8e83ad | |||
| 3c7d3b2f63 | |||
| a65ad3c72a | |||
| f6a7b25e87 | |||
| 0e06245177 | |||
| 5a81a9eed7 | |||
| 38e7d7e697 | |||
| 820069d5d5 | |||
| 49f3979d18 | |||
| 055e209e0b | |||
| 5579563105 | |||
| cda9d0f9f2 | |||
| 49b7f15e3d | |||
| 8ae700eedf | |||
| 411bbcc192 | |||
| d598fdce54 | |||
| c16ce4ce1f | |||
| 86021c5052 | |||
| de87701c95 | |||
| 5060e709d1 | |||
| c073191f69 | |||
| b4450ca7f2 | |||
| 7004765acf | |||
| 77a91bc017 | |||
| 14b96da588 | |||
| c21837e906 | |||
| 99324035e5 | |||
| 8b9a5000be | |||
| 32892cf5e0 | |||
| c82bddb7dd | |||
| 478ca30948 | |||
| f2fd45576b | |||
| 6bf5f4dbdf | |||
| 32d757b13c | |||
| 882d5ec7f5 | |||
| ce69497a68 | |||
| 1d43cf491b | |||
| d5a687cf3d | |||
| c9082ce511 | |||
| 46c7737c17 | |||
| 06c829eb27 | |||
| e439302b93 | |||
| 6a1d7d2586 | |||
| 27283754c0 | |||
| 5b2212829c | |||
| 26acecb720 | |||
| 4a6844b949 | |||
| ac9b484d3d | |||
| 97a76d5ad4 | |||
| d4fab66411 | |||
| 4cd287616e | |||
| d22afe07db | |||
| fa56f1a59d | |||
| 546209ddb5 | |||
| 1e7c44ead0 | |||
| 90448f1c78 | |||
| 6dc34e1167 | |||
| 6956e01861 | |||
| 7c42cd26c2 | |||
| 13758418ac | |||
| 6bd1a77b96 | |||
| 77b04d74d4 | |||
| 86b2dac231 | |||
| 54bda6f12a | |||
| 2f67a485aa | |||
| f192f241cf | |||
| bf797bccc7 | |||
| 4336f5201d | |||
| 431f91b900 | |||
| ed019a90e2 | |||
| d4acc2c79b | |||
| b05c4711ce | |||
| 74c167ed03 | |||
| 690af8e494 | |||
| 9d06ce53a4 | |||
| d0e8e77fc5 | |||
| 798105998e | |||
| f4994a5133 | |||
| 6bbfb65100 | |||
| cca3b9032f | |||
| 4fc3398e49 | |||
| 2d5254e169 | |||
| c5033710ca | |||
| c824d27728 | |||
| ac54affa57 | |||
| f7d7a43720 | |||
| 6028907a02 | |||
| e14ca4f3d0 | |||
| a97b566065 | |||
| 26a95a8ca1 | |||
| e3308e86f0 | |||
| 1d98507785 | |||
| 096c3aeec6 | |||
| 7ca5c48ae7 | |||
| 4a1a216b3c | |||
| f96d6aed9d | |||
| 192aae71a2 | |||
| bd8af539b4 | |||
| 479d859a75 | |||
| 1050af5376 | |||
| 82dbfb769e | |||
| 55eb996c19 | |||
| ac4de99f30 | |||
| a3150b7d4e | |||
| 1701eb2450 | |||
| 20ac21aef2 | |||
| 4ac20de1dd | |||
| 848eaf5e27 | |||
| a6996922b7 | |||
| 434703f4ec | |||
| b51e571803 | |||
| a275567dba | |||
| 2a95ed3c7a | |||
| 9965aa461a | |||
| 8de8b53039 | |||
| 27eeb383b4 | |||
| 4f5347fa4d | |||
| 994f91e496 | |||
| 4ab7f768fa | |||
| a020a02b26 | |||
| ca777b26de | |||
| 1f636d05d1 | |||
| 7495cd3f2c | |||
| 5f68f26d2f | |||
| 1261f6b159 | |||
| 5446e4b0fc | |||
| 79c2400d27 |
@@ -15,6 +15,8 @@
|
||||
<language>en-US</language>
|
||||
<tags>umbraco</tags>
|
||||
<dependencies>
|
||||
<!--Must be this way because 2.0.5 is actually 1.2.15.0 and 2.0.6 is actually 2.0.6 !-->
|
||||
<dependency id="log4net" version="[2.0.5,2.0.6)" />
|
||||
<dependency id="Microsoft.AspNet.Mvc" version="[5.2.3,6.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.WebApi" version="[5.2.3,6.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.Identity.Owin" version="[2.2.1, 3.0.0)" />
|
||||
@@ -28,52 +30,51 @@
|
||||
<dependency id="MySql.Data" version="[6.9.8, 7.0.0)" />
|
||||
<dependency id="xmlrpcnet" version="[2.5.0, 3.0.0)" />
|
||||
<dependency id="ClientDependency" version="[1.9.2, 2.0.0)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0, 2.0.0)" />
|
||||
<!-- AutoMapper can not be updated due to: https://github.com/AutoMapper/AutoMapper/issues/373#issuecomment-127644405 -->
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.8.0.0, 2.0.0)" />
|
||||
<dependency id="AutoMapper" version="[3.3.1, 4.0.0)" />
|
||||
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
|
||||
<dependency id="Examine" version="[0.1.80, 1.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.5.1, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.8.0, 5.0.0)" />
|
||||
<dependency id="semver" version="[1.1.2, 2.0.0)" />
|
||||
<dependency id="Newtonsoft.Json" version="[9.0.1, 10.0.0)" />
|
||||
<dependency id="Examine" version="[0.1.81, 1.0.0)" />
|
||||
<dependency id="ImageProcessor" version="[2.5.2, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web" version="[4.8.2, 5.0.0)" />
|
||||
<dependency id="semver" version="[2.0.4, 3.0.0)" />
|
||||
<dependency id="UrlRewritingNet" version="[2.0.7, 3.0.0)" />
|
||||
<!-- Markdown can not be updated due to: https://github.com/hey-red/markdownsharp/issues/71#issuecomment-233585487 -->
|
||||
<dependency id="Markdown" version="[1.14.4, 2.0.0)" />
|
||||
<dependency id="Markdown" version="[1.14.7, 2.0.0)" />
|
||||
<dependency id="System.Threading.Tasks.Dataflow" version="[4.6.0, 5.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
<file src="..\_BuildOutput\WebApp\bin\businesslogic.dll" target="lib\businesslogic.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\businesslogic.xml" target="lib\businesslogic.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\cms.dll" target="lib\cms.dll" />
|
||||
<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\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" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Microsoft.ApplicationBlocks.Data.dll" target="lib\Microsoft.ApplicationBlocks.Data.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.dll" target="lib\SQLCE4Umbraco.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.xml" target="lib\SQLCE4Umbraco.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.dll" target="lib\System.Data.SqlServerCe.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.Entity.dll" target="lib\System.Data.SqlServerCe.Entity.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\TidyNet.dll" target="lib\TidyNet.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.dll" target="lib\Umbraco.Core.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.xml" target="lib\Umbraco.Core.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.dll" target="lib\umbraco.DataLayer.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.xml" target="lib\umbraco.DataLayer.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.dll" target="lib\umbraco.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.xml" target="lib\umbraco.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.dll" target="lib\umbraco.editorControls.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.xml" target="lib\umbraco.editorControls.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.dll" target="lib\umbraco.MacroEngines.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.xml" target="lib\umbraco.MacroEngines.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.dll" target="lib\umbraco.providers.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.xml" target="lib\umbraco.providers.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.dll" target="lib\Umbraco.Web.UI.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.xml" target="lib\Umbraco.Web.UI.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.dll" target="lib\UmbracoExamine.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.xml" target="lib\UmbracoExamine.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\businesslogic.dll" target="lib\net45\businesslogic.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\businesslogic.xml" target="lib\net45\businesslogic.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\cms.dll" target="lib\net45\cms.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\cms.xml" target="lib\net45\cms.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\controls.dll" target="lib\net45\controls.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\controls.xml" target="lib\net45\controls.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\interfaces.dll" target="lib\net45\interfaces.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\interfaces.xml" target="lib\net45\interfaces.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\log4net.dll" target="lib\net45\log4net.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Microsoft.ApplicationBlocks.Data.dll" target="lib\net45\Microsoft.ApplicationBlocks.Data.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.dll" target="lib\net45\SQLCE4Umbraco.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.xml" target="lib\net45\SQLCE4Umbraco.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.dll" target="lib\net45\System.Data.SqlServerCe.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.Entity.dll" target="lib\net45\System.Data.SqlServerCe.Entity.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\TidyNet.dll" target="lib\net45\TidyNet.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.dll" target="lib\net45\Umbraco.Core.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.xml" target="lib\net45\Umbraco.Core.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.dll" target="lib\net45\umbraco.DataLayer.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.xml" target="lib\net45\umbraco.DataLayer.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.dll" target="lib\net45\umbraco.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.xml" target="lib\net45\umbraco.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.dll" target="lib\net45\umbraco.editorControls.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.xml" target="lib\net45\umbraco.editorControls.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.dll" target="lib\net45\umbraco.MacroEngines.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.xml" target="lib\net45\umbraco.MacroEngines.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.dll" target="lib\net45\umbraco.providers.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.xml" target="lib\net45\umbraco.providers.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.dll" target="lib\net45\Umbraco.Web.UI.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.xml" target="lib\net45\Umbraco.Web.UI.xml" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.dll" target="lib\net45\UmbracoExamine.dll" />
|
||||
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.xml" target="lib\net45\UmbracoExamine.xml" />
|
||||
<file src="tools\install.core.ps1" target="tools\install.ps1" />
|
||||
|
||||
<!-- Added to be able to produce a symbols package -->
|
||||
@@ -104,4 +105,4 @@
|
||||
<file src="..\_BuildOutput\bin\UmbracoExamine.pdb" target="lib" />
|
||||
<file src="..\..\src\UmbracoExamine\**\*.cs" exclude="..\..\src\**\TemporaryGeneratedFile*.cs" target="src\UmbracoExamine" />
|
||||
</files>
|
||||
</package>
|
||||
</package>
|
||||
|
||||
@@ -16,9 +16,10 @@
|
||||
<tags>umbraco</tags>
|
||||
<dependencies>
|
||||
<dependency id="UmbracoCms.Core" version="[$version$]" />
|
||||
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
|
||||
<dependency id="Newtonsoft.Json" version="[9.0.1, 10.0.0)" />
|
||||
<dependency id="Umbraco.ModelsBuilder" version="[3.0.5, 4.0.0)" />
|
||||
<dependency id="Microsoft.AspNet.SignalR.Core" version="[2.2.1, 3.0.0)" />
|
||||
<dependency id="ImageProcessor.Web.Config" version="[2.3.0, 3.0.0)" />
|
||||
</dependencies>
|
||||
</metadata>
|
||||
<files>
|
||||
@@ -45,6 +46,7 @@
|
||||
<file src="tools\umbracoSettings.config.install.xdt" target="Content\config\umbracoSettings.config.install.xdt" />
|
||||
<file src="tools\Views.Web.config.install.xdt" target="Views\Web.config.install.xdt" />
|
||||
<file src="tools\processing.config.install.xdt" target="Content\Config\imageprocessor\processing.config.install.xdt" />
|
||||
<file src="tools\cache.config.install.xdt" target="Content\Config\imageprocessor\cache.config.install.xdt" />
|
||||
<file src="build\**" target="build" />
|
||||
</files>
|
||||
</package>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0"?>
|
||||
<caching xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
|
||||
<caches>
|
||||
<cache name="DiskCache" trimCache="false" xdt:Transform="SetAttributes(trimCache)" xdt:Locator="Match(name)" />
|
||||
</caches>
|
||||
</caching>
|
||||
@@ -1,3 +1,3 @@
|
||||
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
|
||||
7.6.0
|
||||
alpha056
|
||||
alpha063
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
@@ -10,7 +10,7 @@
|
||||
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||
<RootNamespace>SQLCE4Umbraco</RootNamespace>
|
||||
<AssemblyName>SQLCE4Umbraco</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
|
||||
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<SccProjectName>
|
||||
</SccProjectName>
|
||||
|
||||
@@ -240,7 +240,7 @@ namespace SqlCE4Umbraco
|
||||
{
|
||||
var cmd = trx == null ? new SqlCeCommand(commandText, conn) : new SqlCeCommand(commandText, conn, trx);
|
||||
AttachParameters(cmd, commandParameters);
|
||||
return cmd.ExecuteReader(CommandBehavior.CloseConnection);
|
||||
return cmd.ExecuteReader();
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" culture="neutral" />
|
||||
@@ -32,4 +32,4 @@
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
</configuration>
|
||||
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.2" /></startup></configuration>
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.6.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.6.0-alpha056")]
|
||||
[assembly: AssemblyInformationalVersion("7.6.0-alpha063")]
|
||||
@@ -7,6 +7,7 @@ using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
using Umbraco.Core.Profiling;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
@@ -162,6 +163,9 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
public static ApplicationContext Current { get; internal set; }
|
||||
|
||||
// fixme
|
||||
internal IScopeProvider ScopeProvider { get { return _databaseContext == null ? null : _databaseContext.ScopeProvider; } }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the application wide cache accessor
|
||||
/// </summary>
|
||||
@@ -418,10 +422,17 @@ namespace Umbraco.Core
|
||||
this.ApplicationCache = null;
|
||||
if (_databaseContext != null) //need to check the internal field here
|
||||
{
|
||||
if (_databaseContext.ScopeProvider.AmbientScope != null)
|
||||
{
|
||||
var scope = _databaseContext.ScopeProvider.AmbientScope;
|
||||
scope.Dispose();
|
||||
}
|
||||
/*
|
||||
if (DatabaseContext.IsDatabaseConfigured && DatabaseContext.Database != null)
|
||||
{
|
||||
DatabaseContext.Database.Dispose();
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
this.DatabaseContext = null;
|
||||
this.Services = null;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages any assembly binding redirects that cannot be done via config
|
||||
/// </summary>
|
||||
internal class BindingRedirects
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
// this only gets called when an assembly can't be resolved
|
||||
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
|
||||
}
|
||||
|
||||
private static readonly Regex Log4NetAssemblyPattern = new Regex("log4net, Version=([\\d\\.]+?), Culture=neutral, PublicKeyToken=null", RegexOptions.Compiled);
|
||||
private const string Log4NetReplacement = "log4net, Version=1.2.15.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a";
|
||||
|
||||
/// <summary>
|
||||
/// This is used to do an assembly binding redirect via code - normally required due to signature changes in assemblies
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <returns></returns>
|
||||
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
|
||||
{
|
||||
//log4net:
|
||||
// Use regex to match and replace
|
||||
if (Log4NetAssemblyPattern.IsMatch(args.Name) && args.Name != Log4NetReplacement)
|
||||
{
|
||||
return Assembly.Load(Log4NetAssemblyPattern.Replace(args.Name, Log4NetReplacement));
|
||||
}
|
||||
|
||||
//AutoMapper:
|
||||
// ensure the assembly is indeed AutoMapper and that the PublicKeyToken is null before trying to Load again
|
||||
// do NOT just replace this with 'return Assembly', as it will cause an infinite loop -> stackoverflow
|
||||
if (args.Name.StartsWith("AutoMapper") && args.Name.EndsWith("PublicKeyToken=null"))
|
||||
return Assembly.Load(args.Name.Replace(", PublicKeyToken=null", ", PublicKeyToken=be96cd2c38ef1005"));
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// The default cache policy for retrieving a single entity
|
||||
/// Represents the default cache policy.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TId"></typeparam>
|
||||
/// <typeparam name="TEntity">The type of the entity.</typeparam>
|
||||
/// <typeparam name="TId">The type of the identifier.</typeparam>
|
||||
/// <remarks>
|
||||
/// This cache policy uses sliding expiration and caches instances for 5 minutes. However if allow zero count is true, then we use the
|
||||
/// default policy with no expiry.
|
||||
/// <para>The default cache policy caches entities with a 5 minutes sliding expiration.</para>
|
||||
/// <para>Each entity is cached individually.</para>
|
||||
/// <para>If options.GetAllCacheAllowZeroCount then a 'zero-count' array is cached when GetAll finds nothing.</para>
|
||||
/// <para>If options.GetAllCacheValidateCount then we check against the db when getting many entities.</para>
|
||||
/// </remarks>
|
||||
internal class DefaultRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyBase<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
private static readonly TEntity[] EmptyEntities = new TEntity[0]; // const
|
||||
private readonly RepositoryCachePolicyOptions _options;
|
||||
|
||||
public DefaultRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options)
|
||||
@@ -27,242 +30,223 @@ namespace Umbraco.Core.Cache
|
||||
_options = options;
|
||||
}
|
||||
|
||||
protected string GetCacheIdKey(object id)
|
||||
public override IRepositoryCachePolicy<TEntity, TId> Scoped(IRuntimeCacheProvider runtimeCache, IScope scope)
|
||||
{
|
||||
if (id == null) throw new ArgumentNullException("id");
|
||||
|
||||
return string.Format("{0}{1}", GetCacheTypeKey(), id);
|
||||
return new ScopedRepositoryCachePolicy<TEntity, TId>(this, runtimeCache, scope);
|
||||
}
|
||||
|
||||
protected string GetCacheTypeKey()
|
||||
protected string GetEntityCacheKey(object id)
|
||||
{
|
||||
if (id == null) throw new ArgumentNullException("id");
|
||||
return GetEntityTypeCacheKey() + id;
|
||||
}
|
||||
|
||||
protected string GetEntityTypeCacheKey()
|
||||
{
|
||||
return string.Format("uRepo_{0}_", typeof(TEntity).Name);
|
||||
}
|
||||
|
||||
public override void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod)
|
||||
protected virtual void InsertEntity(string cacheKey, TEntity entity)
|
||||
{
|
||||
Cache.InsertCacheItem(cacheKey, () => entity, TimeSpan.FromMinutes(5), true);
|
||||
}
|
||||
|
||||
protected virtual void InsertEntities(TId[] ids, TEntity[] entities)
|
||||
{
|
||||
if (ids.Length == 0 && entities.Length == 0 && _options.GetAllCacheAllowZeroCount)
|
||||
{
|
||||
// getting all of them, and finding nothing.
|
||||
// if we can cache a zero count, cache an empty array,
|
||||
// for as long as the cache is not cleared (no expiration)
|
||||
Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => EmptyEntities);
|
||||
}
|
||||
else
|
||||
{
|
||||
// individually cache each item
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
var capture = entity;
|
||||
Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => capture, TimeSpan.FromMinutes(5), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Create(TEntity entity, Action<TEntity> persistNew)
|
||||
{
|
||||
if (entity == null) throw new ArgumentNullException("entity");
|
||||
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
|
||||
|
||||
try
|
||||
{
|
||||
persistMethod(entity);
|
||||
persistNew(entity);
|
||||
|
||||
//set the disposal action
|
||||
SetCacheAction(() =>
|
||||
// just to be safe, we cannot cache an item without an identity
|
||||
if (entity.HasIdentity)
|
||||
{
|
||||
//just to be safe, we cannot cache an item without an identity
|
||||
if (entity.HasIdentity)
|
||||
{
|
||||
Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => entity,
|
||||
timeout: TimeSpan.FromMinutes(5),
|
||||
isSliding: true);
|
||||
}
|
||||
|
||||
//If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.ClearCacheItem(GetCacheTypeKey());
|
||||
});
|
||||
|
||||
Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true);
|
||||
}
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.ClearCacheItem(GetEntityTypeCacheKey());
|
||||
}
|
||||
catch
|
||||
{
|
||||
//set the disposal action
|
||||
SetCacheAction(() =>
|
||||
{
|
||||
//if an exception is thrown we need to remove the entry from cache, this is ONLY a work around because of the way
|
||||
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
|
||||
Cache.ClearCacheItem(GetCacheIdKey(entity.Id));
|
||||
// if an exception is thrown we need to remove the entry from cache,
|
||||
// this is ONLY a work around because of the way
|
||||
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
|
||||
Cache.ClearCacheItem(GetEntityCacheKey(entity.Id));
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.ClearCacheItem(GetEntityTypeCacheKey());
|
||||
|
||||
//If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.ClearCacheItem(GetCacheTypeKey());
|
||||
});
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Remove(TEntity entity, Action<TEntity> persistMethod)
|
||||
/// <inheritdoc />
|
||||
public override void Update(TEntity entity, Action<TEntity> persistUpdated)
|
||||
{
|
||||
if (entity == null) throw new ArgumentNullException("entity");
|
||||
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
|
||||
|
||||
try
|
||||
{
|
||||
persistMethod(entity);
|
||||
}
|
||||
finally
|
||||
{
|
||||
//set the disposal action
|
||||
var cacheKey = GetCacheIdKey(entity.Id);
|
||||
SetCacheAction(() =>
|
||||
persistUpdated(entity);
|
||||
|
||||
// just to be safe, we cannot cache an item without an identity
|
||||
if (entity.HasIdentity)
|
||||
{
|
||||
Cache.ClearCacheItem(cacheKey);
|
||||
//If there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.ClearCacheItem(GetCacheTypeKey());
|
||||
});
|
||||
Cache.InsertCacheItem(GetEntityCacheKey(entity.Id), () => entity, TimeSpan.FromMinutes(5), true);
|
||||
}
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.ClearCacheItem(GetEntityTypeCacheKey());
|
||||
}
|
||||
catch
|
||||
{
|
||||
// if an exception is thrown we need to remove the entry from cache,
|
||||
// this is ONLY a work around because of the way
|
||||
// that we cache entities: http://issues.umbraco.org/issue/U4-4259
|
||||
Cache.ClearCacheItem(GetEntityCacheKey(entity.Id));
|
||||
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.ClearCacheItem(GetEntityTypeCacheKey());
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public override TEntity Get(TId id, Func<TId, TEntity> getFromRepo)
|
||||
/// <inheritdoc />
|
||||
public override void Delete(TEntity entity, Action<TEntity> persistDeleted)
|
||||
{
|
||||
if (getFromRepo == null) throw new ArgumentNullException("getFromRepo");
|
||||
if (entity == null) throw new ArgumentNullException("entity");
|
||||
|
||||
var cacheKey = GetCacheIdKey(id);
|
||||
try
|
||||
{
|
||||
persistDeleted(entity);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// whatever happens, clear the cache
|
||||
var cacheKey = GetEntityCacheKey(entity.Id);
|
||||
Cache.ClearCacheItem(cacheKey);
|
||||
// if there's a GetAllCacheAllowZeroCount cache, ensure it is cleared
|
||||
Cache.ClearCacheItem(GetEntityTypeCacheKey());
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TEntity Get(TId id, Func<TId, TEntity> performGet, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
var cacheKey = GetEntityCacheKey(id);
|
||||
var fromCache = Cache.GetCacheItem<TEntity>(cacheKey);
|
||||
|
||||
// if found in cache then return else fetch and cache
|
||||
if (fromCache != null)
|
||||
return fromCache;
|
||||
|
||||
var entity = getFromRepo(id);
|
||||
var entity = performGet(id);
|
||||
|
||||
//set the disposal action
|
||||
SetCacheAction(cacheKey, entity);
|
||||
if (entity != null && entity.HasIdentity)
|
||||
InsertEntity(cacheKey, entity);
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
public override TEntity Get(TId id)
|
||||
/// <inheritdoc />
|
||||
public override TEntity GetCached(TId id)
|
||||
{
|
||||
var cacheKey = GetCacheIdKey(id);
|
||||
var cacheKey = GetEntityCacheKey(id);
|
||||
return Cache.GetCacheItem<TEntity>(cacheKey);
|
||||
}
|
||||
|
||||
public override bool Exists(TId id, Func<TId, bool> getFromRepo)
|
||||
/// <inheritdoc />
|
||||
public override bool Exists(TId id, Func<TId, bool> performExists, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
if (getFromRepo == null) throw new ArgumentNullException("getFromRepo");
|
||||
|
||||
var cacheKey = GetCacheIdKey(id);
|
||||
// if found in cache the return else check
|
||||
var cacheKey = GetEntityCacheKey(id);
|
||||
var fromCache = Cache.GetCacheItem<TEntity>(cacheKey);
|
||||
return fromCache != null || getFromRepo(id);
|
||||
return fromCache != null || performExists(id);
|
||||
}
|
||||
|
||||
public override TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo)
|
||||
/// <inheritdoc />
|
||||
public override TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
if (getFromRepo == null) throw new ArgumentNullException("getFromRepo");
|
||||
|
||||
if (ids.Any())
|
||||
if (ids.Length > 0)
|
||||
{
|
||||
var entities = ids.Select(Get).ToArray();
|
||||
if (ids.Length.Equals(entities.Length) && entities.Any(x => x == null) == false)
|
||||
return entities;
|
||||
// try to get each entity from the cache
|
||||
// if we can find all of them, return
|
||||
var entities = ids.Select(GetCached).WhereNotNull().ToArray();
|
||||
if (ids.Length.Equals(entities.Length))
|
||||
return entities; // no need for null checks, we are not caching nulls
|
||||
}
|
||||
else
|
||||
{
|
||||
var allEntities = GetAllFromCache();
|
||||
if (allEntities.Any())
|
||||
// get everything we have
|
||||
var entities = Cache.GetCacheItemsByKeySearch<TEntity>(GetEntityTypeCacheKey())
|
||||
.ToArray(); // no need for null checks, we are not caching nulls
|
||||
|
||||
if (entities.Length > 0)
|
||||
{
|
||||
// if some of them were in the cache...
|
||||
if (_options.GetAllCacheValidateCount)
|
||||
{
|
||||
//Get count of all entities of current type (TEntity) to ensure cached result is correct
|
||||
// need to validate the count, get the actual count and return if ok
|
||||
var totalCount = _options.PerformCount();
|
||||
if (allEntities.Length == totalCount)
|
||||
return allEntities;
|
||||
if (entities.Length == totalCount)
|
||||
return entities;
|
||||
}
|
||||
else
|
||||
{
|
||||
return allEntities;
|
||||
// no need to validate, just return what we have and assume it's all there is
|
||||
return entities;
|
||||
}
|
||||
}
|
||||
else if (_options.GetAllCacheAllowZeroCount)
|
||||
{
|
||||
//if the repository allows caching a zero count, then check the zero count cache
|
||||
if (HasZeroCountCache())
|
||||
{
|
||||
//there is a zero count cache so return an empty list
|
||||
return new TEntity[] {};
|
||||
}
|
||||
// if none of them were in the cache
|
||||
// and we allow zero count - check for the special (empty) entry
|
||||
var empty = Cache.GetCacheItem<TEntity[]>(GetEntityTypeCacheKey());
|
||||
if (empty != null) return empty;
|
||||
}
|
||||
}
|
||||
|
||||
//we need to do the lookup from the repo
|
||||
var entityCollection = getFromRepo(ids)
|
||||
//ensure we don't include any null refs in the returned collection!
|
||||
.WhereNotNull()
|
||||
// cache failed, get from repo and cache
|
||||
var repoEntities = performGetAll(ids)
|
||||
.WhereNotNull() // exclude nulls!
|
||||
.Where(x => x.HasIdentity) // be safe, though would be weird...
|
||||
.ToArray();
|
||||
|
||||
//set the disposal action
|
||||
SetCacheAction(ids, entityCollection);
|
||||
// note: if empty & allow zero count, will cache a special (empty) entry
|
||||
InsertEntities(ids, repoEntities);
|
||||
|
||||
return entityCollection;
|
||||
return repoEntities;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the zero count cache, must return null if it doesn't exist
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected bool HasZeroCountCache()
|
||||
/// <inheritdoc />
|
||||
public override void ClearAll()
|
||||
{
|
||||
var zeroCount = Cache.GetCacheItem<TEntity[]>(GetCacheTypeKey());
|
||||
return (zeroCount != null && zeroCount.Any() == false);
|
||||
// fixme the cache should NOT contain anything else so we can clean all, can't we?
|
||||
Cache.ClearAllCache();
|
||||
//Cache.ClearCacheByKeySearch(GetEntityTypeCacheKey());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the lookup for all entities of this type from the cache
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected TEntity[] GetAllFromCache()
|
||||
{
|
||||
var allEntities = Cache.GetCacheItemsByKeySearch<TEntity>(GetCacheTypeKey())
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
return allEntities.Any() ? allEntities : new TEntity[] {};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the action to execute on disposal for a single entity
|
||||
/// </summary>
|
||||
/// <param name="cacheKey"></param>
|
||||
/// <param name="entity"></param>
|
||||
protected virtual void SetCacheAction(string cacheKey, TEntity entity)
|
||||
{
|
||||
if (entity == null) return;
|
||||
|
||||
SetCacheAction(() =>
|
||||
{
|
||||
//just to be safe, we cannot cache an item without an identity
|
||||
if (entity.HasIdentity)
|
||||
{
|
||||
Cache.InsertCacheItem(cacheKey, () => entity,
|
||||
timeout: TimeSpan.FromMinutes(5),
|
||||
isSliding: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the action to execute on disposal for an entity collection
|
||||
/// </summary>
|
||||
/// <param name="ids"></param>
|
||||
/// <param name="entityCollection"></param>
|
||||
protected virtual void SetCacheAction(TId[] ids, TEntity[] entityCollection)
|
||||
{
|
||||
SetCacheAction(() =>
|
||||
{
|
||||
//This option cannot execute if we are looking up specific Ids
|
||||
if (ids.Any() == false && entityCollection.Length == 0 && _options.GetAllCacheAllowZeroCount)
|
||||
{
|
||||
//there was nothing returned but we want to cache a zero count result so add an TEntity[] to the cache
|
||||
// to signify that there is a zero count cache
|
||||
//NOTE: Don't set expiry/sliding for a zero count
|
||||
Cache.InsertCacheItem(GetCacheTypeKey(), () => new TEntity[] {});
|
||||
}
|
||||
else
|
||||
{
|
||||
//This is the default behavior, we'll individually cache each item so that if/when these items are resolved
|
||||
// by id, they are returned from the already existing cache.
|
||||
foreach (var entity in entityCollection.WhereNotNull())
|
||||
{
|
||||
var localCopy = entity;
|
||||
//just to be safe, we cannot cache an item without an identity
|
||||
if (localCopy.HasIdentity)
|
||||
{
|
||||
Cache.InsertCacheItem(GetCacheIdKey(entity.Id), () => localCopy,
|
||||
timeout: TimeSpan.FromMinutes(5),
|
||||
isSliding: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates cache policies
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TId"></typeparam>
|
||||
internal class DefaultRepositoryCachePolicyFactory<TEntity, TId> : IRepositoryCachePolicyFactory<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
private readonly IRuntimeCacheProvider _runtimeCache;
|
||||
private readonly RepositoryCachePolicyOptions _options;
|
||||
|
||||
public DefaultRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options)
|
||||
{
|
||||
_runtimeCache = runtimeCache;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public virtual IRepositoryCachePolicy<TEntity, TId> CreatePolicy()
|
||||
{
|
||||
return new DefaultRepositoryCachePolicy<TEntity, TId>(_runtimeCache, _options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,227 +3,178 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// A caching policy that caches an entire dataset as a single collection
|
||||
/// Represents a caching policy that caches the entire entities set as a single collection.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TId"></typeparam>
|
||||
/// <typeparam name="TEntity">The type of the entity.</typeparam>
|
||||
/// <typeparam name="TId">The type of the identifier.</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>Caches the entire set of entities as a single collection.</para>
|
||||
/// <para>Used by Content-, Media- and MemberTypeRepository, DataTypeRepository, DomainRepository,
|
||||
/// LanguageRepository, PublicAccessRepository, TemplateRepository... things that make sense to
|
||||
/// keep as a whole in memory.</para>
|
||||
/// </remarks>
|
||||
internal class FullDataSetRepositoryCachePolicy<TEntity, TId> : RepositoryCachePolicyBase<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
private readonly Func<TEntity, TId> _getEntityId;
|
||||
private readonly Func<IEnumerable<TEntity>> _getAllFromRepo;
|
||||
private readonly Func<TEntity, TId> _entityGetId;
|
||||
private readonly bool _expires;
|
||||
|
||||
public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func<TEntity, TId> getEntityId, Func<IEnumerable<TEntity>> getAllFromRepo, bool expires)
|
||||
public FullDataSetRepositoryCachePolicy(IRuntimeCacheProvider cache, Func<TEntity, TId> entityGetId, bool expires)
|
||||
: base(cache)
|
||||
{
|
||||
_getEntityId = getEntityId;
|
||||
_getAllFromRepo = getAllFromRepo;
|
||||
_entityGetId = entityGetId;
|
||||
_expires = expires;
|
||||
}
|
||||
|
||||
private bool? _hasZeroCountCache;
|
||||
public override IRepositoryCachePolicy<TEntity, TId> Scoped(IRuntimeCacheProvider runtimeCache, IScope scope)
|
||||
{
|
||||
return new ScopedRepositoryCachePolicy<TEntity, TId>(this, runtimeCache, scope);
|
||||
}
|
||||
|
||||
protected static readonly TId[] EmptyIds = new TId[0]; // const
|
||||
|
||||
protected string GetCacheTypeKey()
|
||||
protected string GetEntityTypeCacheKey()
|
||||
{
|
||||
return string.Format("uRepo_{0}_", typeof(TEntity).Name);
|
||||
}
|
||||
|
||||
public override void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod)
|
||||
protected void InsertEntities(TEntity[] entities)
|
||||
{
|
||||
if (entity == null) throw new ArgumentNullException("entity");
|
||||
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
|
||||
// cache is expected to be a deep-cloning cache ie it deep-clones whatever is
|
||||
// IDeepCloneable when it goes in, and out. it also resets dirty properties,
|
||||
// making sure that no 'dirty' entity is cached.
|
||||
//
|
||||
// this policy is caching the entire list of entities. to ensure that entities
|
||||
// are properly deep-clones when cached, it uses a DeepCloneableList. however,
|
||||
// we don't want to deep-clone *each* entity in the list when fetching it from
|
||||
// cache as that would not be efficient for Get(id). so the DeepCloneableList is
|
||||
// set to ListCloneBehavior.CloneOnce ie it will clone *once* when inserting,
|
||||
// and then will *not* clone when retrieving.
|
||||
|
||||
try
|
||||
if (_expires)
|
||||
{
|
||||
persistMethod(entity);
|
||||
|
||||
//set the disposal action
|
||||
SetCacheAction(() =>
|
||||
{
|
||||
//Clear all
|
||||
Cache.ClearCacheItem(GetCacheTypeKey());
|
||||
});
|
||||
Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList<TEntity>(entities), TimeSpan.FromMinutes(5), true);
|
||||
}
|
||||
catch
|
||||
else
|
||||
{
|
||||
//set the disposal action
|
||||
SetCacheAction(() =>
|
||||
{
|
||||
//Clear all
|
||||
Cache.ClearCacheItem(GetCacheTypeKey());
|
||||
});
|
||||
throw;
|
||||
Cache.InsertCacheItem(GetEntityTypeCacheKey(), () => new DeepCloneableList<TEntity>(entities));
|
||||
}
|
||||
}
|
||||
|
||||
public override void Remove(TEntity entity, Action<TEntity> persistMethod)
|
||||
/// <inheritdoc />
|
||||
public override void Create(TEntity entity, Action<TEntity> persistNew)
|
||||
{
|
||||
if (entity == null) throw new ArgumentNullException("entity");
|
||||
if (persistMethod == null) throw new ArgumentNullException("persistMethod");
|
||||
|
||||
try
|
||||
{
|
||||
persistMethod(entity);
|
||||
persistNew(entity);
|
||||
}
|
||||
finally
|
||||
{
|
||||
//set the disposal action
|
||||
SetCacheAction(() =>
|
||||
{
|
||||
//Clear all
|
||||
Cache.ClearCacheItem(GetCacheTypeKey());
|
||||
});
|
||||
ClearAll();
|
||||
}
|
||||
}
|
||||
|
||||
public override TEntity Get(TId id, Func<TId, TEntity> getFromRepo)
|
||||
/// <inheritdoc />
|
||||
public override void Update(TEntity entity, Action<TEntity> persistUpdated)
|
||||
{
|
||||
//Force get all with cache
|
||||
var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
|
||||
if (entity == null) throw new ArgumentNullException("entity");
|
||||
|
||||
//we don't have anything in cache (this should never happen), just return from the repo
|
||||
if (found == null) return getFromRepo(id);
|
||||
var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id));
|
||||
if (entity == null) return null;
|
||||
|
||||
//We must ensure to deep clone each one out manually since the deep clone list only clones one way
|
||||
return (TEntity)entity.DeepClone();
|
||||
}
|
||||
|
||||
public override TEntity Get(TId id)
|
||||
{
|
||||
//Force get all with cache
|
||||
var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
|
||||
|
||||
//we don't have anything in cache (this should never happen), just return null
|
||||
if (found == null) return null;
|
||||
var entity = found.FirstOrDefault(x => _getEntityId(x).Equals(id));
|
||||
if (entity == null) return null;
|
||||
|
||||
//We must ensure to deep clone each one out manually since the deep clone list only clones one way
|
||||
return (TEntity)entity.DeepClone();
|
||||
}
|
||||
|
||||
public override bool Exists(TId id, Func<TId, bool> getFromRepo)
|
||||
{
|
||||
//Force get all with cache
|
||||
var found = GetAll(new TId[] { }, ids => _getAllFromRepo().WhereNotNull());
|
||||
|
||||
//we don't have anything in cache (this should never happen), just return from the repo
|
||||
return found == null
|
||||
? getFromRepo(id)
|
||||
: found.Any(x => _getEntityId(x).Equals(id));
|
||||
}
|
||||
|
||||
public override TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo)
|
||||
{
|
||||
//process getting all including setting the cache callback
|
||||
var result = PerformGetAll(getFromRepo);
|
||||
|
||||
//now that the base result has been calculated, they will all be cached.
|
||||
// Now we can just filter by ids if they have been supplied
|
||||
|
||||
return (ids.Any()
|
||||
? result.Where(x => ids.Contains(_getEntityId(x))).ToArray()
|
||||
: result)
|
||||
//We must ensure to deep clone each one out manually since the deep clone list only clones one way
|
||||
.Select(x => (TEntity)x.DeepClone())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private TEntity[] PerformGetAll(Func<TId[], IEnumerable<TEntity>> getFromRepo)
|
||||
{
|
||||
var allEntities = GetAllFromCache();
|
||||
if (allEntities.Any())
|
||||
try
|
||||
{
|
||||
return allEntities;
|
||||
persistUpdated(entity);
|
||||
}
|
||||
|
||||
//check the zero count cache
|
||||
if (HasZeroCountCache())
|
||||
finally
|
||||
{
|
||||
//there is a zero count cache so return an empty list
|
||||
return new TEntity[] { };
|
||||
ClearAll();
|
||||
}
|
||||
|
||||
//we need to do the lookup from the repo
|
||||
var entityCollection = getFromRepo(new TId[] { })
|
||||
//ensure we don't include any null refs in the returned collection!
|
||||
.WhereNotNull()
|
||||
.ToArray();
|
||||
|
||||
//set the disposal action
|
||||
SetCacheAction(entityCollection);
|
||||
|
||||
return entityCollection;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// For this type of caching policy, we don't cache individual items
|
||||
/// </summary>
|
||||
/// <param name="cacheKey"></param>
|
||||
/// <param name="entity"></param>
|
||||
protected void SetCacheAction(string cacheKey, TEntity entity)
|
||||
/// <inheritdoc />
|
||||
public override void Delete(TEntity entity, Action<TEntity> persistDeleted)
|
||||
{
|
||||
//No-op
|
||||
}
|
||||
if (entity == null) throw new ArgumentNullException("entity");
|
||||
|
||||
/// <summary>
|
||||
/// Sets the action to execute on disposal for an entity collection
|
||||
/// </summary>
|
||||
/// <param name="entityCollection"></param>
|
||||
protected void SetCacheAction(TEntity[] entityCollection)
|
||||
{
|
||||
//set the disposal action
|
||||
SetCacheAction(() =>
|
||||
try
|
||||
{
|
||||
//We want to cache the result as a single collection
|
||||
|
||||
if (_expires)
|
||||
{
|
||||
Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList<TEntity>(entityCollection),
|
||||
timeout: TimeSpan.FromMinutes(5),
|
||||
isSliding: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cache.InsertCacheItem(GetCacheTypeKey(), () => new DeepCloneableList<TEntity>(entityCollection));
|
||||
}
|
||||
});
|
||||
persistDeleted(entity);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ClearAll();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up the zero count cache, must return null if it doesn't exist
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected bool HasZeroCountCache()
|
||||
/// <inheritdoc />
|
||||
public override TEntity Get(TId id, Func<TId, TEntity> performGet, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
if (_hasZeroCountCache.HasValue)
|
||||
return _hasZeroCountCache.Value;
|
||||
// get all from the cache, then look for the entity
|
||||
var all = GetAllCached(performGetAll);
|
||||
var entity = all.FirstOrDefault(x => _entityGetId(x).Equals(id));
|
||||
|
||||
_hasZeroCountCache = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetCacheTypeKey()) != null;
|
||||
return _hasZeroCountCache.Value;
|
||||
// see note in InsertEntities - what we get here is the original
|
||||
// cached entity, not a clone, so we need to manually ensure it is deep-cloned.
|
||||
return entity == null ? null : (TEntity) entity.DeepClone();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This policy will cache the full data set as a single collection
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected TEntity[] GetAllFromCache()
|
||||
/// <inheritdoc />
|
||||
public override TEntity GetCached(TId id)
|
||||
{
|
||||
var found = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetCacheTypeKey());
|
||||
// get all from the cache -- and only the cache, then look for the entity
|
||||
var all = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetEntityTypeCacheKey());
|
||||
var entity = all == null ? null : all.FirstOrDefault(x => _entityGetId(x).Equals(id));
|
||||
|
||||
//This method will get called before checking for zero count cache, so we'll just set the flag here
|
||||
_hasZeroCountCache = found != null;
|
||||
|
||||
return found == null ? new TEntity[] { } : found.WhereNotNull().ToArray();
|
||||
// see note in InsertEntities - what we get here is the original
|
||||
// cached entity, not a clone, so we need to manually ensure it is deep-cloned.
|
||||
return entity == null ? null : (TEntity)entity.DeepClone();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Exists(TId id, Func<TId, bool> performExits, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
// get all as one set, then look for the entity
|
||||
var all = GetAllCached(performGetAll);
|
||||
return all.Any(x => _entityGetId(x).Equals(id));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
// get all as one set, from cache if possible, else repo
|
||||
var all = GetAllCached(performGetAll);
|
||||
|
||||
// if ids have been specified, filter
|
||||
if (ids.Length > 0) all = all.Where(x => ids.Contains(_entityGetId(x)));
|
||||
|
||||
// and return
|
||||
// see note in SetCacheActionToInsertEntities - what we get here is the original
|
||||
// cached entities, not clones, so we need to manually ensure they are deep-cloned.
|
||||
return all.Select(x => (TEntity) x.DeepClone()).ToArray();
|
||||
}
|
||||
|
||||
// does NOT clone anything, so be nice with the returned values
|
||||
private IEnumerable<TEntity> GetAllCached(Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
// try the cache first
|
||||
var all = Cache.GetCacheItem<DeepCloneableList<TEntity>>(GetEntityTypeCacheKey());
|
||||
if (all != null) return all.ToArray();
|
||||
|
||||
// else get from repo and cache
|
||||
var entities = performGetAll(EmptyIds).WhereNotNull().ToArray();
|
||||
InsertEntities(entities); // may be an empty array...
|
||||
return entities;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ClearAll()
|
||||
{
|
||||
Cache.ClearCacheItem(GetEntityTypeCacheKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates cache policies
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TId"></typeparam>
|
||||
internal class FullDataSetRepositoryCachePolicyFactory<TEntity, TId> : IRepositoryCachePolicyFactory<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
private readonly IRuntimeCacheProvider _runtimeCache;
|
||||
private readonly Func<TEntity, TId> _getEntityId;
|
||||
private readonly Func<IEnumerable<TEntity>> _getAllFromRepo;
|
||||
private readonly bool _expires;
|
||||
|
||||
public FullDataSetRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, Func<TEntity, TId> getEntityId, Func<IEnumerable<TEntity>> getAllFromRepo, bool expires)
|
||||
{
|
||||
_runtimeCache = runtimeCache;
|
||||
_getEntityId = getEntityId;
|
||||
_getAllFromRepo = getAllFromRepo;
|
||||
_expires = expires;
|
||||
}
|
||||
|
||||
public virtual IRepositoryCachePolicy<TEntity, TId> CreatePolicy()
|
||||
{
|
||||
return new FullDataSetRepositoryCachePolicy<TEntity, TId>(_runtimeCache, _getEntityId, _getAllFromRepo, _expires);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
internal interface IRepositoryCachePolicy<TEntity, TId> : IDisposable
|
||||
internal interface IRepositoryCachePolicy<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
TEntity Get(TId id, Func<TId, TEntity> getFromRepo);
|
||||
TEntity Get(TId id);
|
||||
bool Exists(TId id, Func<TId, bool> getFromRepo);
|
||||
|
||||
void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod);
|
||||
void Remove(TEntity entity, Action<TEntity> persistMethod);
|
||||
TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo);
|
||||
// note:
|
||||
// at the moment each repository instance creates its corresponding cache policy instance
|
||||
// we could reduce allocations by using static cache policy instances but then we would need
|
||||
// to modify all methods here to pass the repository and cache eg:
|
||||
//
|
||||
// TEntity Get(TRepository repository, IRuntimeCacheProvider cache, TId id);
|
||||
//
|
||||
// it is not *that* complicated but then RepositoryBase needs to have a TRepository generic
|
||||
// type parameter and it all becomes convoluted - keeping it simple for the time being.
|
||||
|
||||
// fixme explain
|
||||
IRepositoryCachePolicy<TEntity, TId> Scoped(IRuntimeCacheProvider runtimeCache, IScope scope);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an entity from the cache, else from the repository.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="performGet">The repository PerformGet method.</param>
|
||||
/// <param name="performGetAll">The repository PerformGetAll method.</param>
|
||||
/// <returns>The entity with the specified identifier, if it exits, else null.</returns>
|
||||
/// <remarks>First considers the cache then the repository.</remarks>
|
||||
TEntity Get(TId id, Func<TId, TEntity> performGet, Func<TId[], IEnumerable<TEntity>> performGetAll);
|
||||
|
||||
/// <summary>
|
||||
/// Gets an entity from the cache.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <returns>The entity with the specified identifier, if it is in the cache already, else null.</returns>
|
||||
/// <remarks>Does not consider the repository at all.</remarks>
|
||||
TEntity GetCached(TId id);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether an entity with a specified identifier exists.
|
||||
/// </summary>
|
||||
/// <param name="id">The identifier.</param>
|
||||
/// <param name="performExists">The repository PerformExists method.</param>
|
||||
/// <param name="performGetAll">The repository PerformGetAll method.</param>
|
||||
/// <returns>A value indicating whether an entity with the specified identifier exists.</returns>
|
||||
/// <remarks>First considers the cache then the repository.</remarks>
|
||||
bool Exists(TId id, Func<TId, bool> performExists, Func<TId[], IEnumerable<TEntity>> performGetAll);
|
||||
|
||||
/// <summary>
|
||||
/// Creates an entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <param name="persistNew">The repository PersistNewItem method.</param>
|
||||
/// <remarks>Creates the entity in the repository, and updates the cache accordingly.</remarks>
|
||||
void Create(TEntity entity, Action<TEntity> persistNew);
|
||||
|
||||
/// <summary>
|
||||
/// Updates an entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <param name="persistUpdated">The reopsitory PersistUpdatedItem method.</param>
|
||||
/// <remarks>Updates the entity in the repository, and updates the cache accordingly.</remarks>
|
||||
void Update(TEntity entity, Action<TEntity> persistUpdated);
|
||||
|
||||
/// <summary>
|
||||
/// Removes an entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The entity.</param>
|
||||
/// <param name="persistDeleted">The repository PersistDeletedItem method.</param>
|
||||
/// <remarks>Removes the entity from the repository and clears the cache.</remarks>
|
||||
void Delete(TEntity entity, Action<TEntity> persistDeleted);
|
||||
|
||||
/// <summary>
|
||||
/// Gets entities.
|
||||
/// </summary>
|
||||
/// <param name="ids">The identifiers.</param>
|
||||
/// <param name="performGetAll">The repository PerformGetAll method.</param>
|
||||
/// <returns>If <paramref name="ids"/> is empty, all entities, else the entities with the specified identifiers.</returns>
|
||||
/// <remarks>Get all the entities. Either from the cache or the repository depending on the implementation.</remarks>
|
||||
TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> performGetAll);
|
||||
|
||||
/// <summary>
|
||||
/// Clears the entire cache.
|
||||
/// </summary>
|
||||
void ClearAll();
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
internal interface IRepositoryCachePolicyFactory<TEntity, TId> where TEntity : class, IAggregateRoot
|
||||
{
|
||||
IRepositoryCachePolicy<TEntity, TId> CreatePolicy();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
internal class NoRepositoryCachePolicy<TEntity, TId> : IRepositoryCachePolicy<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
private static readonly NoRepositoryCachePolicy<TEntity, TId> StaticInstance = new NoRepositoryCachePolicy<TEntity, TId>();
|
||||
|
||||
private NoRepositoryCachePolicy()
|
||||
{ }
|
||||
|
||||
public static NoRepositoryCachePolicy<TEntity, TId> Instance { get { return StaticInstance; } }
|
||||
|
||||
public IRepositoryCachePolicy<TEntity, TId> Scoped(IRuntimeCacheProvider runtimeCache, IScope scope)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public TEntity Get(TId id, Func<TId, TEntity> performGet, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
return performGet(id);
|
||||
}
|
||||
|
||||
public TEntity GetCached(TId id)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool Exists(TId id, Func<TId, bool> performExists, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
return performExists(id);
|
||||
}
|
||||
|
||||
public void Create(TEntity entity, Action<TEntity> persistNew)
|
||||
{
|
||||
persistNew(entity);
|
||||
}
|
||||
|
||||
public void Update(TEntity entity, Action<TEntity> persistUpdated)
|
||||
{
|
||||
persistUpdated(entity);
|
||||
}
|
||||
|
||||
public void Delete(TEntity entity, Action<TEntity> persistDeleted)
|
||||
{
|
||||
persistDeleted(entity);
|
||||
}
|
||||
|
||||
public TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
return performGetAll(ids).ToArray();
|
||||
}
|
||||
|
||||
public void ClearAll()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates cache policies
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TId"></typeparam>
|
||||
internal class OnlySingleItemsRepositoryCachePolicyFactory<TEntity, TId> : IRepositoryCachePolicyFactory<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
private readonly IRuntimeCacheProvider _runtimeCache;
|
||||
private readonly RepositoryCachePolicyOptions _options;
|
||||
|
||||
public OnlySingleItemsRepositoryCachePolicyFactory(IRuntimeCacheProvider runtimeCache, RepositoryCachePolicyOptions options)
|
||||
{
|
||||
_runtimeCache = runtimeCache;
|
||||
_options = options;
|
||||
}
|
||||
|
||||
public virtual IRepositoryCachePolicy<TEntity, TId> CreatePolicy()
|
||||
{
|
||||
return new SingleItemsOnlyRepositoryCachePolicy<TEntity, TId>(_runtimeCache, _options);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,51 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
internal abstract class RepositoryCachePolicyBase<TEntity, TId> : DisposableObject, IRepositoryCachePolicy<TEntity, TId>
|
||||
/// <summary>
|
||||
/// A base class for repository cache policies.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity">The type of the entity.</typeparam>
|
||||
/// <typeparam name="TId">The type of the identifier.</typeparam>
|
||||
internal abstract class RepositoryCachePolicyBase<TEntity, TId> : IRepositoryCachePolicy<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
private Action _action;
|
||||
|
||||
protected RepositoryCachePolicyBase(IRuntimeCacheProvider cache)
|
||||
{
|
||||
if (cache == null) throw new ArgumentNullException("cache");
|
||||
|
||||
if (cache == null) throw new ArgumentNullException("cache");
|
||||
Cache = cache;
|
||||
}
|
||||
|
||||
public abstract IRepositoryCachePolicy<TEntity, TId> Scoped(IRuntimeCacheProvider runtimeCache, IScope scope);
|
||||
|
||||
protected IRuntimeCacheProvider Cache { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// The disposal performs the caching
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
if (_action != null)
|
||||
{
|
||||
_action();
|
||||
}
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public abstract TEntity Get(TId id, Func<TId, TEntity> performGet, Func<TId[], IEnumerable<TEntity>> performGetAll);
|
||||
|
||||
/// <summary>
|
||||
/// Sets the action to execute on disposal
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
protected void SetCacheAction(Action action)
|
||||
{
|
||||
_action = action;
|
||||
}
|
||||
/// <inheritdoc />
|
||||
public abstract TEntity GetCached(TId id);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract bool Exists(TId id, Func<TId, bool> performExists, Func<TId[], IEnumerable<TEntity>> performGetAll);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Create(TEntity entity, Action<TEntity> persistNew);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Update(TEntity entity, Action<TEntity> persistUpdated);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void Delete(TEntity entity, Action<TEntity> persistDeleted);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> performGetAll);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract void ClearAll();
|
||||
|
||||
public abstract TEntity Get(TId id, Func<TId, TEntity> getFromRepo);
|
||||
public abstract TEntity Get(TId id);
|
||||
public abstract bool Exists(TId id, Func<TId, bool> getFromRepo);
|
||||
public abstract void CreateOrUpdate(TEntity entity, Action<TEntity> persistMethod);
|
||||
public abstract void Remove(TEntity entity, Action<TEntity> persistMethod);
|
||||
public abstract TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> getFromRepo);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@ using System;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// Specifies how a repository cache policy should cache entities.
|
||||
/// </summary>
|
||||
internal class RepositoryCachePolicyOptions
|
||||
{
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
internal class ScopedRepositoryCachePolicy<TEntity, TId> : IRepositoryCachePolicy<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
private readonly IRepositoryCachePolicy<TEntity, TId> _cachePolicy;
|
||||
private readonly IRuntimeCacheProvider _globalIsolatedCache;
|
||||
private readonly IScope _scope;
|
||||
|
||||
public ScopedRepositoryCachePolicy(IRepositoryCachePolicy<TEntity, TId> cachePolicy, IRuntimeCacheProvider globalIsolatedCache, IScope scope)
|
||||
{
|
||||
_cachePolicy = cachePolicy;
|
||||
_globalIsolatedCache = globalIsolatedCache;
|
||||
_scope = scope;
|
||||
}
|
||||
|
||||
public IRepositoryCachePolicy<TEntity, TId> Scoped(IRuntimeCacheProvider runtimeCache, IScope scope)
|
||||
{
|
||||
throw new InvalidOperationException(); // obviously
|
||||
}
|
||||
|
||||
public TEntity Get(TId id, Func<TId, TEntity> performGet, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
// loads into the local cache only, ok for now
|
||||
return _cachePolicy.Get(id, performGet, performGetAll);
|
||||
}
|
||||
|
||||
public TEntity GetCached(TId id)
|
||||
{
|
||||
// loads into the local cache only, ok for now
|
||||
return _cachePolicy.GetCached(id);
|
||||
}
|
||||
|
||||
public bool Exists(TId id, Func<TId, bool> performExists, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
// loads into the local cache only, ok for now
|
||||
return _cachePolicy.Exists(id, performExists, performGetAll);
|
||||
}
|
||||
|
||||
public void Create(TEntity entity, Action<TEntity> persistNew)
|
||||
{
|
||||
// writes into the local cache
|
||||
_cachePolicy.Create(entity, persistNew);
|
||||
}
|
||||
|
||||
public void Update(TEntity entity, Action<TEntity> persistUpdated)
|
||||
{
|
||||
// writes into the local cache
|
||||
_cachePolicy.Update(entity, persistUpdated);
|
||||
}
|
||||
|
||||
public void Delete(TEntity entity, Action<TEntity> persistDeleted)
|
||||
{
|
||||
// deletes the local cache
|
||||
_cachePolicy.Delete(entity, persistDeleted);
|
||||
}
|
||||
|
||||
public TEntity[] GetAll(TId[] ids, Func<TId[], IEnumerable<TEntity>> performGetAll)
|
||||
{
|
||||
// loads into the local cache only, ok for now
|
||||
return _cachePolicy.GetAll(ids, performGetAll);
|
||||
}
|
||||
|
||||
public void ClearAll()
|
||||
{
|
||||
// clears the local cache
|
||||
_cachePolicy.ClearAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +1,27 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Core.Models.EntityBase;
|
||||
|
||||
namespace Umbraco.Core.Cache
|
||||
{
|
||||
/// <summary>
|
||||
/// A caching policy that ignores all caches for GetAll - it will only cache calls for individual items
|
||||
/// Represents a special policy that does not cache the result of GetAll.
|
||||
/// </summary>
|
||||
/// <typeparam name="TEntity"></typeparam>
|
||||
/// <typeparam name="TId"></typeparam>
|
||||
/// <typeparam name="TEntity">The type of the entity.</typeparam>
|
||||
/// <typeparam name="TId">The type of the identifier.</typeparam>
|
||||
/// <remarks>
|
||||
/// <para>Overrides the default repository cache policy and does not writes the result of GetAll
|
||||
/// to cache, but only the result of individual Gets. It does read the cache for GetAll, though.</para>
|
||||
/// <para>Used by DictionaryRepository.</para>
|
||||
/// </remarks>
|
||||
internal class SingleItemsOnlyRepositoryCachePolicy<TEntity, TId> : DefaultRepositoryCachePolicy<TEntity, TId>
|
||||
where TEntity : class, IAggregateRoot
|
||||
{
|
||||
public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options) : base(cache, options)
|
||||
public SingleItemsOnlyRepositoryCachePolicy(IRuntimeCacheProvider cache, RepositoryCachePolicyOptions options)
|
||||
: base(cache, options)
|
||||
{ }
|
||||
|
||||
protected override void InsertEntities(TId[] ids, TEntity[] entities)
|
||||
{
|
||||
}
|
||||
|
||||
protected override void SetCacheAction(TId[] ids, TEntity[] entityCollection)
|
||||
{
|
||||
//no-op
|
||||
// nop
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,10 @@ namespace Umbraco.Core
|
||||
private static readonly ICacheProvider NullRequestCache = new NullCacheProvider();
|
||||
private static readonly ICacheProvider NullStaticCache = new NullCacheProvider();
|
||||
private static readonly IRuntimeCacheProvider NullRuntimeCache = new NullCacheProvider();
|
||||
private static readonly IsolatedRuntimeCache NullIsolatedCache = new IsolatedRuntimeCache(_ => NullRuntimeCache);
|
||||
private static readonly CacheHelper NullCache = new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, NullIsolatedCache);
|
||||
|
||||
public static CacheHelper NoCache { get { return NullCache; } }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a cache helper with disabled caches
|
||||
@@ -31,7 +35,10 @@ namespace Umbraco.Core
|
||||
/// </remarks>
|
||||
public static CacheHelper CreateDisabledCacheHelper()
|
||||
{
|
||||
return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, new IsolatedRuntimeCache(t => NullRuntimeCache));
|
||||
// do *not* return NoCache
|
||||
// NoCache is a special instance that is detected by RepositoryBase and disables all cache policies
|
||||
// CreateDisabledCacheHelper is used in tests to use no cache, *but* keep all cache policies
|
||||
return new CacheHelper(NullRuntimeCache, NullStaticCache, NullRequestCache, NullIsolatedCache);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
|
||||
/// Gets the version comment (like beta or RC).
|
||||
/// </summary>
|
||||
/// <value>The version comment.</value>
|
||||
public static string CurrentComment { get { return "alpha056"; } }
|
||||
public static string CurrentComment { get { return "alpha063"; } }
|
||||
|
||||
// Get the version of the umbraco.dll by looking at a class in that dll
|
||||
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace Umbraco.Core
|
||||
|
||||
// forms
|
||||
public const string FormsForm = "forms-form";
|
||||
public const string FormsWorkflow = "forms-workflow";
|
||||
public const string FormsRecord = "forms-record";
|
||||
public const string FormsPreValue = "forms-prevalue";
|
||||
public const string FormsDataSource = "forms-datasource";
|
||||
|
||||
// string entity types
|
||||
|
||||
@@ -89,10 +89,10 @@ namespace Umbraco.Core
|
||||
return RelationType;
|
||||
case UmbracoObjectTypes.FormsForm:
|
||||
return FormsForm;
|
||||
case UmbracoObjectTypes.FormsWorkflow:
|
||||
return FormsWorkflow;
|
||||
case UmbracoObjectTypes.FormsRecord:
|
||||
return FormsRecord;
|
||||
case UmbracoObjectTypes.FormsPreValue:
|
||||
return FormsPreValue;
|
||||
case UmbracoObjectTypes.FormsDataSource:
|
||||
return FormsDataSource;
|
||||
}
|
||||
throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType));
|
||||
}
|
||||
@@ -131,10 +131,10 @@ namespace Umbraco.Core
|
||||
return UmbracoObjectTypes.RelationType;
|
||||
case FormsForm:
|
||||
return UmbracoObjectTypes.FormsForm;
|
||||
case FormsWorkflow:
|
||||
return UmbracoObjectTypes.FormsWorkflow;
|
||||
case FormsRecord:
|
||||
return UmbracoObjectTypes.FormsRecord;
|
||||
case FormsPreValue:
|
||||
return UmbracoObjectTypes.FormsPreValue;
|
||||
case FormsDataSource:
|
||||
return UmbracoObjectTypes.FormsDataSource;
|
||||
}
|
||||
throw new NotSupportedException(
|
||||
string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType));
|
||||
|
||||
@@ -174,24 +174,24 @@ namespace Umbraco.Core
|
||||
public static readonly Guid FormsFormGuid = new Guid(FormsForm);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Forms Workflow.
|
||||
/// Guid for a Forms PreValue Source.
|
||||
/// </summary>
|
||||
public const string FormsWorkflow = "42D7BF9B-A362-4FEE-B45A-674D5C064B70";
|
||||
public const string FormsPreValue = "42D7BF9B-A362-4FEE-B45A-674D5C064B70";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Forms Workflow.
|
||||
/// Guid for a Forms PreValue Source.
|
||||
/// </summary>
|
||||
public static readonly Guid FormsWorkflowGuid = new Guid(FormsWorkflow);
|
||||
public static readonly Guid FormsPreValueGuid = new Guid(FormsPreValue);
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Forms Record.
|
||||
/// Guid for a Forms DataSource.
|
||||
/// </summary>
|
||||
public const string FormsRecord = "CFED6CE4-9359-443E-9977-9956FEB1D867";
|
||||
public const string FormsDataSource = "CFED6CE4-9359-443E-9977-9956FEB1D867";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Forms Record.
|
||||
/// Guid for a Forms DataSource.
|
||||
/// </summary>
|
||||
public static readonly Guid FormsRecordGuid = new Guid(FormsRecord);
|
||||
public static readonly Guid FormsDataSourceGuid = new Guid(FormsDataSource);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ using Umbraco.Core.PropertyEditors.ValueConverters;
|
||||
using Umbraco.Core.Publishing;
|
||||
using Umbraco.Core.Macros;
|
||||
using Umbraco.Core.Manifest;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Sync;
|
||||
using Umbraco.Core.Strings;
|
||||
@@ -108,8 +109,11 @@ namespace Umbraco.Core
|
||||
var dbFactory = new DefaultDatabaseFactory(Constants.System.UmbracoConnectionName, ProfilingLogger.Logger);
|
||||
Database.Mapper = new PetaPocoMapper();
|
||||
|
||||
var scopeProvider = new ScopeProvider(dbFactory);
|
||||
dbFactory.ScopeProvider = scopeProvider;
|
||||
|
||||
var dbContext = new DatabaseContext(
|
||||
dbFactory,
|
||||
scopeProvider,
|
||||
ProfilingLogger.Logger,
|
||||
SqlSyntaxProviders.CreateDefault(ProfilingLogger.Logger));
|
||||
|
||||
@@ -117,7 +121,7 @@ namespace Umbraco.Core
|
||||
dbContext.Initialize();
|
||||
|
||||
//get the service context
|
||||
var serviceContext = CreateServiceContext(dbContext, dbFactory);
|
||||
var serviceContext = CreateServiceContext(dbContext, scopeProvider);
|
||||
|
||||
//set property and singleton from response
|
||||
ApplicationContext.Current = ApplicationContext = CreateApplicationContext(dbContext, serviceContext);
|
||||
@@ -160,17 +164,16 @@ namespace Umbraco.Core
|
||||
/// Creates and returns the service context for the app
|
||||
/// </summary>
|
||||
/// <param name="dbContext"></param>
|
||||
/// <param name="dbFactory"></param>
|
||||
/// <param name="scopeProvider"></param>
|
||||
/// <returns></returns>
|
||||
protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IDatabaseFactory dbFactory)
|
||||
protected virtual ServiceContext CreateServiceContext(DatabaseContext dbContext, IScopeProvider scopeProvider)
|
||||
{
|
||||
//default transient factory
|
||||
var msgFactory = new TransientMessagesFactory();
|
||||
return new ServiceContext(
|
||||
new RepositoryFactory(ApplicationCache, ProfilingLogger.Logger, dbContext.SqlSyntax, UmbracoConfig.For.UmbracoSettings()),
|
||||
new PetaPocoUnitOfWorkProvider(dbFactory),
|
||||
new FileUnitOfWorkProvider(),
|
||||
new PublishingStrategy(msgFactory, ProfilingLogger.Logger),
|
||||
new PetaPocoUnitOfWorkProvider(scopeProvider),
|
||||
new FileUnitOfWorkProvider(scopeProvider),
|
||||
ApplicationCache,
|
||||
ProfilingLogger.Logger,
|
||||
msgFactory);
|
||||
|
||||
@@ -14,6 +14,7 @@ using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Migrations;
|
||||
using Umbraco.Core.Persistence.Migrations.Initial;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core
|
||||
@@ -26,7 +27,7 @@ namespace Umbraco.Core
|
||||
/// </remarks>
|
||||
public class DatabaseContext
|
||||
{
|
||||
private readonly IDatabaseFactory _factory;
|
||||
internal readonly IScopeProviderInternal ScopeProvider;
|
||||
private readonly ILogger _logger;
|
||||
private readonly SqlSyntaxProviders _syntaxProviders;
|
||||
private bool _configured;
|
||||
@@ -40,6 +41,19 @@ namespace Umbraco.Core
|
||||
/// </summary>
|
||||
private const int ConnectionCheckMinutes = 1;
|
||||
|
||||
#region Compatibility with 7.5
|
||||
|
||||
// note: the ctors accepting IDatabaseFactory are here only for backward compatibility purpose
|
||||
//
|
||||
// problem: IDatabaseFactory2 adds the CreateNewDatabase() method which creates a new database
|
||||
// 'cos IDatabaseFactory CreateDatabase() is supposed to also manage the ambient thing. We
|
||||
// want to keep these ctors for backward compatibility reasons (in case ppl use them in tests)
|
||||
// so we need to create a scope provider (else nothing would work) and so we need a IDatabaseFactory2,
|
||||
// so...?
|
||||
// solution: wrap IDatabaseFactory and pretend we have a IDatabaseFactory2, it *should* work in most
|
||||
// cases but really, it depends on what ppl are doing in their tests... yet, cannot really see any
|
||||
// other way to do it?
|
||||
|
||||
[Obsolete("Use the constructor specifying all dependencies instead")]
|
||||
public DatabaseContext(IDatabaseFactory factory)
|
||||
: this(factory, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[]
|
||||
@@ -48,8 +62,7 @@ namespace Umbraco.Core
|
||||
new SqlCeSyntaxProvider(),
|
||||
new SqlServerSyntaxProvider()
|
||||
}))
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
@@ -63,7 +76,11 @@ namespace Umbraco.Core
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
if (syntaxProviders == null) throw new ArgumentNullException("syntaxProviders");
|
||||
|
||||
_factory = factory;
|
||||
var asDbFactory2 = factory as IDatabaseFactory2;
|
||||
ScopeProvider = asDbFactory2 == null
|
||||
? new ScopeProvider(new DatabaseFactoryWrapper(factory))
|
||||
: new ScopeProvider(asDbFactory2);
|
||||
|
||||
_logger = logger;
|
||||
_syntaxProviders = syntaxProviders;
|
||||
}
|
||||
@@ -80,22 +97,86 @@ namespace Umbraco.Core
|
||||
_providerName = providerName;
|
||||
SqlSyntax = sqlSyntax;
|
||||
SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax;
|
||||
_factory = factory;
|
||||
|
||||
var asDbFactory2 = factory as IDatabaseFactory2;
|
||||
ScopeProvider = asDbFactory2 == null
|
||||
? new ScopeProvider(new DatabaseFactoryWrapper(factory))
|
||||
: new ScopeProvider(asDbFactory2);
|
||||
|
||||
_logger = logger;
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
public List<UmbracoDatabase> Databases
|
||||
private class DatabaseFactoryWrapper : IDatabaseFactory2
|
||||
{
|
||||
get
|
||||
private readonly IDatabaseFactory _factory;
|
||||
|
||||
public DatabaseFactoryWrapper(IDatabaseFactory factory)
|
||||
{
|
||||
var factory = _factory as DefaultDatabaseFactory;
|
||||
if (factory == null) throw new NotSupportedException();
|
||||
return factory.Databases;
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
public UmbracoDatabase CreateDatabase()
|
||||
{
|
||||
return _factory.CreateDatabase();
|
||||
}
|
||||
|
||||
public UmbracoDatabase CreateNewDatabase()
|
||||
{
|
||||
return CreateDatabase();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_factory.Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endregion
|
||||
|
||||
[Obsolete("Use the constructor specifying all dependencies instead")]
|
||||
internal DatabaseContext(IScopeProviderInternal scopeProvider)
|
||||
: this(scopeProvider, LoggerResolver.Current.Logger, new SqlSyntaxProviders(new ISqlSyntaxProvider[]
|
||||
{
|
||||
new MySqlSyntaxProvider(LoggerResolver.Current.Logger),
|
||||
new SqlCeSyntaxProvider(),
|
||||
new SqlServerSyntaxProvider()
|
||||
}))
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Default constructor
|
||||
/// </summary>
|
||||
/// <param name="scopeProvider"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="syntaxProviders"></param>
|
||||
internal DatabaseContext(IScopeProviderInternal scopeProvider, ILogger logger, SqlSyntaxProviders syntaxProviders)
|
||||
{
|
||||
if (scopeProvider == null) throw new ArgumentNullException("scopeProvider");
|
||||
if (logger == null) throw new ArgumentNullException("logger");
|
||||
if (syntaxProviders == null) throw new ArgumentNullException("syntaxProviders");
|
||||
|
||||
ScopeProvider = scopeProvider;
|
||||
_logger = logger;
|
||||
_syntaxProviders = syntaxProviders;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a configured DatabaseContext
|
||||
/// </summary>
|
||||
/// <param name="scopeProvider"></param>
|
||||
/// <param name="logger"></param>
|
||||
/// <param name="sqlSyntax"></param>
|
||||
/// <param name="providerName"></param>
|
||||
internal DatabaseContext(IScopeProviderInternal scopeProvider, ILogger logger, ISqlSyntaxProvider sqlSyntax, string providerName)
|
||||
{
|
||||
_providerName = providerName;
|
||||
SqlSyntax = sqlSyntax;
|
||||
SqlSyntaxContext.SqlSyntaxProvider = SqlSyntax;
|
||||
ScopeProvider = scopeProvider;
|
||||
_logger = logger;
|
||||
_configured = true;
|
||||
}
|
||||
|
||||
public ISqlSyntaxProvider SqlSyntax { get; private set; }
|
||||
|
||||
@@ -110,7 +191,17 @@ namespace Umbraco.Core
|
||||
/// </remarks>
|
||||
public virtual UmbracoDatabase Database
|
||||
{
|
||||
get { return _factory.CreateDatabase(); }
|
||||
get
|
||||
{
|
||||
if (IsDatabaseConfigured == false)
|
||||
{
|
||||
throw new InvalidOperationException("Cannot create a database instance, there is no available connection string");
|
||||
}
|
||||
|
||||
return ScopeProvider.GetAmbientOrNoScope().Database;
|
||||
//var scope = ScopeProvider.AmbientScope;
|
||||
//return scope != null ? scope.Database : ScopeProvider.CreateNoScope().Database;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -123,6 +214,8 @@ namespace Umbraco.Core
|
||||
/// will be properly removed from call context and does not interfere with anything else. In most case
|
||||
/// it is not replacing anything, just temporarily installing a database in context.</para>
|
||||
/// </remarks>
|
||||
// fixme - this should just entirely be replaced by Scope?
|
||||
/*
|
||||
public virtual IDisposable UseSafeDatabase(bool force = false)
|
||||
{
|
||||
var factory = _factory as DefaultDatabaseFactory;
|
||||
@@ -138,6 +231,7 @@ namespace Umbraco.Core
|
||||
// create a new, temp, database (will be disposed with UsingDatabase)
|
||||
return new UsingDatabase(null, factory.CreateDatabase());
|
||||
}
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Boolean indicating whether the database has been configured
|
||||
@@ -159,7 +253,7 @@ namespace Umbraco.Core
|
||||
|
||||
//Don't check again if the timeout period hasn't elapsed
|
||||
//this ensures we don't keep checking the connection too many times in a row like during startup.
|
||||
//Do check if the _connectionLastChecked is null which means we're just initializing or it could
|
||||
//Do check if the _connectionLastChecked is null which means we're just initializing or it could
|
||||
//not connect last time it was checked.
|
||||
if ((_connectionLastChecked.HasValue && (DateTime.Now - _connectionLastChecked.Value).TotalMinutes > ConnectionCheckMinutes)
|
||||
|| _connectionLastChecked.HasValue == false)
|
||||
@@ -202,7 +296,7 @@ namespace Umbraco.Core
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Can't find a connection string with the name '" + Constants.System.UmbracoConnectionName + "'");
|
||||
throw new NullReferenceException("Can't find a connection string with the name '" + Constants.System.UmbracoConnectionName + "'");
|
||||
}
|
||||
return _providerName;
|
||||
}
|
||||
@@ -809,6 +903,7 @@ namespace Umbraco.Core
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
private class UsingDatabase : IDisposable
|
||||
{
|
||||
private readonly UmbracoDatabase _orig;
|
||||
@@ -831,5 +926,6 @@ namespace Umbraco.Core
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
@@ -10,8 +10,8 @@ namespace Umbraco.Core.Events
|
||||
/// Event args for that can support cancellation
|
||||
/// </summary>
|
||||
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
|
||||
public class CancellableEventArgs : EventArgs
|
||||
{
|
||||
public class CancellableEventArgs : EventArgs, IEquatable<CancellableEventArgs>
|
||||
{
|
||||
private bool _cancel;
|
||||
|
||||
public CancellableEventArgs(bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
|
||||
@@ -98,6 +98,36 @@ namespace Umbraco.Core.Events
|
||||
/// This allows for a bit of flexibility in our event raising - it's not pretty but we need to maintain backwards compatibility
|
||||
/// so we cannot change the strongly typed nature for some events.
|
||||
/// </remarks>
|
||||
public ReadOnlyDictionary<string, object> AdditionalData { get; private set; }
|
||||
}
|
||||
public ReadOnlyDictionary<string, object> AdditionalData { get; private set; }
|
||||
|
||||
public bool Equals(CancellableEventArgs other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Equals(AdditionalData, other.AdditionalData);
|
||||
}
|
||||
|
||||
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((CancellableEventArgs) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return (AdditionalData != null ? AdditionalData.GetHashCode() : 0);
|
||||
}
|
||||
|
||||
public static bool operator ==(CancellableEventArgs left, CancellableEventArgs right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(CancellableEventArgs left, CancellableEventArgs right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Permissions;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -10,7 +11,7 @@ namespace Umbraco.Core.Events
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[HostProtection(SecurityAction.LinkDemand, SharedState = true)]
|
||||
public class CancellableObjectEventArgs<T> : CancellableEventArgs
|
||||
public class CancellableObjectEventArgs<T> : CancellableEventArgs, IEquatable<CancellableObjectEventArgs<T>>
|
||||
{
|
||||
public CancellableObjectEventArgs(T eventObject, bool canCancel, EventMessages messages, IDictionary<string, object> additionalData)
|
||||
: base(canCancel, messages, additionalData)
|
||||
@@ -48,5 +49,37 @@ namespace Umbraco.Core.Events
|
||||
/// </remarks>
|
||||
protected T EventObject { get; set; }
|
||||
|
||||
public bool Equals(CancellableObjectEventArgs<T> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && EqualityComparer<T>.Default.Equals(EventObject, other.EventObject);
|
||||
}
|
||||
|
||||
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((CancellableObjectEventArgs<T>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ EqualityComparer<T>.Default.GetHashCode(EventObject);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(CancellableObjectEventArgs<T> left, CancellableObjectEventArgs<T> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class CopyEventArgs<TEntity> : CancellableObjectEventArgs<TEntity>
|
||||
public class CopyEventArgs<TEntity> : CancellableObjectEventArgs<TEntity>, IEquatable<CopyEventArgs<TEntity>>
|
||||
{
|
||||
public CopyEventArgs(TEntity original, TEntity copy, bool canCancel, int parentId)
|
||||
: base(original, canCancel)
|
||||
@@ -43,5 +46,42 @@ namespace Umbraco.Core.Events
|
||||
public int ParentId { get; private set; }
|
||||
|
||||
public bool RelateToOriginal { get; set; }
|
||||
|
||||
public bool Equals(CopyEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && EqualityComparer<TEntity>.Default.Equals(Copy, other.Copy) && ParentId == other.ParentId && RelateToOriginal == other.RelateToOriginal;
|
||||
}
|
||||
|
||||
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((CopyEventArgs<TEntity>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = base.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ EqualityComparer<TEntity>.Default.GetHashCode(Copy);
|
||||
hashCode = (hashCode * 397) ^ ParentId;
|
||||
hashCode = (hashCode * 397) ^ RelateToOriginal.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(CopyEventArgs<TEntity> left, CopyEventArgs<TEntity> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(CopyEventArgs<TEntity> left, CopyEventArgs<TEntity> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class DeleteEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
|
||||
public class DeleteEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<DeleteEventArgs<TEntity>>, IDeletingMediaFilesEventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting multiple entities that are used in the delete operation
|
||||
@@ -99,10 +100,43 @@ namespace Umbraco.Core.Events
|
||||
/// <summary>
|
||||
/// A list of media files that can be added to during a deleted operation for which Umbraco will ensure are removed
|
||||
/// </summary>
|
||||
public List<string> MediaFilesToDelete { get; private set; }
|
||||
public List<string> MediaFilesToDelete { get; private set; }
|
||||
|
||||
public bool Equals(DeleteEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && MediaFilesToDelete.Equals(other.MediaFilesToDelete);
|
||||
}
|
||||
|
||||
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((DeleteEventArgs<TEntity>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ MediaFilesToDelete.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(DeleteEventArgs<TEntity> left, DeleteEventArgs<TEntity> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(DeleteEventArgs<TEntity> left, DeleteEventArgs<TEntity> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
|
||||
public class DeleteEventArgs : CancellableEventArgs
|
||||
public class DeleteEventArgs : CancellableEventArgs, IEquatable<DeleteEventArgs>
|
||||
{
|
||||
public DeleteEventArgs(int id, bool canCancel, EventMessages eventMessages)
|
||||
: base(canCancel, eventMessages)
|
||||
@@ -125,5 +159,38 @@ namespace Umbraco.Core.Events
|
||||
/// Gets the Id of the object being deleted.
|
||||
/// </summary>
|
||||
public int Id { get; private set; }
|
||||
|
||||
public bool Equals(DeleteEventArgs other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && Id == other.Id;
|
||||
}
|
||||
|
||||
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((DeleteEventArgs) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ Id;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(DeleteEventArgs left, DeleteEventArgs right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(DeleteEventArgs left, DeleteEventArgs right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@ using System;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class DeleteRevisionsEventArgs : DeleteEventArgs
|
||||
public class DeleteRevisionsEventArgs : DeleteEventArgs, IEquatable<DeleteRevisionsEventArgs>
|
||||
{
|
||||
public DeleteRevisionsEventArgs(int id, bool canCancel, Guid specificVersion = default(Guid), bool deletePriorVersions = false, DateTime dateToRetain = default(DateTime))
|
||||
: base(id, canCancel)
|
||||
@@ -31,5 +31,42 @@ namespace Umbraco.Core.Events
|
||||
{
|
||||
get { return SpecificVersion != default(Guid); }
|
||||
}
|
||||
|
||||
public bool Equals(DeleteRevisionsEventArgs other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && DateToRetain.Equals(other.DateToRetain) && DeletePriorVersions == other.DeletePriorVersions && SpecificVersion.Equals(other.SpecificVersion);
|
||||
}
|
||||
|
||||
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((DeleteRevisionsEventArgs) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = base.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ DateToRetain.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ DeletePriorVersions.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ SpecificVersion.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(DeleteRevisionsEventArgs left, DeleteRevisionsEventArgs right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
internal class EventDefinition : EventDefinitionBase
|
||||
{
|
||||
private readonly EventHandler _trackedEvent;
|
||||
private readonly object _sender;
|
||||
private readonly EventArgs _args;
|
||||
|
||||
public EventDefinition(EventHandler trackedEvent, object sender, EventArgs args, string eventName = null)
|
||||
: base(sender, args, eventName)
|
||||
{
|
||||
_trackedEvent = trackedEvent;
|
||||
_sender = sender;
|
||||
_args = args;
|
||||
}
|
||||
|
||||
public override void RaiseEvent()
|
||||
{
|
||||
if (_trackedEvent != null)
|
||||
{
|
||||
_trackedEvent(_sender, _args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class EventDefinition<TEventArgs> : EventDefinitionBase
|
||||
{
|
||||
private readonly EventHandler<TEventArgs> _trackedEvent;
|
||||
private readonly object _sender;
|
||||
private readonly TEventArgs _args;
|
||||
|
||||
public EventDefinition(EventHandler<TEventArgs> trackedEvent, object sender, TEventArgs args, string eventName = null)
|
||||
: base(sender, args, eventName)
|
||||
{
|
||||
_trackedEvent = trackedEvent;
|
||||
_sender = sender;
|
||||
_args = args;
|
||||
}
|
||||
|
||||
public override void RaiseEvent()
|
||||
{
|
||||
if (_trackedEvent != null)
|
||||
{
|
||||
_trackedEvent(_sender, _args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class EventDefinition<TSender, TEventArgs> : EventDefinitionBase
|
||||
{
|
||||
private readonly TypedEventHandler<TSender, TEventArgs> _trackedEvent;
|
||||
private readonly TSender _sender;
|
||||
private readonly TEventArgs _args;
|
||||
|
||||
public EventDefinition(TypedEventHandler<TSender, TEventArgs> trackedEvent, TSender sender, TEventArgs args, string eventName = null)
|
||||
: base(sender, args, eventName)
|
||||
{
|
||||
_trackedEvent = trackedEvent;
|
||||
_sender = sender;
|
||||
_args = args;
|
||||
}
|
||||
|
||||
public override void RaiseEvent()
|
||||
{
|
||||
if (_trackedEvent != null)
|
||||
{
|
||||
_trackedEvent(_sender, _args);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public abstract class EventDefinitionBase : IEventDefinition, IEquatable<EventDefinitionBase>
|
||||
{
|
||||
protected EventDefinitionBase(object sender, object args, string eventName = null)
|
||||
{
|
||||
if (sender == null) throw new ArgumentNullException("sender");
|
||||
if (args == null) throw new ArgumentNullException("args");
|
||||
Sender = sender;
|
||||
Args = args;
|
||||
EventName = eventName;
|
||||
|
||||
if (EventName.IsNullOrWhiteSpace())
|
||||
{
|
||||
var findResult = EventNameExtractor.FindEvent(sender, args,
|
||||
//don't match "Ing" suffixed names
|
||||
exclude:EventNameExtractor.MatchIngNames);
|
||||
|
||||
if (findResult.Success == false)
|
||||
throw new AmbiguousMatchException("Could not automatically find the event name, the event name will need to be explicitly registered for this event definition. Error: " + findResult.Result.Error);
|
||||
EventName = findResult.Result.Name;
|
||||
}
|
||||
}
|
||||
|
||||
public object Sender { get; private set; }
|
||||
public object Args { get; private set; }
|
||||
public string EventName { get; private set; }
|
||||
|
||||
public abstract void RaiseEvent();
|
||||
|
||||
public bool Equals(EventDefinitionBase other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Args.Equals(other.Args) && string.Equals(EventName, other.EventName) && Sender.Equals(other.Sender);
|
||||
}
|
||||
|
||||
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((EventDefinitionBase) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = Args.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ EventName.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ Sender.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(EventDefinitionBase left, EventDefinitionBase right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(EventDefinitionBase left, EventDefinitionBase right)
|
||||
{
|
||||
return Equals(left, right) == false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// The filter used in the <see cref="IEventDispatcher"/> GetEvents method which determines
|
||||
/// how the result list is filtered
|
||||
/// </summary>
|
||||
public enum EventDefinitionFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns all events tracked
|
||||
/// </summary>
|
||||
All,
|
||||
|
||||
/// <summary>
|
||||
/// Deduplicates events and only returns the first duplicate instance tracked
|
||||
/// </summary>
|
||||
FirstIn,
|
||||
|
||||
/// <summary>
|
||||
/// Deduplicates events and only returns the last duplicate instance tracked
|
||||
/// </summary>
|
||||
LastIn
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
@@ -10,73 +12,40 @@ namespace Umbraco.Core.Events
|
||||
/// </summary>
|
||||
public static class EventExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Raises the event and returns a boolean value indicating if the event was cancelled
|
||||
/// </summary>
|
||||
/// <typeparam name="TSender"></typeparam>
|
||||
/// <typeparam name="TArgs"></typeparam>
|
||||
/// <param name="eventHandler"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <param name="sender"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsRaisedEventCancelled<TSender, TArgs>(
|
||||
this TypedEventHandler<TSender, TArgs> eventHandler,
|
||||
TArgs args,
|
||||
TSender sender)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler != null)
|
||||
eventHandler(sender, args);
|
||||
// keep these two for backward compatibility reasons but understand that
|
||||
// they are *not* part of any scope / event dispatcher / anything...
|
||||
|
||||
/// <summary>
|
||||
/// Raises a cancelable event and returns a value indicating whether the event should be cancelled.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSender">The type of the event source.</typeparam>
|
||||
/// <typeparam name="TArgs">The type of the event data.</typeparam>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="args">The event source.</param>
|
||||
/// <param name="sender">The event data.</param>
|
||||
/// <returns>A value indicating whether the cancelable event should be cancelled</returns>
|
||||
/// <remarks>A cancelable event is raised by a component when it is about to perform an action that can be canceled.</remarks>
|
||||
public static bool IsRaisedEventCancelled<TSender, TArgs>(this TypedEventHandler<TSender, TArgs> eventHandler, TArgs args, TSender sender)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Raises the event
|
||||
/// Raises an event.
|
||||
/// </summary>
|
||||
/// <typeparam name="TSender"></typeparam>
|
||||
/// <typeparam name="TArgs"></typeparam>
|
||||
/// <param name="eventHandler"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <param name="sender"></param>
|
||||
public static void RaiseEvent<TSender, TArgs>(
|
||||
this TypedEventHandler<TSender, TArgs> eventHandler,
|
||||
TArgs args,
|
||||
TSender sender)
|
||||
where TArgs : EventArgs
|
||||
{
|
||||
if (eventHandler != null)
|
||||
eventHandler(sender, args);
|
||||
}
|
||||
|
||||
// moves the last handler that was added to an instance event, to first position
|
||||
public static void PromoteLastHandler(object sender, string eventName)
|
||||
{
|
||||
var fieldInfo = sender.GetType().GetField(eventName, BindingFlags.Instance | BindingFlags.NonPublic);
|
||||
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
|
||||
PromoteLastHandler(sender, fieldInfo);
|
||||
}
|
||||
|
||||
// moves the last handler that was added to a static event, to first position
|
||||
public static void PromoteLastHandler<TSender>(string eventName)
|
||||
/// <typeparam name="TSender">The type of the event source.</typeparam>
|
||||
/// <typeparam name="TArgs">The type of the event data.</typeparam>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="args">The event source.</param>
|
||||
/// <param name="sender">The event data.</param>
|
||||
public static void RaiseEvent<TSender, TArgs>(this TypedEventHandler<TSender, TArgs> eventHandler, TArgs args, TSender sender)
|
||||
where TArgs : EventArgs
|
||||
{
|
||||
var fieldInfo = typeof(TSender).GetField(eventName, BindingFlags.Static | BindingFlags.NonPublic);
|
||||
if (fieldInfo == null) throw new InvalidOperationException("No event named " + eventName + ".");
|
||||
PromoteLastHandler(null, fieldInfo);
|
||||
}
|
||||
|
||||
private static void PromoteLastHandler(object sender, FieldInfo fieldInfo)
|
||||
{
|
||||
var d = fieldInfo.GetValue(sender) as Delegate;
|
||||
if (d == null) return;
|
||||
|
||||
var l = d.GetInvocationList();
|
||||
var x = l[l.Length - 1];
|
||||
for (var i = l.Length - 1; i > 0; i--)
|
||||
l[i] = l[i - 1];
|
||||
l[0] = x;
|
||||
|
||||
fieldInfo.SetValue(sender, Delegate.Combine(l));
|
||||
if (eventHandler == null) return;
|
||||
eventHandler(sender, args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// There is actually no way to discover an event name in c# at the time of raising the event. It is possible
|
||||
/// to get the event name from the handler that is being executed based on the event being raised, however that is not
|
||||
/// what we want in this case. We need to find the event name before it is being raised - you would think that it's possible
|
||||
/// with reflection or anything but that is not the case, the delegate that defines an event has no info attached to it, it
|
||||
/// is literally just an event.
|
||||
///
|
||||
/// So what this does is take the sender and event args objects, looks up all public/static events on the sender that have
|
||||
/// a generic event handler with generic arguments (but only) one, then we match the type of event arguments with the ones
|
||||
/// being passed in. As it turns out, in our services this will work for the majority of our events! In some cases it may not
|
||||
/// work and we'll have to supply a string but hopefully this saves a bit of magic strings.
|
||||
///
|
||||
/// We can also write tests to validate these are all working correctly for all services.
|
||||
/// </summary>
|
||||
internal class EventNameExtractor
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Finds the event name on the sender that matches the args type
|
||||
/// </summary>
|
||||
/// <param name="senderType"></param>
|
||||
/// <param name="argsType"></param>
|
||||
/// <param name="exclude">
|
||||
/// A filter to exclude matched event names, this filter should return true to exclude the event name from being matched
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// null if not found or an ambiguous match
|
||||
/// </returns>
|
||||
public static Attempt<EventNameExtractorResult> FindEvent(Type senderType, Type argsType, Func<string, bool> exclude)
|
||||
{
|
||||
var found = MatchedEventNames.GetOrAdd(new Tuple<Type, Type>(senderType, argsType), tuple =>
|
||||
{
|
||||
var events = CandidateEvents.GetOrAdd(senderType, t =>
|
||||
{
|
||||
return t.GetEvents(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)
|
||||
//we can only look for events handlers with generic types because that is the only
|
||||
// way that we can try to find a matching event based on the arg type passed in
|
||||
.Where(x => x.EventHandlerType.IsGenericType)
|
||||
.Select(x => new EventInfoArgs(x, x.EventHandlerType.GetGenericArguments()))
|
||||
//we are only looking for event handlers that have more than one generic argument
|
||||
.Where(x =>
|
||||
{
|
||||
if (x.GenericArgs.Length == 1) return true;
|
||||
|
||||
//special case for our own TypedEventHandler
|
||||
if (x.EventInfo.EventHandlerType.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>) && x.GenericArgs.Length == 2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.ToArray();
|
||||
});
|
||||
|
||||
return events.Where(x =>
|
||||
{
|
||||
if (x.GenericArgs.Length == 1 && x.GenericArgs[0] == tuple.Item2)
|
||||
return true;
|
||||
|
||||
//special case for our own TypedEventHandler
|
||||
if (x.EventInfo.EventHandlerType.GetGenericTypeDefinition() == typeof(TypedEventHandler<,>)
|
||||
&& x.GenericArgs.Length == 2
|
||||
&& x.GenericArgs[1] == tuple.Item2)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}).Select(x => x.EventInfo.Name).ToArray();
|
||||
});
|
||||
|
||||
var filtered = found.Where(x => exclude(x) == false).ToArray();
|
||||
|
||||
if (filtered.Length == 0)
|
||||
return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.NoneFound));
|
||||
|
||||
if (filtered.Length == 1)
|
||||
return Attempt.Succeed(new EventNameExtractorResult(filtered[0]));
|
||||
|
||||
//there's more than one left so it's ambiguous!
|
||||
return Attempt.Fail(new EventNameExtractorResult(EventNameExtractorError.Ambiguous));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the event name on the sender that matches the args type
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
/// <param name="exclude">
|
||||
/// A filter to exclude matched event names, this filter should return true to exclude the event name from being matched
|
||||
/// </param>
|
||||
/// <returns>
|
||||
/// null if not found or an ambiguous match
|
||||
/// </returns>
|
||||
public static Attempt<EventNameExtractorResult> FindEvent(object sender, object args, Func<string, bool> exclude)
|
||||
{
|
||||
return FindEvent(sender.GetType(), args.GetType(), exclude);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the event is named with an ING name such as "Saving" or "RollingBack"
|
||||
/// </summary>
|
||||
/// <param name="eventName"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool MatchIngNames(string eventName)
|
||||
{
|
||||
var splitter = new Regex(@"(?<!^)(?=[A-Z])");
|
||||
var words = splitter.Split(eventName);
|
||||
if (words.Length == 0)
|
||||
return false;
|
||||
return words[0].EndsWith("ing");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return true if the event is not named with an ING name such as "Saving" or "RollingBack"
|
||||
/// </summary>
|
||||
/// <param name="eventName"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool MatchNonIngNames(string eventName)
|
||||
{
|
||||
var splitter = new Regex(@"(?<!^)(?=[A-Z])");
|
||||
var words = splitter.Split(eventName);
|
||||
if (words.Length == 0)
|
||||
return false;
|
||||
return words[0].EndsWith("ing") == false;
|
||||
}
|
||||
|
||||
private class EventInfoArgs
|
||||
{
|
||||
public EventInfo EventInfo { get; private set; }
|
||||
public Type[] GenericArgs { get; private set; }
|
||||
|
||||
public EventInfoArgs(EventInfo eventInfo, Type[] genericArgs)
|
||||
{
|
||||
EventInfo = eventInfo;
|
||||
GenericArgs = genericArgs;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to cache all candidate events for a given type so we don't re-look them up
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Type, EventInfoArgs[]> CandidateEvents = new ConcurrentDictionary<Type, EventInfoArgs[]>();
|
||||
|
||||
/// <summary>
|
||||
/// Used to cache all matched event names by (sender type + arg type) so we don't re-look them up
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Tuple<Type, Type>, string[]> MatchedEventNames = new ConcurrentDictionary<Tuple<Type, Type>, string[]>();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
internal enum EventNameExtractorError
|
||||
{
|
||||
NoneFound,
|
||||
Ambiguous
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
internal class EventNameExtractorResult
|
||||
{
|
||||
public EventNameExtractorError? Error { get; private set; }
|
||||
public string Name { get; private set; }
|
||||
|
||||
public EventNameExtractorResult(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public EventNameExtractorResult(EventNameExtractorError error)
|
||||
{
|
||||
Error = error;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class ExportEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
|
||||
public class ExportEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<ExportEventArgs<TEntity>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting a single entity instance
|
||||
@@ -48,5 +49,38 @@ namespace Umbraco.Core.Events
|
||||
/// Returns the xml relating to the export event
|
||||
/// </summary>
|
||||
public XElement Xml { get; private set; }
|
||||
|
||||
public bool Equals(ExportEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && Equals(Xml, other.Xml);
|
||||
}
|
||||
|
||||
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((ExportEventArgs<TEntity>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(ExportEventArgs<TEntity> left, ExportEventArgs<TEntity> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ExportEventArgs<TEntity> left, ExportEventArgs<TEntity> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
internal interface IDeletingMediaFilesEventArgs
|
||||
{
|
||||
List<string> MediaFilesToDelete { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public interface IEventDefinition
|
||||
{
|
||||
object Sender { get; }
|
||||
object Args { get; }
|
||||
string EventName { get; }
|
||||
|
||||
void RaiseEvent();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Dispatches events from within a scope.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The name of the event is auto-magically discovered by matching the sender type, args type, and
|
||||
/// eventHandler type. If the match is not unique, then the name parameter must be used to specify the
|
||||
/// name in an explicit way.</para>
|
||||
/// <para>What happens when an event is dispatched depends on the scope settings. It can be anything from
|
||||
/// "trigger immediately" to "just ignore". Refer to the scope documentation for more details.</para>
|
||||
/// </remarks>
|
||||
public interface IEventDispatcher
|
||||
{
|
||||
// not sure about the Dispatch & DispatchCancelable signatures at all for now
|
||||
// nor about the event name thing, etc - but let's keep it like this
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches a cancelable event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <returns>A value indicating whether the cancelable event was cancelled.</returns>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string name = null);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches a cancelable event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <returns>A value indicating whether the cancelable event was cancelled.</returns>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
bool DispatchCancelable<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string name = null)
|
||||
where TArgs : CancellableEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches a cancelable event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <returns>A value indicating whether the cancelable event was cancelled.</returns>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
bool DispatchCancelable<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string name = null)
|
||||
where TArgs : CancellableEventArgs;
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches an event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string name = null);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches an event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
void Dispatch<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string name = null);
|
||||
|
||||
/// <summary>
|
||||
/// Dispatches an event.
|
||||
/// </summary>
|
||||
/// <param name="eventHandler">The event handler.</param>
|
||||
/// <param name="sender">The object that raised the event.</param>
|
||||
/// <param name="args">The event data.</param>
|
||||
/// <param name="name">The optional name of the event.</param>
|
||||
/// <remarks>See general remarks on the interface.</remarks>
|
||||
void Dispatch<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string name = null);
|
||||
|
||||
/// <summary>
|
||||
/// Notifies the dispatcher that the scope is exiting.
|
||||
/// </summary>
|
||||
/// <param name="completed">A value indicating whether the scope completed.</param>
|
||||
void ScopeExit(bool completed);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the collected events.
|
||||
/// </summary>
|
||||
/// <returns>The collected events.</returns>
|
||||
IEnumerable<IEventDefinition> GetEvents(EventDefinitionFilter filter);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class ImportEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
|
||||
public class ImportEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<ImportEventArgs<TEntity>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting an XElement with the xml being imported
|
||||
@@ -46,5 +47,38 @@ namespace Umbraco.Core.Events
|
||||
/// Returns the xml relating to the import event
|
||||
/// </summary>
|
||||
public XElement Xml { get; private set; }
|
||||
|
||||
public bool Equals(ImportEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && Equals(Xml, other.Xml);
|
||||
}
|
||||
|
||||
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((ImportEventArgs<TEntity>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ (Xml != null ? Xml.GetHashCode() : 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(ImportEventArgs<TEntity> left, ImportEventArgs<TEntity> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ImportEventArgs<TEntity> left, ImportEventArgs<TEntity> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Packaging.Models;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
internal class ImportPackageEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
|
||||
internal class ImportPackageEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<ImportPackageEventArgs<TEntity>>
|
||||
{
|
||||
private readonly MetaData _packageMetaData;
|
||||
|
||||
@@ -21,6 +22,44 @@ namespace Umbraco.Core.Events
|
||||
public MetaData PackageMetaData
|
||||
{
|
||||
get { return _packageMetaData; }
|
||||
}
|
||||
|
||||
public IEnumerable<TEntity> InstallationSummary
|
||||
{
|
||||
get { return EventObject; }
|
||||
}
|
||||
|
||||
public bool Equals(ImportPackageEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && _packageMetaData.Equals(other._packageMetaData);
|
||||
}
|
||||
|
||||
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((ImportPackageEventArgs<TEntity>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ _packageMetaData.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(ImportPackageEventArgs<TEntity> left, ImportPackageEventArgs<TEntity> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(ImportPackageEventArgs<TEntity> left, ImportPackageEventArgs<TEntity> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using Umbraco.Core.Persistence.Migrations;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class MigrationEventArgs : CancellableObjectEventArgs<IList<IMigration>>
|
||||
public class MigrationEventArgs : CancellableObjectEventArgs<IList<IMigration>>, IEquatable<MigrationEventArgs>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting multiple migrations that are used in the migration runner
|
||||
@@ -141,5 +141,43 @@ namespace Umbraco.Core.Events
|
||||
public string ProductName { get; private set; }
|
||||
|
||||
internal MigrationContext MigrationContext { get; private set; }
|
||||
|
||||
public bool Equals(MigrationEventArgs other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && ConfiguredSemVersion.Equals(other.ConfiguredSemVersion) && MigrationContext.Equals(other.MigrationContext) && string.Equals(ProductName, other.ProductName) && TargetSemVersion.Equals(other.TargetSemVersion);
|
||||
}
|
||||
|
||||
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((MigrationEventArgs) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = base.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ ConfiguredSemVersion.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ MigrationContext.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ ProductName.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ TargetSemVersion.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(MigrationEventArgs left, MigrationEventArgs right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(MigrationEventArgs left, MigrationEventArgs right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class MoveEventArgs<TEntity> : CancellableObjectEventArgs<TEntity>
|
||||
public class MoveEventArgs<TEntity> : CancellableObjectEventArgs<TEntity>, IEquatable<MoveEventArgs<TEntity>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting a collection of MoveEventInfo objects
|
||||
@@ -123,5 +123,38 @@ namespace Umbraco.Core.Events
|
||||
/// </summary>
|
||||
[Obsolete("Retrieve the ParentId from the MoveInfoCollection property instead")]
|
||||
public int ParentId { get; private set; }
|
||||
|
||||
public bool Equals(MoveEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && MoveInfoCollection.Equals(other.MoveInfoCollection);
|
||||
}
|
||||
|
||||
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((MoveEventArgs<TEntity>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ MoveInfoCollection.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(MoveEventArgs<TEntity> left, MoveEventArgs<TEntity> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(MoveEventArgs<TEntity> left, MoveEventArgs<TEntity> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class MoveEventInfo<TEntity>
|
||||
public class MoveEventInfo<TEntity> : IEquatable<MoveEventInfo<TEntity>>
|
||||
{
|
||||
public MoveEventInfo(TEntity entity, string originalPath, int newParentId)
|
||||
{
|
||||
@@ -12,5 +15,41 @@ namespace Umbraco.Core.Events
|
||||
public TEntity Entity { get; set; }
|
||||
public string OriginalPath { get; set; }
|
||||
public int NewParentId { get; set; }
|
||||
|
||||
public bool Equals(MoveEventInfo<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return EqualityComparer<TEntity>.Default.Equals(Entity, other.Entity) && NewParentId == other.NewParentId && string.Equals(OriginalPath, other.OriginalPath);
|
||||
}
|
||||
|
||||
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((MoveEventInfo<TEntity>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = EqualityComparer<TEntity>.Default.GetHashCode(Entity);
|
||||
hashCode = (hashCode * 397) ^ NewParentId;
|
||||
hashCode = (hashCode * 397) ^ OriginalPath.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(MoveEventInfo<TEntity> left, MoveEventInfo<TEntity> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(MoveEventInfo<TEntity> left, MoveEventInfo<TEntity> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class NewEventArgs<TEntity> : CancellableObjectEventArgs<TEntity>
|
||||
public class NewEventArgs<TEntity> : CancellableObjectEventArgs<TEntity>, IEquatable<NewEventArgs<TEntity>>
|
||||
{
|
||||
|
||||
|
||||
@@ -84,5 +86,42 @@ namespace Umbraco.Core.Events
|
||||
/// Gets or Sets the parent IContent object.
|
||||
/// </summary>
|
||||
public TEntity Parent { get; private set; }
|
||||
|
||||
public bool Equals(NewEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && string.Equals(Alias, other.Alias) && EqualityComparer<TEntity>.Default.Equals(Parent, other.Parent) && ParentId == other.ParentId;
|
||||
}
|
||||
|
||||
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((NewEventArgs<TEntity>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = base.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ Alias.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ EqualityComparer<TEntity>.Default.GetHashCode(Parent);
|
||||
hashCode = (hashCode * 397) ^ ParentId;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(NewEventArgs<TEntity> left, NewEventArgs<TEntity> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(NewEventArgs<TEntity> left, NewEventArgs<TEntity> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// This event manager supports event cancellation and will raise the events as soon as they are tracked, it does not store tracked events
|
||||
/// </summary>
|
||||
internal class PassThroughEventDispatcher : IEventDispatcher
|
||||
{
|
||||
public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public bool DispatchCancelable<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public bool DispatchCancelable<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return;
|
||||
eventHandler(sender, args);
|
||||
}
|
||||
|
||||
public void Dispatch<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return;
|
||||
eventHandler(sender, args);
|
||||
}
|
||||
|
||||
public void Dispatch<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return;
|
||||
eventHandler(sender, args);
|
||||
}
|
||||
|
||||
public IEnumerable<IEventDefinition> GetEvents(EventDefinitionFilter filter)
|
||||
{
|
||||
return Enumerable.Empty<IEventDefinition>();
|
||||
}
|
||||
|
||||
public void ScopeExit(bool completed)
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class PublishEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
|
||||
public class PublishEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>, IEquatable<PublishEventArgs<TEntity>>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructor accepting multiple entities that are used in the publish operation
|
||||
@@ -101,5 +102,38 @@ namespace Umbraco.Core.Events
|
||||
}
|
||||
|
||||
public bool IsAllRepublished { get; private set; }
|
||||
|
||||
public bool Equals(PublishEventArgs<TEntity> other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && IsAllRepublished == other.IsAllRepublished;
|
||||
}
|
||||
|
||||
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((PublishEventArgs<TEntity>) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
return (base.GetHashCode() * 397) ^ IsAllRepublished.GetHashCode();
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(PublishEventArgs<TEntity> left, PublishEventArgs<TEntity> right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(PublishEventArgs<TEntity> left, PublishEventArgs<TEntity> right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public class RecycleBinEventArgs : CancellableEventArgs
|
||||
public class RecycleBinEventArgs : CancellableEventArgs, IEquatable<RecycleBinEventArgs>, IDeletingMediaFilesEventArgs
|
||||
{
|
||||
public RecycleBinEventArgs(Guid nodeObjectType, Dictionary<int, IEnumerable<Property>> allPropertyData, bool emptiedSuccessfully)
|
||||
: base(false)
|
||||
@@ -97,6 +97,8 @@ namespace Umbraco.Core.Events
|
||||
/// </remarks>
|
||||
public List<string> Files { get; private set; }
|
||||
|
||||
public List<string> MediaFilesToDelete { get { return Files; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all property data associated with a content id
|
||||
/// </summary>
|
||||
@@ -122,5 +124,44 @@ namespace Umbraco.Core.Events
|
||||
{
|
||||
get { return NodeObjectType == new Guid(Constants.ObjectTypes.Media); }
|
||||
}
|
||||
|
||||
public bool Equals(RecycleBinEventArgs other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return base.Equals(other) && AllPropertyData.Equals(other.AllPropertyData) && Files.Equals(other.Files) && Ids.Equals(other.Ids) && NodeObjectType.Equals(other.NodeObjectType) && RecycleBinEmptiedSuccessfully == other.RecycleBinEmptiedSuccessfully;
|
||||
}
|
||||
|
||||
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((RecycleBinEventArgs) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
int hashCode = base.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ AllPropertyData.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ Files.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ Ids.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ NodeObjectType.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ RecycleBinEmptiedSuccessfully.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(RecycleBinEventArgs left, RecycleBinEventArgs right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(RecycleBinEventArgs left, RecycleBinEventArgs right)
|
||||
{
|
||||
return !Equals(left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,5 +17,7 @@ namespace Umbraco.Core.Events
|
||||
{
|
||||
get { return EventObject; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -116,5 +116,7 @@ namespace Umbraco.Core.Events
|
||||
{
|
||||
get { return EventObject; }
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using Umbraco.Core.IO;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// This event manager is created for each scope and is aware of if it is nested in an outer scope
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The outer scope is the only scope that can raise events, the inner scope's will defer to the outer scope
|
||||
/// </remarks>
|
||||
internal class ScopeEventDispatcher : ScopeEventDispatcherBase
|
||||
{
|
||||
public ScopeEventDispatcher()
|
||||
: base(true)
|
||||
{ }
|
||||
|
||||
protected override void ScopeExitCompleted()
|
||||
{
|
||||
// fixme - we'd need to de-duplicate events somehow, etc - and the deduplication should be last in wins
|
||||
|
||||
foreach (var e in GetEvents(EventDefinitionFilter.All))
|
||||
{
|
||||
e.RaiseEvent();
|
||||
|
||||
// fixme - not sure I like doing it here - but then where? how?
|
||||
var delete = e.Args as IDeletingMediaFilesEventArgs;
|
||||
if (delete != null && delete.MediaFilesToDelete.Count > 0)
|
||||
MediaFileSystem.DeleteMediaFiles(delete.MediaFilesToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
private MediaFileSystem _mediaFileSystem;
|
||||
|
||||
private MediaFileSystem MediaFileSystem
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_mediaFileSystem != null) return _mediaFileSystem;
|
||||
|
||||
// fixme - insane! reading config goes cross AppDomain and serializes context?
|
||||
using (new SafeCallContext())
|
||||
{
|
||||
return _mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
public abstract class ScopeEventDispatcherBase : IEventDispatcher
|
||||
{
|
||||
private List<IEventDefinition> _events;
|
||||
private readonly bool _raiseCancelable;
|
||||
|
||||
protected ScopeEventDispatcherBase(bool raiseCancelable)
|
||||
{
|
||||
_raiseCancelable = raiseCancelable;
|
||||
}
|
||||
|
||||
private List<IEventDefinition> Events { get { return _events ?? (_events = new List<IEventDefinition>()); } }
|
||||
|
||||
public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
if (_raiseCancelable == false) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public bool DispatchCancelable<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
if (_raiseCancelable == false) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public bool DispatchCancelable<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
|
||||
where TArgs : CancellableEventArgs
|
||||
{
|
||||
if (eventHandler == null) return args.Cancel;
|
||||
if (_raiseCancelable == false) return args.Cancel;
|
||||
eventHandler(sender, args);
|
||||
return args.Cancel;
|
||||
}
|
||||
|
||||
public void Dispatch(EventHandler eventHandler, object sender, EventArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return;
|
||||
Events.Add(new EventDefinition(eventHandler, sender, args, eventName));
|
||||
}
|
||||
|
||||
public void Dispatch<TArgs>(EventHandler<TArgs> eventHandler, object sender, TArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return;
|
||||
Events.Add(new EventDefinition<TArgs>(eventHandler, sender, args, eventName));
|
||||
}
|
||||
|
||||
public void Dispatch<TSender, TArgs>(TypedEventHandler<TSender, TArgs> eventHandler, TSender sender, TArgs args, string eventName = null)
|
||||
{
|
||||
if (eventHandler == null) return;
|
||||
Events.Add(new EventDefinition<TSender, TArgs>(eventHandler, sender, args, eventName));
|
||||
}
|
||||
|
||||
public IEnumerable<IEventDefinition> GetEvents(EventDefinitionFilter filter)
|
||||
{
|
||||
if (_events == null)
|
||||
return Enumerable.Empty<IEventDefinition>();
|
||||
|
||||
switch (filter)
|
||||
{
|
||||
case EventDefinitionFilter.All:
|
||||
return _events;
|
||||
case EventDefinitionFilter.FirstIn:
|
||||
var l1 = new OrderedHashSet<IEventDefinition>();
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l1.Add(e);
|
||||
}
|
||||
return l1;
|
||||
case EventDefinitionFilter.LastIn:
|
||||
var l2 = new OrderedHashSet<IEventDefinition>(keepOldest: false);
|
||||
foreach (var e in _events)
|
||||
{
|
||||
l2.Add(e);
|
||||
}
|
||||
return l2;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("filter", filter, null);
|
||||
}
|
||||
}
|
||||
|
||||
public void ScopeExit(bool completed)
|
||||
{
|
||||
if (_events == null) return;
|
||||
if (completed)
|
||||
ScopeExitCompleted();
|
||||
_events.Clear();
|
||||
}
|
||||
|
||||
protected abstract void ScopeExitCompleted();
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,6 @@ using System;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
[Serializable]
|
||||
[Serializable]
|
||||
public delegate void TypedEventHandler<in TSender, in TEventArgs>(TSender sender, TEventArgs e);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Packaging.Models;
|
||||
|
||||
namespace Umbraco.Core.Events
|
||||
{
|
||||
internal class UninstallPackageEventArgs<TEntity> : CancellableObjectEventArgs<IEnumerable<TEntity>>
|
||||
{
|
||||
private readonly MetaData _packageMetaData;
|
||||
|
||||
public UninstallPackageEventArgs(TEntity eventObject, bool canCancel)
|
||||
: base(new[] { eventObject }, canCancel)
|
||||
{
|
||||
}
|
||||
|
||||
public UninstallPackageEventArgs(TEntity eventObject, MetaData packageMetaData)
|
||||
: base(new[] { eventObject })
|
||||
{
|
||||
_packageMetaData = packageMetaData;
|
||||
}
|
||||
|
||||
public MetaData PackageMetaData
|
||||
{
|
||||
get { return _packageMetaData; }
|
||||
}
|
||||
|
||||
public IEnumerable<TEntity> UninstallationSummary
|
||||
{
|
||||
get { return EventObject; }
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.0
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
|
||||
@@ -5,9 +5,10 @@ using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
{
|
||||
public class FileSystemProviderManager
|
||||
{
|
||||
private readonly FileSystemProvidersSection _config;
|
||||
@@ -40,6 +41,12 @@ namespace Umbraco.Core.IO
|
||||
get { return Instance; }
|
||||
}
|
||||
|
||||
private IScopeProviderInternal ScopeProvider
|
||||
{
|
||||
// fixme - 'course this is bad, but enough for now
|
||||
get { return ApplicationContext.Current == null ? null : ApplicationContext.Current.ScopeProvider as IScopeProviderInternal; }
|
||||
}
|
||||
|
||||
internal FileSystemProviderManager()
|
||||
{
|
||||
_config = (FileSystemProvidersSection) ConfigurationManager.GetSection("umbracoConfiguration/FileSystemProviders");
|
||||
@@ -52,13 +59,13 @@ namespace Umbraco.Core.IO
|
||||
_masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages);
|
||||
_mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews);
|
||||
|
||||
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials");
|
||||
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials");
|
||||
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css");
|
||||
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts");
|
||||
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt");
|
||||
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages");
|
||||
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views");
|
||||
_macroPartialFileSystem = _macroPartialFileSystemWrapper = new ShadowWrapper(_macroPartialFileSystem, "Views/MacroPartials", ScopeProvider);
|
||||
_partialViewsFileSystem = _partialViewsFileSystemWrapper = new ShadowWrapper(_partialViewsFileSystem, "Views/Partials", ScopeProvider);
|
||||
_stylesheetsFileSystem = _stylesheetsFileSystemWrapper = new ShadowWrapper(_stylesheetsFileSystem, "css", ScopeProvider);
|
||||
_scriptsFileSystem = _scriptsFileSystemWrapper = new ShadowWrapper(_scriptsFileSystem, "scripts", ScopeProvider);
|
||||
_xsltFileSystem = _xsltFileSystemWrapper = new ShadowWrapper(_xsltFileSystem, "xslt", ScopeProvider);
|
||||
_masterPagesFileSystem = _masterPagesFileSystemWrapper = new ShadowWrapper(_masterPagesFileSystem, "masterpages", ScopeProvider);
|
||||
_mvcViewsFileSystem = _mvcViewsFileSystemWrapper = new ShadowWrapper(_mvcViewsFileSystem, "Views", ScopeProvider);
|
||||
|
||||
// filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again
|
||||
MediaFileSystem = GetFileSystemProvider<MediaFileSystem>();
|
||||
@@ -92,7 +99,7 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
|
||||
private readonly ConcurrentDictionary<string, ProviderConstructionInfo> _providerLookup = new ConcurrentDictionary<string, ProviderConstructionInfo>();
|
||||
private readonly ConcurrentDictionary<Type, string> _aliases = new ConcurrentDictionary<Type, string>();
|
||||
private readonly ConcurrentDictionary<Type, string> _aliases = new ConcurrentDictionary<Type, string>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets an underlying (non-typed) filesystem supporting a strongly-typed filesystem.
|
||||
@@ -121,7 +128,7 @@ namespace Umbraco.Core.IO
|
||||
|
||||
// find a ctor matching the config parameters
|
||||
var paramCount = providerConfig.Parameters != null ? providerConfig.Parameters.Count : 0;
|
||||
var constructor = providerType.GetConstructors().SingleOrDefault(x
|
||||
var constructor = providerType.GetConstructors().SingleOrDefault(x
|
||||
=> x.GetParameters().Length == paramCount && x.GetParameters().All(y => providerConfig.Parameters.AllKeys.Contains(y.Name)));
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException(string.Format("Type {0} has no ctor matching the {1} configuration parameter(s).", providerType.FullName, paramCount));
|
||||
@@ -129,7 +136,7 @@ namespace Umbraco.Core.IO
|
||||
var parameters = new object[paramCount];
|
||||
if (providerConfig.Parameters != null) // keeps ReSharper happy
|
||||
for (var i = 0; i < paramCount; i++)
|
||||
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
|
||||
parameters[i] = providerConfig.Parameters[providerConfig.Parameters.AllKeys[i]].Value;
|
||||
|
||||
return new ProviderConstructionInfo
|
||||
{
|
||||
@@ -159,7 +166,7 @@ namespace Umbraco.Core.IO
|
||||
var alias = _aliases.GetOrAdd(typeof (TFileSystem), fsType =>
|
||||
{
|
||||
// validate the ctor
|
||||
var constructor = fsType.GetConstructors().SingleOrDefault(x
|
||||
var constructor = fsType.GetConstructors().SingleOrDefault(x
|
||||
=> x.GetParameters().Length == 1 && TypeHelper.IsTypeAssignableFrom<IFileSystem>(x.GetParameters().Single().ParameterType));
|
||||
if (constructor == null)
|
||||
throw new InvalidOperationException("Type " + fsType.FullName + " must inherit from FileSystemWrapper and have a constructor that accepts one parameter of type " + typeof(IFileSystem).FullName + ".");
|
||||
@@ -176,8 +183,8 @@ namespace Umbraco.Core.IO
|
||||
// so we are double-wrapping here
|
||||
// could be optimized by having FileSystemWrapper inherit from ShadowWrapper, maybe
|
||||
var innerFs = GetUnderlyingFileSystemProvider(alias);
|
||||
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias);
|
||||
var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), innerFs);
|
||||
var shadowWrapper = new ShadowWrapper(innerFs, "typed/" + alias, ScopeProvider);
|
||||
var fs = (TFileSystem) Activator.CreateInstance(typeof (TFileSystem), shadowWrapper);
|
||||
_wrappers.Add(shadowWrapper); // keeping a weak reference to the wrapper
|
||||
return fs;
|
||||
}
|
||||
@@ -186,25 +193,7 @@ namespace Umbraco.Core.IO
|
||||
|
||||
#region Shadow
|
||||
|
||||
// note
|
||||
// shadowing is thread-safe, but entering and exiting shadow mode is not, and there is only one
|
||||
// global shadow for the entire application, so great care should be taken to ensure that the
|
||||
// application is *not* doing anything else when using a shadow.
|
||||
// shadow applies to well-known filesystems *only* - at the moment, any other filesystem that would
|
||||
// be created directly (via ctor) or via GetFileSystemProvider<T> is *not* shadowed.
|
||||
|
||||
// shadow must be enabled in an app event handler before anything else ie before any filesystem
|
||||
// is actually created and used - after, it is too late - enabling shadow has a neglictible perfs
|
||||
// impact.
|
||||
// NO! by the time an app event handler is instanciated it is already too late, see note in ctor.
|
||||
//internal void EnableShadow()
|
||||
//{
|
||||
// if (_mvcViewsFileSystem != null) // test one of the fs...
|
||||
// throw new InvalidOperationException("Cannot enable shadow once filesystems have been created.");
|
||||
// _shadowEnabled = true;
|
||||
//}
|
||||
|
||||
public ICompletable Shadow(Guid id)
|
||||
internal ICompletable Shadow(Guid id)
|
||||
{
|
||||
var typed = _wrappers.ToArray();
|
||||
var wrappers = new ShadowWrapper[typed.Length + 7];
|
||||
@@ -218,7 +207,7 @@ namespace Umbraco.Core.IO
|
||||
wrappers[i++] = _masterPagesFileSystemWrapper;
|
||||
wrappers[i] = _mvcViewsFileSystemWrapper;
|
||||
|
||||
return ShadowFileSystemsScope.CreateScope(id, wrappers);
|
||||
return new ShadowFileSystems(id, wrappers);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -109,5 +109,24 @@ namespace Umbraco.Core.IO
|
||||
var wrapped2 = Wrapped as IFileSystem2;
|
||||
return wrapped2 == null ? Wrapped.GetSize(path) : wrapped2.GetSize(path);
|
||||
}
|
||||
|
||||
// explicitely implementing - not breaking
|
||||
bool IFileSystem2.CanAddPhysical
|
||||
{
|
||||
get
|
||||
{
|
||||
var wrapped2 = Wrapped as IFileSystem2;
|
||||
return wrapped2 != null && wrapped2.CanAddPhysical;
|
||||
}
|
||||
}
|
||||
|
||||
// explicitely implementing - not breaking
|
||||
void IFileSystem2.AddFile(string path, string physicalPath, bool overrideIfExists, bool copy)
|
||||
{
|
||||
var wrapped2 = Wrapped as IFileSystem2;
|
||||
if (wrapped2 == null)
|
||||
throw new NotSupportedException();
|
||||
wrapped2.AddFile(path, physicalPath, overrideIfExists, copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,10 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
long GetSize(string path);
|
||||
|
||||
bool CanAddPhysical { get; }
|
||||
|
||||
void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false);
|
||||
|
||||
// TODO: implement these
|
||||
//
|
||||
//void CreateDirectory(string path);
|
||||
|
||||
@@ -4,11 +4,11 @@ using System.Globalization;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
using System.Configuration;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
@@ -351,7 +351,54 @@ namespace Umbraco.Core.IO
|
||||
writer.Write(contents);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given path is a full path including drive letter
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
// From: http://stackoverflow.com/a/35046453/5018
|
||||
internal static bool IsFullPath(this string path)
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(path) == false
|
||||
&& path.IndexOfAny(Path.GetInvalidPathChars().ToArray()) == -1
|
||||
&& Path.IsPathRooted(path)
|
||||
&& Path.GetPathRoot(path).Equals(Path.DirectorySeparatorChar.ToString(), StringComparison.Ordinal) == false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get properly formatted relative path from an existing absolute or relative path
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
internal static string GetRelativePath(this string path)
|
||||
{
|
||||
if (path.IsFullPath())
|
||||
{
|
||||
var rootDirectory = GetRootDirectorySafe();
|
||||
var relativePath = path.ToLowerInvariant().Replace(rootDirectory.ToLowerInvariant(), string.Empty);
|
||||
path = relativePath;
|
||||
}
|
||||
|
||||
return path.EnsurePathIsApplicationRootPrefixed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures that a path has `~/` as prefix
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
internal static string EnsurePathIsApplicationRootPrefixed(this string path)
|
||||
{
|
||||
if (path.StartsWith("~/"))
|
||||
return path;
|
||||
if (path.StartsWith("/") == false && path.StartsWith("\\") == false)
|
||||
path = string.Format("/{0}", path);
|
||||
if (path.StartsWith("~") == false)
|
||||
path = string.Format("~{0}", path);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
@@ -24,6 +25,7 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
private readonly IContentSection _contentConfig;
|
||||
private readonly UploadAutoFillProperties _uploadAutoFillProperties;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
private readonly object _folderCounterLock = new object();
|
||||
private long _folderCounter;
|
||||
@@ -42,6 +44,7 @@ namespace Umbraco.Core.IO
|
||||
public MediaFileSystem(IFileSystem wrapped, IContentSection contentConfig, ILogger logger)
|
||||
: base(wrapped)
|
||||
{
|
||||
_logger = logger;
|
||||
_contentConfig = contentConfig;
|
||||
_uploadAutoFillProperties = new UploadAutoFillProperties(this, logger, contentConfig);
|
||||
}
|
||||
@@ -99,7 +102,7 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
else
|
||||
{
|
||||
// new scheme: path is "<cuid>-<puid>/<filename>" OR "<cuid>-<puid>-<filename>"
|
||||
// new scheme: path is "<xuid>/<filename>" where xuid is a combination of cuid and puid
|
||||
// default media filesystem maps to "~/media/<filepath>"
|
||||
// assumes that cuid and puid keys can be trusted - and that a single property type
|
||||
// for a single content cannot store two different files with the same name
|
||||
@@ -435,6 +438,64 @@ namespace Umbraco.Core.IO
|
||||
}
|
||||
}
|
||||
|
||||
public void DeleteMediaFiles(IEnumerable<string> files)
|
||||
{
|
||||
files = files.Distinct();
|
||||
|
||||
Parallel.ForEach(files, file =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (file.IsNullOrWhiteSpace()) return;
|
||||
|
||||
if (FileExists(file) == false) return;
|
||||
DeleteFile(file, true);
|
||||
|
||||
if (UseTheNewMediaPathScheme == false)
|
||||
{
|
||||
// old scheme: filepath is "<int>/<filename>" OR "<int>-<filename>"
|
||||
// remove the directory if any
|
||||
var dir = Path.GetDirectoryName(file);
|
||||
if (string.IsNullOrWhiteSpace(dir) == false)
|
||||
DeleteDirectory(dir, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
// new scheme: path is "<xuid>/<filename>" where xuid is a combination of cuid and puid
|
||||
// remove the directory
|
||||
var dir = Path.GetDirectoryName(file);
|
||||
DeleteDirectory(dir, true);
|
||||
}
|
||||
|
||||
// I don't even understand...
|
||||
/*
|
||||
|
||||
var relativeFilePath = GetRelativePath(file); // fixme - should be relative already
|
||||
if (FileExists(relativeFilePath) == false) return;
|
||||
|
||||
var parentDirectory = Path.GetDirectoryName(relativeFilePath);
|
||||
|
||||
// don't want to delete the media folder if not using directories.
|
||||
if (_contentSection.UploadAllowDirectories && parentDirectory != GetRelativePath("/"))
|
||||
{
|
||||
//issue U4-771: if there is a parent directory the recursive parameter should be true
|
||||
DeleteDirectory(parentDirectory, string.IsNullOrEmpty(parentDirectory) == false);
|
||||
}
|
||||
else
|
||||
{
|
||||
DeleteFile(file, true);
|
||||
}
|
||||
|
||||
*/
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.Error<MediaFileSystem>("Failed to delete attached file \"" + file + "\".", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region GenerateThumbnails
|
||||
|
||||
@@ -364,6 +364,29 @@ namespace Umbraco.Core.IO
|
||||
return file.Exists ? file.Length : -1;
|
||||
}
|
||||
|
||||
public bool CanAddPhysical { get { return true; } }
|
||||
|
||||
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
|
||||
{
|
||||
var fullPath = GetFullPath(path);
|
||||
|
||||
if (File.Exists(fullPath))
|
||||
{
|
||||
if (overrideIfExists == false)
|
||||
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
File.Delete(fullPath);
|
||||
}
|
||||
|
||||
var directory = Path.GetDirectoryName(fullPath);
|
||||
if (directory == null) throw new InvalidOperationException("Could not get directory.");
|
||||
Directory.CreateDirectory(directory); // ensure it exists
|
||||
|
||||
if (copy)
|
||||
File.Copy(physicalPath, fullPath);
|
||||
else
|
||||
File.Move(physicalPath, fullPath);
|
||||
}
|
||||
|
||||
#region Helper Methods
|
||||
|
||||
protected virtual void EnsureDirectory(string path)
|
||||
|
||||
@@ -33,8 +33,16 @@ namespace Umbraco.Core.IO
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = _sfs.OpenFile(kvp.Key))
|
||||
_fs.AddFile(kvp.Key, stream, true);
|
||||
var fs2 = _fs as IFileSystem2;
|
||||
if (fs2 != null && fs2.CanAddPhysical)
|
||||
{
|
||||
fs2.AddFile(kvp.Key, _sfs.GetFullPath(kvp.Key)); // overwrite, move
|
||||
}
|
||||
else
|
||||
{
|
||||
using (var stream = _sfs.OpenFile(kvp.Key))
|
||||
_fs.AddFile(kvp.Key, stream, true);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -282,5 +290,36 @@ namespace Umbraco.Core.IO
|
||||
if (sf.IsDelete || sf.IsDir) throw new InvalidOperationException("Invalid path.");
|
||||
return _sfs.GetSize(path);
|
||||
}
|
||||
|
||||
public bool CanAddPhysical { get { return true; } }
|
||||
|
||||
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
|
||||
{
|
||||
ShadowNode sf;
|
||||
var normPath = NormPath(path);
|
||||
if (Nodes.TryGetValue(normPath, out sf) && sf.IsExist && (sf.IsDir || overrideIfExists == false))
|
||||
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
||||
|
||||
var parts = normPath.Split('/');
|
||||
for (var i = 0; i < parts.Length - 1; i++)
|
||||
{
|
||||
var dirPath = string.Join("/", parts.Take(i + 1));
|
||||
ShadowNode sd;
|
||||
if (Nodes.TryGetValue(dirPath, out sd))
|
||||
{
|
||||
if (sd.IsFile) throw new InvalidOperationException("Invalid path.");
|
||||
if (sd.IsDelete) Nodes[dirPath] = new ShadowNode(false, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_fs.DirectoryExists(dirPath)) continue;
|
||||
if (_fs.FileExists(dirPath)) throw new InvalidOperationException("Invalid path.");
|
||||
Nodes[dirPath] = new ShadowNode(false, true);
|
||||
}
|
||||
}
|
||||
|
||||
_sfs.AddFile(path, physicalPath, overrideIfExists, copy);
|
||||
Nodes[normPath] = new ShadowNode(false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ShadowFileSystems : ICompletable
|
||||
{
|
||||
// note: taking a reference to the _manager instead of using manager.Current
|
||||
// to avoid using Current everywhere but really, we support only 1 scope at
|
||||
// a time, not multiple scopes in case of multiple managers (not supported)
|
||||
|
||||
private static readonly object Locker = new object();
|
||||
private static Guid _currentId = Guid.Empty;
|
||||
private readonly Guid _id;
|
||||
private readonly ShadowWrapper[] _wrappers;
|
||||
private bool _completed;
|
||||
|
||||
public ShadowFileSystems(Guid id, ShadowWrapper[] wrappers)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (_currentId != Guid.Empty)
|
||||
throw new InvalidOperationException("Already shadowing.");
|
||||
_currentId = id;
|
||||
|
||||
LogHelper.Debug<ShadowFileSystems>("Shadow " + id + ".");
|
||||
_id = id;
|
||||
_wrappers = wrappers;
|
||||
foreach (var wrapper in _wrappers)
|
||||
wrapper.Shadow(id);
|
||||
}
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
_completed = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystems>("UnShadow " + _id + " (" + (_completed ? "complete" : "abort") + ").");
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var wrapper in _wrappers)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this may throw an AggregateException if some of the changes could not be applied
|
||||
wrapper.UnShadow(_completed);
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
exceptions.Add(ae);
|
||||
}
|
||||
}
|
||||
|
||||
_currentId = Guid.Empty;
|
||||
|
||||
if (exceptions.Count > 0)
|
||||
throw new AggregateException(_completed ? "Failed to apply all changes (see exceptions)." : "Failed to abort (see exceptions).", exceptions);
|
||||
}
|
||||
}
|
||||
|
||||
// for tests
|
||||
internal static void ResetId()
|
||||
{
|
||||
_currentId = Guid.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ShadowFileSystemsScope : ICompletable
|
||||
{
|
||||
// note: taking a reference to the _manager instead of using manager.Current
|
||||
// to avoid using Current everywhere but really, we support only 1 scope at
|
||||
// a time, not multiple scopes in case of multiple managers (not supported)
|
||||
|
||||
private const string ItemKey = "Umbraco.Core.IO.ShadowFileSystemsScope";
|
||||
private static readonly object Locker = new object();
|
||||
private readonly Guid _id;
|
||||
private readonly ShadowWrapper[] _wrappers;
|
||||
|
||||
static ShadowFileSystemsScope()
|
||||
{
|
||||
SafeCallContext.Register(
|
||||
() =>
|
||||
{
|
||||
var scope = CallContext.LogicalGetData(ItemKey);
|
||||
CallContext.FreeNamedDataSlot(ItemKey);
|
||||
return scope;
|
||||
},
|
||||
o =>
|
||||
{
|
||||
if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException();
|
||||
if (o != null) CallContext.LogicalSetData(ItemKey, o);
|
||||
});
|
||||
}
|
||||
|
||||
private ShadowFileSystemsScope(Guid id, ShadowWrapper[] wrappers)
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystemsScope>("Shadow " + id + ".");
|
||||
_id = id;
|
||||
_wrappers = wrappers;
|
||||
foreach (var wrapper in _wrappers)
|
||||
wrapper.Shadow(id);
|
||||
}
|
||||
|
||||
// internal for tests + FileSystemProviderManager
|
||||
// do NOT use otherwise
|
||||
internal static ShadowFileSystemsScope CreateScope(Guid id, ShadowWrapper[] wrappers)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (CallContext.LogicalGetData(ItemKey) != null) throw new InvalidOperationException("Already shadowing.");
|
||||
CallContext.LogicalSetData(ItemKey, ItemKey); // value does not matter
|
||||
}
|
||||
return new ShadowFileSystemsScope(id, wrappers);
|
||||
}
|
||||
|
||||
internal static bool InScope
|
||||
{
|
||||
get { return NoScope == false; }
|
||||
}
|
||||
|
||||
internal static bool NoScope
|
||||
{
|
||||
get { return CallContext.LogicalGetData(ItemKey) == null; }
|
||||
}
|
||||
|
||||
public void Complete()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystemsScope>("UnShadow " + _id + " (complete).");
|
||||
|
||||
var exceptions = new List<Exception>();
|
||||
foreach (var wrapper in _wrappers)
|
||||
{
|
||||
try
|
||||
{
|
||||
// this may throw an AggregateException if some of the changes could not be applied
|
||||
wrapper.UnShadow(true);
|
||||
}
|
||||
catch (AggregateException ae)
|
||||
{
|
||||
exceptions.Add(ae);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions.Count > 0)
|
||||
throw new AggregateException("Failed to apply all changes (see exceptions).", exceptions);
|
||||
|
||||
// last, & *only* if successful (otherwise we'll unshadow & cleanup as best as we can)
|
||||
CallContext.FreeNamedDataSlot(ItemKey);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
if (CallContext.LogicalGetData(ItemKey) == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
LogHelper.Debug<ShadowFileSystemsScope>("UnShadow " + _id + " (abort)");
|
||||
foreach (var wrapper in _wrappers)
|
||||
wrapper.UnShadow(false); // should not throw
|
||||
}
|
||||
finally
|
||||
{
|
||||
// last, & always
|
||||
CallContext.FreeNamedDataSlot(ItemKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,23 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.IO
|
||||
{
|
||||
internal class ShadowWrapper : IFileSystem2
|
||||
{
|
||||
private readonly IScopeProviderInternal _scopeProvider;
|
||||
private readonly IFileSystem _innerFileSystem;
|
||||
private readonly string _shadowPath;
|
||||
private ShadowFileSystem _shadowFileSystem;
|
||||
private string _shadowDir;
|
||||
|
||||
public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath)
|
||||
public ShadowWrapper(IFileSystem innerFileSystem, string shadowPath, IScopeProviderInternal scopeProvider)
|
||||
{
|
||||
_innerFileSystem = innerFileSystem;
|
||||
_shadowPath = shadowPath;
|
||||
_scopeProvider = scopeProvider;
|
||||
}
|
||||
|
||||
internal void Shadow(Guid id)
|
||||
@@ -62,7 +65,14 @@ namespace Umbraco.Core.IO
|
||||
|
||||
private IFileSystem FileSystem
|
||||
{
|
||||
get { return ShadowFileSystemsScope.NoScope ? _innerFileSystem : _shadowFileSystem; }
|
||||
get
|
||||
{
|
||||
var isScoped = _scopeProvider != null && _scopeProvider.AmbientScope != null && _scopeProvider.AmbientScope.ScopedFileSystems;
|
||||
|
||||
return isScoped
|
||||
? _shadowFileSystem
|
||||
: _innerFileSystem;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<string> GetDirectories(string path)
|
||||
@@ -154,5 +164,22 @@ namespace Umbraco.Core.IO
|
||||
var filesystem2 = filesystem as IFileSystem2;
|
||||
return filesystem2 == null ? filesystem.GetSize(path) : filesystem2.GetSize(path);
|
||||
}
|
||||
|
||||
public bool CanAddPhysical
|
||||
{
|
||||
get
|
||||
{
|
||||
var fileSystem2 = FileSystem as IFileSystem2;
|
||||
return fileSystem2 != null && fileSystem2.CanAddPhysical;
|
||||
}
|
||||
}
|
||||
|
||||
public void AddFile(string path, string physicalPath, bool overrideIfExists = true, bool copy = false)
|
||||
{
|
||||
var fileSystem2 = FileSystem as IFileSystem2;
|
||||
if (fileSystem2 == null)
|
||||
throw new NotSupportedException();
|
||||
fileSystem2.AddFile(path, physicalPath, overrideIfExists, copy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -400,7 +400,7 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Set property values by alias with an annonymous object
|
||||
/// </summary>
|
||||
public static void PropertyValues(this IContent content, object value)
|
||||
public static void PropertyValues(this IContentBase content, object value)
|
||||
{
|
||||
if (value == null)
|
||||
throw new Exception("No properties has been passed in");
|
||||
|
||||
@@ -1,32 +1,72 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
//fixme: This whole thing needs to go, it's super hacky and doens't need to exist in the first place
|
||||
internal static class ContentTypeExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Get all descendant content types
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="contentTypeService"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<IContentTypeBase> Descendants(this IContentTypeBase contentType)
|
||||
public static IEnumerable<IContentTypeBase> Descendants(this IContentTypeBase contentType, IContentTypeService contentTypeService)
|
||||
{
|
||||
var contentTypeService = ApplicationContext.Current.Services.ContentTypeService;
|
||||
var descendants = contentTypeService.GetContentTypeChildren(contentType.Id)
|
||||
.SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id));
|
||||
return descendants;
|
||||
if (contentType is IContentType)
|
||||
{
|
||||
var descendants = contentTypeService.GetContentTypeChildren(contentType.Id)
|
||||
.SelectRecursive(type => contentTypeService.GetContentTypeChildren(type.Id));
|
||||
return descendants;
|
||||
}
|
||||
|
||||
if (contentType is IMediaType)
|
||||
{
|
||||
var descendants = contentTypeService.GetMediaTypeChildren(contentType.Id)
|
||||
.SelectRecursive(type => contentTypeService.GetMediaTypeChildren(type.Id));
|
||||
return descendants;
|
||||
}
|
||||
|
||||
throw new NotSupportedException("The content type must be " + typeof(IContentType) + " or " + typeof(IMediaType));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all descendant content types
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="contentTypeService"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<IContentTypeBase> Descendants(this IContentTypeBase contentType, ContentTypeServiceBase contentTypeService)
|
||||
{
|
||||
var cService = contentTypeService as IContentTypeService;
|
||||
|
||||
return cService == null ? Enumerable.Empty<IContentTypeBase>() : contentType.Descendants(cService);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all descendant and self content types
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="contentTypeService"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<IContentTypeBase> DescendantsAndSelf(this IContentTypeBase contentType)
|
||||
public static IEnumerable<IContentTypeBase> DescendantsAndSelf(this IContentTypeBase contentType, IContentTypeService contentTypeService)
|
||||
{
|
||||
var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants());
|
||||
var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants(contentTypeService));
|
||||
return descendantsAndSelf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all descendant and self content types
|
||||
/// </summary>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="contentTypeService"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<IContentTypeBase> DescendantsAndSelf(this IContentTypeBase contentType, ContentTypeServiceBase contentTypeService)
|
||||
{
|
||||
var descendantsAndSelf = new[] { contentType }.Concat(contentType.Descendants(contentTypeService));
|
||||
return descendantsAndSelf;
|
||||
}
|
||||
|
||||
|
||||
@@ -106,12 +106,13 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
/// <param name="propertyTypeAlias">The property type alias.</param>
|
||||
/// <param name="dataTypeDefinitionId">The datatype definition identifier.</param>
|
||||
/// <param name="propertyEditorAlias">The property editor alias.</param>
|
||||
/// <param name="initConverters">Generally used only for testing, in production this will always be true</param>
|
||||
/// <remarks>
|
||||
/// <para>The new published property type does not belong to a published content type.</para>
|
||||
/// <para>The values of <paramref name="dataTypeDefinitionId"/> and <paramref name="propertyEditorAlias"/> are
|
||||
/// assumed to be valid and consistent.</para>
|
||||
/// </remarks>
|
||||
internal PublishedPropertyType(string propertyTypeAlias, int dataTypeDefinitionId, string propertyEditorAlias)
|
||||
internal PublishedPropertyType(string propertyTypeAlias, int dataTypeDefinitionId, string propertyEditorAlias, bool initConverters = true)
|
||||
{
|
||||
// ContentType
|
||||
// - in unit tests, to be set by PublishedContentType when creating it
|
||||
@@ -122,7 +123,8 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
DataTypeId = dataTypeDefinitionId;
|
||||
PropertyEditorAlias = propertyEditorAlias;
|
||||
|
||||
InitializeConverters();
|
||||
if (initConverters)
|
||||
InitializeConverters();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -22,6 +22,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
[Column("LoginName")]
|
||||
[Length(1000)]
|
||||
[Constraint(Default = "''")]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_cmsMember_LoginName")]
|
||||
public string LoginName { get; set; }
|
||||
|
||||
[Column("Password")]
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
|
||||
[Column("path")]
|
||||
[Length(150)]
|
||||
[Index(IndexTypes.NonClustered, Name = "IX_umbracoNodePath")]
|
||||
public string Path { get; set; }
|
||||
|
||||
[Column("sortOrder")]
|
||||
|
||||
@@ -16,6 +16,7 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
|
||||
[Column("parentId")]
|
||||
[ForeignKey(typeof(NodeDto), Name = "FK_umbracoRelation_umbracoNode")]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelation_parentChildType", ForColumns = "parentId,childId,relType")]
|
||||
public int ParentId { get; set; }
|
||||
|
||||
[Column("childId")]
|
||||
|
||||
@@ -29,11 +29,13 @@ namespace Umbraco.Core.Models.Rdbms
|
||||
public Guid ChildObjectType { get; set; }
|
||||
|
||||
[Column("name")]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[Column("alias")]
|
||||
[NullSetting(NullSetting = NullSettings.Null)]
|
||||
[Length(100)]
|
||||
[Index(IndexTypes.UniqueNonClustered, Name = "IX_umbracoRelationType_alias")]
|
||||
public string Alias { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -153,17 +153,17 @@ namespace Umbraco.Core.Models
|
||||
FormsForm,
|
||||
|
||||
/// <summary>
|
||||
/// Forms Workflow
|
||||
/// Forms PreValue
|
||||
/// </summary>
|
||||
[UmbracoObjectType(Constants.ObjectTypes.FormsWorkflow)]
|
||||
[FriendlyName("Workflow")]
|
||||
FormsWorkflow,
|
||||
[UmbracoObjectType(Constants.ObjectTypes.FormsPreValue)]
|
||||
[FriendlyName("PreValue")]
|
||||
FormsPreValue,
|
||||
|
||||
/// <summary>
|
||||
/// Forms Record
|
||||
/// Forms DataSource
|
||||
/// </summary>
|
||||
[UmbracoObjectType(Constants.ObjectTypes.FormsRecord)]
|
||||
[FriendlyName("Record")]
|
||||
FormsRecord
|
||||
[UmbracoObjectType(Constants.ObjectTypes.FormsDataSource)]
|
||||
[FriendlyName("DataSource")]
|
||||
FormsDataSource
|
||||
}
|
||||
}
|
||||
@@ -647,5 +647,10 @@ namespace Umbraco.Core
|
||||
return "[GetPropertyValueException]";
|
||||
}
|
||||
}
|
||||
|
||||
internal static Guid AsGuid(this object value)
|
||||
{
|
||||
return value is Guid ? (Guid) value : Guid.Empty;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom collection similar to HashSet{T} which only contains unique items, however this collection keeps items in order
|
||||
/// and is customizable to keep the newest or oldest equatable item
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
internal class OrderedHashSet<T> : KeyedCollection<T, T>
|
||||
{
|
||||
private readonly bool _keepOldest;
|
||||
|
||||
public OrderedHashSet(bool keepOldest = true)
|
||||
{
|
||||
_keepOldest = keepOldest;
|
||||
}
|
||||
|
||||
protected override void InsertItem(int index, T item)
|
||||
{
|
||||
if (Dictionary == null)
|
||||
{
|
||||
base.InsertItem(index, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
var exists = Dictionary.ContainsKey(item);
|
||||
|
||||
//if we want to keep the newest, then we need to remove the old item and add the new one
|
||||
if (exists == false)
|
||||
{
|
||||
base.InsertItem(index, item);
|
||||
}
|
||||
else if(_keepOldest == false)
|
||||
{
|
||||
if (Remove(item))
|
||||
{
|
||||
index--;
|
||||
}
|
||||
base.InsertItem(index, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override T GetKeyForItem(T item)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Packaging.Models
|
||||
{
|
||||
[Serializable]
|
||||
[DataContract(IsReference = true)]
|
||||
internal class UninstallationSummary
|
||||
{
|
||||
public MetaData MetaData { get; set; }
|
||||
public IEnumerable<IDataTypeDefinition> DataTypesUninstalled { get; set; }
|
||||
public IEnumerable<ILanguage> LanguagesUninstalled { get; set; }
|
||||
public IEnumerable<IDictionaryItem> DictionaryItemsUninstalled { get; set; }
|
||||
public IEnumerable<IMacro> MacrosUninstalled { get; set; }
|
||||
public IEnumerable<string> FilesUninstalled { get; set; }
|
||||
public IEnumerable<ITemplate> TemplatesUninstalled { get; set; }
|
||||
public IEnumerable<IContentType> ContentTypesUninstalled { get; set; }
|
||||
public IEnumerable<IFile> StylesheetsUninstalled { get; set; }
|
||||
public IEnumerable<IContent> ContentUninstalled { get; set; }
|
||||
public bool PackageUninstalled { get; set; }
|
||||
}
|
||||
|
||||
internal static class UninstallationSummaryExtentions
|
||||
{
|
||||
public static UninstallationSummary InitEmpty(this UninstallationSummary summary)
|
||||
{
|
||||
summary.ContentUninstalled = new List<IContent>();
|
||||
summary.ContentTypesUninstalled = new List<IContentType>();
|
||||
summary.DataTypesUninstalled = new List<IDataTypeDefinition>();
|
||||
summary.DictionaryItemsUninstalled = new List<IDictionaryItem>();
|
||||
summary.FilesUninstalled = new List<string>();
|
||||
summary.LanguagesUninstalled = new List<ILanguage>();
|
||||
summary.MacrosUninstalled = new List<IMacro>();
|
||||
summary.MetaData = new MetaData();
|
||||
summary.TemplatesUninstalled = new List<ITemplate>();
|
||||
summary.PackageUninstalled = false;
|
||||
return summary;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Remoting.Messaging;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
@@ -14,30 +12,17 @@ namespace Umbraco.Core.Persistence
|
||||
/// it will create one per context, otherwise it will be a global singleton object which is NOT thread safe
|
||||
/// since we need (at least) a new instance of the database object per thread.
|
||||
/// </remarks>
|
||||
internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory
|
||||
internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory2
|
||||
{
|
||||
private readonly string _connectionStringName;
|
||||
private readonly ILogger _logger;
|
||||
public string ConnectionString { get; private set; }
|
||||
public string ProviderName { get; private set; }
|
||||
|
||||
// NO! see notes in v8 HybridAccessorBase
|
||||
//[ThreadStatic]
|
||||
//private static volatile UmbracoDatabase _nonHttpInstance;
|
||||
//private static readonly object Locker = new object();
|
||||
|
||||
private const string ItemKey = "Umbraco.Core.Persistence.DefaultDatabaseFactory";
|
||||
|
||||
private static UmbracoDatabase NonContextValue
|
||||
{
|
||||
get { return (UmbracoDatabase) CallContext.LogicalGetData(ItemKey); }
|
||||
set
|
||||
{
|
||||
if (value == null) CallContext.FreeNamedDataSlot(ItemKey);
|
||||
else CallContext.LogicalSetData(ItemKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly object Locker = new object();
|
||||
// bwc imposes a weird x-dependency between database factory and scope provider...
|
||||
public IScopeProviderInternal ScopeProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor accepting custom connection string
|
||||
@@ -76,251 +61,26 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
public UmbracoDatabase CreateDatabase()
|
||||
{
|
||||
UmbracoDatabase database;
|
||||
|
||||
// gets or creates a database, using either the call context (if no http context) or
|
||||
// the current request context (http context) to store it. once done using the database,
|
||||
// it should be disposed - which will remove it from whatever context it is currently
|
||||
// stored in. this is automatic with http context because UmbracoDatabase implements
|
||||
// IDisposeOnRequestEnd, but NOT with call context.
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
database = NonContextValue;
|
||||
if (database == null)
|
||||
{
|
||||
lock (Locker)
|
||||
{
|
||||
database = NonContextValue;
|
||||
if (database == null)
|
||||
{
|
||||
database = CreateDatabaseInstance(ContextOwner.CallContext);
|
||||
NonContextValue = database;
|
||||
}
|
||||
#if DEBUG_DATABASES
|
||||
else
|
||||
{
|
||||
Log("Get lcc", database);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#if DEBUG_DATABASES
|
||||
else
|
||||
{
|
||||
Log("Get lcc", database);
|
||||
}
|
||||
#endif
|
||||
return database;
|
||||
}
|
||||
|
||||
if (HttpContext.Current.Items.Contains(typeof (DefaultDatabaseFactory)) == false)
|
||||
{
|
||||
database = CreateDatabaseInstance(ContextOwner.HttpContext);
|
||||
HttpContext.Current.Items.Add(typeof (DefaultDatabaseFactory), database);
|
||||
}
|
||||
else
|
||||
{
|
||||
database = (UmbracoDatabase) HttpContext.Current.Items[typeof(DefaultDatabaseFactory)];
|
||||
#if DEBUG_DATABASES
|
||||
Log("Get ctx", database);
|
||||
#endif
|
||||
}
|
||||
|
||||
return database;
|
||||
return ScopeProvider.GetAmbientOrNoScope().Database;
|
||||
}
|
||||
|
||||
// called by UmbracoDatabase when disposed, so that the factory can de-list it from context
|
||||
internal void OnDispose(UmbracoDatabase disposing)
|
||||
public UmbracoDatabase CreateNewDatabase()
|
||||
{
|
||||
var value = disposing;
|
||||
switch (disposing.ContextOwner)
|
||||
{
|
||||
case ContextOwner.CallContext:
|
||||
value = NonContextValue;
|
||||
break;
|
||||
case ContextOwner.HttpContext:
|
||||
value = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
|
||||
break;
|
||||
}
|
||||
return CreateDatabaseInstance();
|
||||
|
||||
if (value != null && value.InstanceId != disposing.InstanceId) throw new Exception("panic: wrong db.");
|
||||
}
|
||||
|
||||
switch (disposing.ContextOwner)
|
||||
{
|
||||
case ContextOwner.CallContext:
|
||||
NonContextValue = null;
|
||||
#if DEBUG_DATABASES
|
||||
Log("Clr lcc", disposing);
|
||||
#endif
|
||||
break;
|
||||
case ContextOwner.HttpContext:
|
||||
HttpContext.Current.Items.Remove(typeof(DefaultDatabaseFactory));
|
||||
#if DEBUG_DATABASES
|
||||
Log("Clr ctx", disposing);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
disposing.ContextOwner = ContextOwner.None;
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
_databases.Remove(value);
|
||||
#endif
|
||||
}
|
||||
|
||||
#if DEBUG_DATABASES
|
||||
// helps identifying when non-httpContext databases are created by logging the stack trace
|
||||
private void LogCallContextStack()
|
||||
{
|
||||
var trace = Environment.StackTrace;
|
||||
if (trace.IndexOf("ScheduledPublishing") > 0)
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: Scheduled Publishing");
|
||||
else if (trace.IndexOf("TouchServerTask") > 0)
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: Server Registration");
|
||||
else if (trace.IndexOf("LogScrubber") > 0)
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: Log Scrubber");
|
||||
else
|
||||
LogHelper.Debug<DefaultDatabaseFactory>("CallContext: " + Environment.StackTrace);
|
||||
}
|
||||
|
||||
private readonly List<UmbracoDatabase> _databases = new List<UmbracoDatabase>();
|
||||
|
||||
// helps identifying database leaks by keeping track of all instances
|
||||
public List<UmbracoDatabase> Databases { get { return _databases; } }
|
||||
|
||||
private static void Log(string message, UmbracoDatabase database)
|
||||
{
|
||||
LogHelper.Debug<DefaultDatabaseFactory>(message + " (" + (database == null ? "" : database.InstanceSid) + ").");
|
||||
}
|
||||
#endif
|
||||
|
||||
internal enum ContextOwner
|
||||
{
|
||||
None,
|
||||
HttpContext,
|
||||
CallContext
|
||||
}
|
||||
|
||||
internal UmbracoDatabase CreateDatabaseInstance(ContextOwner contextOwner)
|
||||
internal UmbracoDatabase CreateDatabaseInstance()
|
||||
{
|
||||
var database = string.IsNullOrEmpty(ConnectionString) == false && string.IsNullOrEmpty(ProviderName) == false
|
||||
? new UmbracoDatabase(ConnectionString, ProviderName, _logger)
|
||||
: new UmbracoDatabase(_connectionStringName, _logger);
|
||||
database.ContextOwner = contextOwner;
|
||||
database.DatabaseFactory = this;
|
||||
//database.EnableSqlTrace = true;
|
||||
#if DEBUG_DATABASES
|
||||
Log("Create " + contextOwner, database);
|
||||
if (contextOwner == ContextOwner.CallContext)
|
||||
LogCallContextStack();
|
||||
_databases.Add(database);
|
||||
#endif
|
||||
return database;
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
UmbracoDatabase database;
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
database = NonContextValue;
|
||||
#if DEBUG_DATABASES
|
||||
Log("Release lcc", database);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
|
||||
#if DEBUG_DATABASES
|
||||
Log("Release ctx", database);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (database != null) database.Dispose(); // removes it from call context
|
||||
}
|
||||
|
||||
// during tests, the thread static var can leak between tests
|
||||
// this method provides a way to force-reset the variable
|
||||
internal void ResetForTests()
|
||||
{
|
||||
var value = NonContextValue;
|
||||
if (value != null) value.Dispose();
|
||||
NonContextValue = null;
|
||||
}
|
||||
|
||||
#region SafeCallContext
|
||||
|
||||
// see notes in SafeCallContext - need to do this since we are using
|
||||
// the logical call context...
|
||||
|
||||
static DefaultDatabaseFactory()
|
||||
{
|
||||
SafeCallContext.Register(DetachAmbientDatabase, AttachAmbientDatabase);
|
||||
}
|
||||
|
||||
// gets a value indicating whether there is an ambient database
|
||||
internal static bool HasAmbientDatabase
|
||||
{
|
||||
get
|
||||
{
|
||||
return HttpContext.Current == null
|
||||
? NonContextValue != null
|
||||
: HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null;
|
||||
}
|
||||
}
|
||||
|
||||
// detaches the current database
|
||||
// ie returns the database and remove it from whatever is "context"
|
||||
internal static UmbracoDatabase DetachAmbientDatabase()
|
||||
{
|
||||
UmbracoDatabase database;
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
database = NonContextValue;
|
||||
NonContextValue = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
database = (UmbracoDatabase) HttpContext.Current.Items[typeof (DefaultDatabaseFactory)];
|
||||
HttpContext.Current.Items.Remove(typeof (DefaultDatabaseFactory));
|
||||
}
|
||||
|
||||
if (database != null) database.ContextOwner = ContextOwner.None;
|
||||
return database;
|
||||
}
|
||||
|
||||
// attach a current database
|
||||
// ie assign it to whatever is "context"
|
||||
// throws if there already is a database
|
||||
internal static void AttachAmbientDatabase(object o)
|
||||
{
|
||||
var database = o as UmbracoDatabase;
|
||||
if (o != null && database == null) throw new ArgumentException("Not an UmbracoDatabase.", "o");
|
||||
|
||||
var ambient = DetachAmbientDatabase();
|
||||
if (ambient != null) ambient.Dispose();
|
||||
|
||||
if (HttpContext.Current == null)
|
||||
{
|
||||
//if (NonContextValue != null) throw new InvalidOperationException();
|
||||
if (database == null) return;
|
||||
|
||||
NonContextValue = database;
|
||||
database.ContextOwner = ContextOwner.CallContext;
|
||||
}
|
||||
else
|
||||
{
|
||||
//if (HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] != null) throw new InvalidOperationException();
|
||||
if (database == null) return;
|
||||
|
||||
HttpContext.Current.Items[typeof (DefaultDatabaseFactory)] = database;
|
||||
database.ContextOwner = ContextOwner.HttpContext;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,20 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
#region Implementation of IEntityFactory<IContent,DocumentDto>
|
||||
|
||||
public static IContent BuildEntity(DocumentDto dto, IContentType contentType)
|
||||
/// <summary>
|
||||
/// Builds a IContent item from the dto(s) and content type
|
||||
/// </summary>
|
||||
/// <param name="dto">
|
||||
/// This DTO can contain all of the information to build an IContent item, however in cases where multiple entities are being built,
|
||||
/// a separate <see cref="DocumentPublishedReadOnlyDto"/> publishedDto entity will be supplied in place of the <see cref="DocumentDto"/>'s own
|
||||
/// ResultColumn DocumentPublishedReadOnlyDto
|
||||
/// </param>
|
||||
/// <param name="contentType"></param>
|
||||
/// <param name="publishedDto">
|
||||
/// When querying for multiple content items the main DTO will not contain the ResultColumn DocumentPublishedReadOnlyDto and a separate publishedDto instance will be supplied
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
public static IContent BuildEntity(DocumentDto dto, IContentType contentType, DocumentPublishedReadOnlyDto publishedDto = null)
|
||||
{
|
||||
var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType);
|
||||
|
||||
@@ -52,8 +65,13 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?)null;
|
||||
content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?)null;
|
||||
content.Version = dto.ContentVersionDto.VersionId;
|
||||
|
||||
content.PublishedState = dto.Published ? PublishedState.Published : PublishedState.Unpublished;
|
||||
content.PublishedVersionGuid = dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId;
|
||||
|
||||
//Check if the publishedDto has been supplied, if not the use the dto's own DocumentPublishedReadOnlyDto value
|
||||
content.PublishedVersionGuid = publishedDto == null
|
||||
? (dto.DocumentPublishedReadOnlyDto == null ? default(Guid) : dto.DocumentPublishedReadOnlyDto.VersionId)
|
||||
: publishedDto.VersionId;
|
||||
|
||||
//on initial construction we don't want to have dirty properties tracked
|
||||
// http://issues.umbraco.org/issue/U4-1946
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Umbraco.Core.Persistence
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to create the UmbracoDatabase for use in the DatabaseContext
|
||||
/// </summary>
|
||||
[Obsolete("Use IDatabaseFactory2 instead")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public interface IDatabaseFactory : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// gets or creates the ambient database
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
UmbracoDatabase CreateDatabase();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to create the UmbracoDatabase for use in the DatabaseContext
|
||||
/// </summary>
|
||||
#pragma warning disable 618
|
||||
public interface IDatabaseFactory2 : IDatabaseFactory
|
||||
#pragma warning restore 618
|
||||
{
|
||||
/// <summary>
|
||||
/// creates a new database
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
UmbracoDatabase CreateNewDatabase();
|
||||
}
|
||||
}
|
||||
@@ -7,21 +7,27 @@ namespace Umbraco.Core.Persistence
|
||||
internal class LockedRepository<TRepository>
|
||||
where TRepository : IDisposable, IRepository
|
||||
{
|
||||
public LockedRepository(Transaction transaction, IDatabaseUnitOfWork unitOfWork, TRepository repository)
|
||||
public LockedRepository(IDatabaseUnitOfWork unitOfWork, TRepository repository)
|
||||
{
|
||||
Transaction = transaction;
|
||||
UnitOfWork = unitOfWork;
|
||||
Repository = repository;
|
||||
}
|
||||
|
||||
public Transaction Transaction { get; private set; }
|
||||
public LockedRepository(Transaction transaction, IDatabaseUnitOfWork unitOfWork, TRepository repository)
|
||||
{
|
||||
//Transaction = transaction;
|
||||
UnitOfWork = unitOfWork;
|
||||
Repository = repository;
|
||||
}
|
||||
|
||||
//public Transaction Transaction { get; private set; }
|
||||
public IDatabaseUnitOfWork UnitOfWork { get; private set; }
|
||||
public TRepository Repository { get; private set; }
|
||||
|
||||
public void Commit()
|
||||
{
|
||||
UnitOfWork.Commit();
|
||||
Transaction.Complete();
|
||||
}
|
||||
//public void Commit()
|
||||
//{
|
||||
// UnitOfWork.Commit();
|
||||
// Transaction.Complete();
|
||||
//}
|
||||
}
|
||||
}
|
||||
@@ -10,11 +10,11 @@ namespace Umbraco.Core.Persistence
|
||||
internal class LockingRepository<TRepository>
|
||||
where TRepository : IDisposable, IRepository
|
||||
{
|
||||
private readonly IDatabaseUnitOfWorkProvider _uowProvider;
|
||||
private readonly Func<IDatabaseUnitOfWork, TRepository> _repositoryFactory;
|
||||
private readonly IScopeUnitOfWorkProvider _uowProvider;
|
||||
private readonly Func<IScopeUnitOfWork, TRepository> _repositoryFactory;
|
||||
private readonly int[] _readLockIds, _writeLockIds;
|
||||
|
||||
public LockingRepository(IDatabaseUnitOfWorkProvider uowProvider, Func<IDatabaseUnitOfWork, TRepository> repositoryFactory,
|
||||
public LockingRepository(IScopeUnitOfWorkProvider uowProvider, Func<IScopeUnitOfWork, TRepository> repositoryFactory,
|
||||
IEnumerable<int> readLockIds, IEnumerable<int> writeLockIds)
|
||||
{
|
||||
Mandate.ParameterNotNull(uowProvider, "uowProvider");
|
||||
@@ -28,76 +28,88 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
public void WithReadLocked(Action<LockedRepository<TRepository>> action, bool autoCommit = true)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead))
|
||||
using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
// getting the database creates a scope and a transaction
|
||||
// the scope is IsolationLevel.RepeatableRead (because UnitOfWork is)
|
||||
// and will throw if outer scope (if any) has a lower isolation level
|
||||
|
||||
foreach (var lockId in _readLockIds)
|
||||
uow.Database.AcquireLockNodeReadLock(lockId);
|
||||
|
||||
using (var repository = _repositoryFactory(uow))
|
||||
{
|
||||
action(new LockedRepository<TRepository>(transaction, uow, repository));
|
||||
action(new LockedRepository<TRepository>(uow, repository));
|
||||
if (autoCommit == false) return;
|
||||
uow.Commit();
|
||||
transaction.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
} // dispose repository => dispose uow => complete (or not) scope
|
||||
} // dispose uow again => nothing
|
||||
}
|
||||
|
||||
public TResult WithReadLocked<TResult>(Func<LockedRepository<TRepository>, TResult> func, bool autoCommit = true)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead))
|
||||
using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
// getting the database creates a scope and a transaction
|
||||
// the scope is IsolationLevel.RepeatableRead (because UnitOfWork is)
|
||||
// and will throw if outer scope (if any) has a lower isolation level
|
||||
|
||||
foreach (var lockId in _readLockIds)
|
||||
uow.Database.AcquireLockNodeReadLock(lockId);
|
||||
|
||||
using (var repository = _repositoryFactory(uow))
|
||||
{
|
||||
var ret = func(new LockedRepository<TRepository>(transaction, uow, repository));
|
||||
var ret = func(new LockedRepository<TRepository>(uow, repository));
|
||||
if (autoCommit == false) return ret;
|
||||
uow.Commit();
|
||||
transaction.Complete();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
} // dispose repository => dispose uow => complete (or not) scope
|
||||
} // dispose uow again => nothing
|
||||
}
|
||||
|
||||
public void WithWriteLocked(Action<LockedRepository<TRepository>> action, bool autoCommit = true)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead))
|
||||
using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
// getting the database creates a scope and a transaction
|
||||
// the scope is IsolationLevel.RepeatableRead (because UnitOfWork is)
|
||||
// and will throw if outer scope (if any) has a lower isolation level
|
||||
|
||||
foreach (var lockId in _writeLockIds)
|
||||
uow.Database.AcquireLockNodeWriteLock(lockId);
|
||||
|
||||
using (var repository = _repositoryFactory(uow))
|
||||
{
|
||||
action(new LockedRepository<TRepository>(transaction, uow, repository));
|
||||
action(new LockedRepository<TRepository>(uow, repository));
|
||||
if (autoCommit == false) return;
|
||||
uow.Commit();
|
||||
transaction.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
} // dispose repository => dispose uow => complete (or not) scope
|
||||
} // dispose uow again => nothing
|
||||
}
|
||||
|
||||
public TResult WithWriteLocked<TResult>(Func<LockedRepository<TRepository>, TResult> func, bool autoCommit = true)
|
||||
{
|
||||
var uow = _uowProvider.GetUnitOfWork();
|
||||
using (var transaction = uow.Database.GetTransaction(IsolationLevel.RepeatableRead))
|
||||
using (var uow = _uowProvider.GetUnitOfWork(IsolationLevel.RepeatableRead))
|
||||
{
|
||||
// getting the database creates a scope and a transaction
|
||||
// the scope is IsolationLevel.RepeatableRead (because UnitOfWork is)
|
||||
// and will throw if outer scope (if any) has a lower isolation level
|
||||
|
||||
foreach (var lockId in _writeLockIds)
|
||||
uow.Database.AcquireLockNodeReadLock(lockId);
|
||||
|
||||
using (var repository = _repositoryFactory(uow))
|
||||
{
|
||||
var ret = func(new LockedRepository<TRepository>(transaction, uow, repository));
|
||||
var ret = func(new LockedRepository<TRepository>(uow, repository));
|
||||
if (autoCommit == false) return ret;
|
||||
uow.Commit();
|
||||
transaction.Complete();
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
} // dispose repository => dispose uow => complete (or not) scope
|
||||
} // dispose uow again => nothing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
|
||||
{
|
||||
[Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)]
|
||||
public class AddIndexToCmsMemberLoginName : MigrationBase
|
||||
{
|
||||
public AddIndexToCmsMemberLoginName(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var dbIndexes = SqlSyntax.GetDefinedIndexes(Context.Database)
|
||||
.Select(x => new DbIndexDefinition()
|
||||
{
|
||||
TableName = x.Item1,
|
||||
IndexName = x.Item2,
|
||||
ColumnName = x.Item3,
|
||||
IsUnique = x.Item4
|
||||
}).ToArray();
|
||||
|
||||
//make sure it doesn't already exist
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_cmsMember_LoginName")) == false)
|
||||
{
|
||||
Create.Index("IX_cmsMember_LoginName").OnTable("cmsMember")
|
||||
.OnColumn("LoginName")
|
||||
.Ascending()
|
||||
.WithOptions()
|
||||
.NonClustered();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
Delete.Index("IX_cmsMember_LoginName").OnTable("cmsMember");
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
|
||||
{
|
||||
[Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)]
|
||||
public class AddIndexToUmbracoNodePath : MigrationBase
|
||||
{
|
||||
public AddIndexToUmbracoNodePath(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database);
|
||||
|
||||
//make sure it doesn't already exist
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoNodePath")) == false)
|
||||
{
|
||||
Create.Index("IX_umbracoNodePath").OnTable("umbracoNode")
|
||||
.OnColumn("path")
|
||||
.Ascending()
|
||||
.WithOptions()
|
||||
.NonClustered();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
Delete.Index("IX_umbracoNodePath").OnTable("umbracoNode");
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero
|
||||
{
|
||||
[Migration("7.6.0", 0, Constants.System.UmbracoMigrationName)]
|
||||
public class AddIndexesToUmbracoRelationTables : MigrationBase
|
||||
{
|
||||
public AddIndexesToUmbracoRelationTables(ISqlSyntaxProvider sqlSyntax, ILogger logger)
|
||||
: base(sqlSyntax, logger)
|
||||
{ }
|
||||
|
||||
public override void Up()
|
||||
{
|
||||
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database).ToArray();
|
||||
|
||||
//make sure it doesn't already exist
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelation_parentChildType")) == false)
|
||||
{
|
||||
//This will remove any corrupt/duplicate data in the relation table before the index is applied
|
||||
//Ensure this executes in a defered block which will be done inside of the migration transaction
|
||||
this.Execute.Code(database =>
|
||||
{
|
||||
//We need to check if this index has corrupted data and then clear that data
|
||||
var duplicates = database.Fetch<dynamic>("SELECT parentId,childId,relType FROM umbracoRelation GROUP BY parentId,childId,relType HAVING COUNT(*) > 1");
|
||||
if (duplicates.Count > 0)
|
||||
{
|
||||
//need to fix this there cannot be duplicates so we'll take the latest entries, it's really not going to matter though
|
||||
foreach (var duplicate in duplicates)
|
||||
{
|
||||
var ids = database.Fetch<int>("SELECT id FROM umbracoRelation WHERE parentId=@parentId AND childId=@childId AND relType=@relType ORDER BY datetime DESC",
|
||||
new { parentId = duplicate.parentId, childId = duplicate.childId, relType = duplicate.relType });
|
||||
|
||||
if (ids.Count == 1)
|
||||
{
|
||||
//this is just a safety check, this should absolutely never happen
|
||||
throw new InvalidOperationException("Duplicates were detected but could not be discovered");
|
||||
}
|
||||
|
||||
//delete the others
|
||||
ids = ids.Skip(0).ToList();
|
||||
|
||||
//iterate in groups of 2000 to avoid the max sql parameter limit
|
||||
foreach (var idGroup in ids.InGroupsOf(2000))
|
||||
{
|
||||
database.Execute("DELETE FROM umbracoRelation WHERE id IN (@ids)", new { ids = idGroup });
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
});
|
||||
|
||||
//unique index to prevent duplicates - and for better perf
|
||||
Create.Index("IX_umbracoRelation_parentChildType").OnTable("umbracoRelation")
|
||||
.OnColumn("parentId").Ascending()
|
||||
.OnColumn("childId").Ascending()
|
||||
.OnColumn("relType").Ascending()
|
||||
.WithOptions()
|
||||
.Unique();
|
||||
}
|
||||
|
||||
//need indexes on alias and name for relation type since these are queried against
|
||||
|
||||
//make sure it doesn't already exist
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_alias")) == false)
|
||||
{
|
||||
Create.Index("IX_umbracoRelationType_alias").OnTable("umbracoRelationType")
|
||||
.OnColumn("alias")
|
||||
.Ascending()
|
||||
.WithOptions()
|
||||
.NonClustered();
|
||||
}
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoRelationType_name")) == false)
|
||||
{
|
||||
Create.Index("IX_umbracoRelationType_name").OnTable("umbracoRelationType")
|
||||
.OnColumn("name")
|
||||
.Ascending()
|
||||
.WithOptions()
|
||||
.NonClustered();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void Down()
|
||||
{
|
||||
Delete.Index("IX_umbracoNodePath").OnTable("umbracoNode");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,12 +167,12 @@ namespace Umbraco.Core.Persistence
|
||||
var providerName = Constants.DatabaseProviders.SqlServer;
|
||||
if (ConfigurationManager.ConnectionStrings[connectionStringName] != null)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName))
|
||||
if (string.IsNullOrEmpty(ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName) == false)
|
||||
providerName = ConfigurationManager.ConnectionStrings[connectionStringName].ProviderName;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Can't find a connection string with the name '" + connectionStringName + "'");
|
||||
throw new NullReferenceException("Can't find a connection string with the name '" + connectionStringName + "'");
|
||||
}
|
||||
|
||||
// Store factory and connection string
|
||||
@@ -332,8 +332,16 @@ namespace Umbraco.Core.Persistence
|
||||
if (_transactionDepth == 1)
|
||||
{
|
||||
OpenSharedConnection();
|
||||
_transaction = _sharedConnection.BeginTransaction(isolationLevel);
|
||||
_transactionCancelled = false;
|
||||
try
|
||||
{
|
||||
_transaction = _sharedConnection.BeginTransaction(isolationLevel);
|
||||
}
|
||||
|
||||
catch (Exception e)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
_transactionCancelled = false;
|
||||
OnBeginTransaction();
|
||||
}
|
||||
else if (isolationLevel > _transaction.IsolationLevel)
|
||||
|
||||
@@ -105,8 +105,8 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
// try to update
|
||||
var rowCount = updateCommand.IsNullOrWhiteSpace()
|
||||
? db.Update(poco)
|
||||
: db.Update<T>(updateCommand, updateArgs);
|
||||
? db.Update(poco)
|
||||
: db.Update<T>(updateCommand, updateArgs);
|
||||
if (rowCount > 0)
|
||||
return RecordPersistenceType.Update;
|
||||
|
||||
@@ -162,7 +162,7 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
[Obsolete("Use the DatabaseSchemaHelper instead")]
|
||||
public static void CreateTable<T>(this Database db)
|
||||
where T : new()
|
||||
where T : new()
|
||||
{
|
||||
var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider);
|
||||
creator.CreateTable<T>();
|
||||
@@ -170,7 +170,7 @@ namespace Umbraco.Core.Persistence
|
||||
|
||||
[Obsolete("Use the DatabaseSchemaHelper instead")]
|
||||
public static void CreateTable<T>(this Database db, bool overwrite)
|
||||
where T : new()
|
||||
where T : new()
|
||||
{
|
||||
var creator = new DatabaseSchemaHelper(db, LoggerResolver.Current.Logger, SqlSyntaxContext.SqlSyntaxProvider);
|
||||
creator.CreateTable<T>(overwrite);
|
||||
@@ -183,16 +183,31 @@ namespace Umbraco.Core.Persistence
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="collection"></param>
|
||||
[Obsolete("Use the method that specifies an SqlSyntaxContext instance instead")]
|
||||
public static void BulkInsertRecords<T>(this Database db, IEnumerable<T> collection)
|
||||
{
|
||||
//don't do anything if there are no records.
|
||||
if (collection.Any() == false)
|
||||
return;
|
||||
db.BulkInsertRecords(collection, null, SqlSyntaxContext.SqlSyntaxProvider, true, false);
|
||||
}
|
||||
|
||||
using (var tr = db.GetTransaction())
|
||||
{
|
||||
db.BulkInsertRecords(collection, tr, SqlSyntaxContext.SqlSyntaxProvider, true, true); // use native, commit
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the bulk insertion
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="db"></param>
|
||||
/// <param name="collection"></param>
|
||||
/// <param name="syntaxProvider"></param>
|
||||
/// <param name="useNativeSqlPlatformBulkInsert">
|
||||
/// If this is false this will try to just generate bulk insert statements instead of using the current SQL platform's bulk
|
||||
/// insert logic. For SQLCE, bulk insert statements do not work so if this is false it will insert one at a time.
|
||||
/// </param>
|
||||
/// <returns>The number of items inserted</returns>
|
||||
public static int BulkInsertRecords<T>(this Database db,
|
||||
IEnumerable<T> collection,
|
||||
ISqlSyntaxProvider syntaxProvider,
|
||||
bool useNativeSqlPlatformBulkInsert = true)
|
||||
{
|
||||
return BulkInsertRecords<T>(db, collection, null, syntaxProvider, useNativeSqlPlatformBulkInsert, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -217,6 +232,26 @@ namespace Umbraco.Core.Persistence
|
||||
bool useNativeSqlPlatformBulkInsert = true,
|
||||
bool commitTrans = false)
|
||||
{
|
||||
db.OpenSharedConnection();
|
||||
try
|
||||
{
|
||||
return BulkInsertRecordsTry(db, collection, tr, syntaxProvider, useNativeSqlPlatformBulkInsert, commitTrans);
|
||||
}
|
||||
finally
|
||||
{
|
||||
db.CloseSharedConnection();
|
||||
}
|
||||
}
|
||||
|
||||
public static int BulkInsertRecordsTry<T>(this Database db,
|
||||
IEnumerable<T> collection,
|
||||
Transaction tr,
|
||||
ISqlSyntaxProvider syntaxProvider,
|
||||
bool useNativeSqlPlatformBulkInsert = true,
|
||||
bool commitTrans = false)
|
||||
{
|
||||
if (commitTrans && tr == null)
|
||||
throw new ArgumentNullException("tr", "The transaction cannot be null if commitTrans is true.");
|
||||
|
||||
//don't do anything if there are no records.
|
||||
if (collection.Any() == false)
|
||||
@@ -224,15 +259,15 @@ namespace Umbraco.Core.Persistence
|
||||
return 0;
|
||||
}
|
||||
|
||||
var pd = Database.PocoData.ForType(typeof(T));
|
||||
if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof(T));
|
||||
var pd = Database.PocoData.ForType(typeof (T));
|
||||
if (pd == null) throw new InvalidOperationException("Could not find PocoData for " + typeof (T));
|
||||
|
||||
try
|
||||
{
|
||||
int processed = 0;
|
||||
|
||||
var usedNativeSqlPlatformInserts = useNativeSqlPlatformBulkInsert
|
||||
&& NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed);
|
||||
&& NativeSqlPlatformBulkInsertRecords(db, syntaxProvider, pd, collection, out processed);
|
||||
|
||||
if (usedNativeSqlPlatformInserts == false)
|
||||
{
|
||||
@@ -266,17 +301,13 @@ namespace Umbraco.Core.Persistence
|
||||
}
|
||||
|
||||
if (commitTrans)
|
||||
{
|
||||
tr.Complete();
|
||||
}
|
||||
return processed;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (commitTrans)
|
||||
{
|
||||
tr.Dispose();
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
@@ -318,13 +349,16 @@ namespace Umbraco.Core.Persistence
|
||||
IEnumerable<T> collection,
|
||||
out string[] sql)
|
||||
{
|
||||
if (db == null) throw new ArgumentNullException("db");
|
||||
if (db.Connection == null) throw new ArgumentException("db.Connection is null.");
|
||||
|
||||
var tableName = db.EscapeTableName(pd.TableInfo.TableName);
|
||||
|
||||
//get all columns to include and format for sql
|
||||
var cols = string.Join(", ",
|
||||
pd.Columns
|
||||
.Where(c => IncludeColumn(pd, c))
|
||||
.Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray());
|
||||
.Where(c => IncludeColumn(pd, c))
|
||||
.Select(c => tableName + "." + db.EscapeSqlIdentifier(c.Key)).ToArray());
|
||||
|
||||
var itemArray = collection.ToArray();
|
||||
|
||||
@@ -338,9 +372,9 @@ namespace Umbraco.Core.Persistence
|
||||
// 4168 / 262 = 15.908... = there will be 16 trans in total
|
||||
|
||||
//all items will be included if we have disabled db parameters
|
||||
var itemsPerTrans = Math.Floor(2000.00 / paramsPerItem);
|
||||
var itemsPerTrans = Math.Floor(2000.00/paramsPerItem);
|
||||
//there will only be one transaction if we have disabled db parameters
|
||||
var numTrans = Math.Ceiling(itemArray.Length / itemsPerTrans);
|
||||
var numTrans = Math.Ceiling(itemArray.Length/itemsPerTrans);
|
||||
|
||||
var sqlQueries = new List<string>();
|
||||
var commands = new List<IDbCommand>();
|
||||
@@ -348,8 +382,8 @@ namespace Umbraco.Core.Persistence
|
||||
for (var tIndex = 0; tIndex < numTrans; tIndex++)
|
||||
{
|
||||
var itemsForTrans = itemArray
|
||||
.Skip(tIndex * (int)itemsPerTrans)
|
||||
.Take((int)itemsPerTrans);
|
||||
.Skip(tIndex*(int) itemsPerTrans)
|
||||
.Take((int) itemsPerTrans);
|
||||
|
||||
var cmd = db.CreateCommand(db.Connection, string.Empty);
|
||||
var pocoValues = new List<string>();
|
||||
@@ -399,7 +433,6 @@ namespace Umbraco.Core.Persistence
|
||||
/// <param name="processed">The number of records inserted</param>
|
||||
private static bool NativeSqlPlatformBulkInsertRecords<T>(Database db, ISqlSyntaxProvider syntaxProvider, Database.PocoData pd, IEnumerable<T> collection, out int processed)
|
||||
{
|
||||
|
||||
var dbConnection = db.Connection;
|
||||
|
||||
//unwrap the profiled connection if there is one
|
||||
@@ -428,7 +461,6 @@ namespace Umbraco.Core.Persistence
|
||||
//could not use the SQL server's specific bulk insert operations
|
||||
processed = 0;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an expression which caches the visitor's result.
|
||||
/// </summary>
|
||||
internal class CachedExpression : Expression
|
||||
{
|
||||
private string _visitResult;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the inner Expression.
|
||||
/// </summary>
|
||||
public Expression InnerExpression { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compiled SQL statement output.
|
||||
/// </summary>
|
||||
public string VisitResult
|
||||
{
|
||||
get { return _visitResult; }
|
||||
set
|
||||
{
|
||||
if (Visited)
|
||||
throw new InvalidOperationException("Cached expression has already been visited.");
|
||||
_visitResult = value;
|
||||
Visited = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the cache Expression has been compiled already.
|
||||
/// </summary>
|
||||
public bool Visited { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the inner expression.
|
||||
/// </summary>
|
||||
/// <param name="expression">expression.</param>
|
||||
/// <remarks>The new expression is assumed to have different parameter but produce the same SQL statement.</remarks>
|
||||
public void Wrap(Expression expression)
|
||||
{
|
||||
InnerExpression = expression;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,49 +9,6 @@ using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an expression which caches the visitor's result.
|
||||
/// </summary>
|
||||
internal class CachedExpression : Expression
|
||||
{
|
||||
private string _visitResult;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the inner Expression.
|
||||
/// </summary>
|
||||
public Expression InnerExpression { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the compiled SQL statement output.
|
||||
/// </summary>
|
||||
public string VisitResult
|
||||
{
|
||||
get { return _visitResult; }
|
||||
set
|
||||
{
|
||||
if (Visited)
|
||||
throw new InvalidOperationException("Cached expression has already been visited.");
|
||||
_visitResult = value;
|
||||
Visited = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the cache Expression has been compiled already.
|
||||
/// </summary>
|
||||
public bool Visited { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Replaces the inner expression.
|
||||
/// </summary>
|
||||
/// <param name="expression">expression.</param>
|
||||
/// <remarks>The new expression is assumed to have different parameter but produce the same SQL statement.</remarks>
|
||||
public void Wrap(Expression expression)
|
||||
{
|
||||
InnerExpression = expression;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An expression tree parser to create SQL statements and SQL parameters based on a strongly typed expression.
|
||||
/// </summary>
|
||||
@@ -581,6 +538,18 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
case "InvariantContains":
|
||||
case "InvariantEquals":
|
||||
|
||||
//special case, if it is 'Contains' and the argumet that Contains is being called on is
|
||||
//Enumerable and the methodArgs is the actual member access, then it's an SQL IN claus
|
||||
if (m.Object == null
|
||||
&& m.Arguments[0].Type != typeof(string)
|
||||
&& m.Arguments.Count == 2
|
||||
&& methodArgs.Length == 1
|
||||
&& methodArgs[0].NodeType == ExpressionType.MemberAccess
|
||||
&& TypeHelper.IsTypeAssignableFrom<IEnumerable>(m.Arguments[0].Type))
|
||||
{
|
||||
goto case "SqlIn";
|
||||
}
|
||||
|
||||
string compareValue;
|
||||
|
||||
if (methodArgs[0].NodeType != ExpressionType.Constant)
|
||||
@@ -597,13 +566,6 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
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;
|
||||
|
||||
@@ -705,29 +667,33 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
// }
|
||||
// return string.Format("{0}{1}", r, s);
|
||||
|
||||
//case "In":
|
||||
case "SqlIn":
|
||||
|
||||
// var member = Expression.Convert(m.Arguments[0], typeof(object));
|
||||
// var lambda = Expression.Lambda<Func<object>>(member);
|
||||
// var getter = lambda.Compile();
|
||||
if (m.Object == null && methodArgs.Length == 1 && methodArgs[0].NodeType == ExpressionType.MemberAccess)
|
||||
{
|
||||
var memberAccess = VisitMemberAccess((MemberExpression) methodArgs[0]);
|
||||
|
||||
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 inArgs = (IEnumerable)getter();
|
||||
|
||||
// var sIn = new StringBuilder();
|
||||
// foreach (var e in inArgs)
|
||||
// {
|
||||
// SqlParameters.Add(e);
|
||||
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 ? "," : "",
|
||||
string.Format("@{0}", SqlParameters.Count - 1));
|
||||
}
|
||||
|
||||
// //sIn.AppendFormat("{0}{1}",
|
||||
// // sIn.Length > 0 ? "," : "",
|
||||
// // GetQuotedValue(e, e.GetType()));
|
||||
// }
|
||||
return string.Format("{0} IN ({1})", memberAccess, sIn);
|
||||
}
|
||||
|
||||
throw new NotSupportedException("SqlIn must contain the member being accessed");
|
||||
|
||||
// return string.Format("{0} {1} ({2})", r, m.Method.Name, sIn.ToString());
|
||||
//case "Desc":
|
||||
// return string.Format("{0} DESC", r);
|
||||
//case "Alias":
|
||||
|
||||
+8
-1
@@ -1,4 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Querying
|
||||
@@ -6,8 +8,13 @@ namespace Umbraco.Core.Persistence.Querying
|
||||
/// <summary>
|
||||
/// String extension methods used specifically to translate into SQL
|
||||
/// </summary>
|
||||
internal static class SqlStringExtensions
|
||||
internal static class SqlExpressionExtensions
|
||||
{
|
||||
public static bool SqlIn<T>(this IEnumerable<T> collection, T item)
|
||||
{
|
||||
return collection.Contains(item);
|
||||
}
|
||||
|
||||
public static bool SqlWildcard(this string str, string txt, TextColumnType columnType)
|
||||
{
|
||||
var wildcardmatch = new Regex("^" + Regex.Escape(txt).
|
||||
@@ -11,7 +11,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal class AuditRepository : PetaPocoRepositoryBase<int, AuditItem>, IAuditRepository
|
||||
{
|
||||
public AuditRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
public AuditRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
: base(work, cache, logger, sqlSyntax)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -2,8 +2,30 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal enum BaseQueryType
|
||||
{
|
||||
Full,
|
||||
/// <summary>
|
||||
/// A query to return all information for a single item
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In some cases this will be the same as <see cref="FullMultiple"/>
|
||||
/// </remarks>
|
||||
FullSingle,
|
||||
|
||||
/// <summary>
|
||||
/// A query to return all information for multiple items
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// In some cases this will be the same as <see cref="FullSingle"/>
|
||||
/// </remarks>
|
||||
FullMultiple,
|
||||
|
||||
/// <summary>
|
||||
/// A query to return the ids for items
|
||||
/// </summary>
|
||||
Ids,
|
||||
|
||||
/// <summary>
|
||||
/// A query to return the count for items
|
||||
/// </summary>
|
||||
Count
|
||||
}
|
||||
}
|
||||
@@ -17,10 +17,9 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
internal class ContentPreviewRepository<TContent> : PetaPocoRepositoryBase<int, ContentPreviewEntity<TContent>>
|
||||
where TContent : IContentBase
|
||||
{
|
||||
public ContentPreviewRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
public ContentPreviewRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
: base(work, cache, logger, sqlSyntax)
|
||||
{
|
||||
}
|
||||
{ }
|
||||
|
||||
#region Not implemented (don't need to for the purposes of this repo)
|
||||
protected override ContentPreviewEntity<TContent> PerformGet(int id)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml;
|
||||
@@ -31,7 +32,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
private readonly ContentPreviewRepository<IContent> _contentPreviewRepository;
|
||||
private readonly ContentXmlRepository<IContent> _contentXmlRepository;
|
||||
|
||||
public ContentRepository(IDatabaseUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection)
|
||||
public ContentRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection)
|
||||
: base(work, cacheHelper, logger, syntaxProvider, contentSection)
|
||||
{
|
||||
if (contentTypeRepository == null) throw new ArgumentNullException("contentTypeRepository");
|
||||
@@ -41,8 +42,8 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
_templateRepository = templateRepository;
|
||||
_tagRepository = tagRepository;
|
||||
_cacheHelper = cacheHelper;
|
||||
_contentPreviewRepository = new ContentPreviewRepository<IContent>(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider);
|
||||
_contentXmlRepository = new ContentXmlRepository<IContent>(work, CacheHelper.CreateDisabledCacheHelper(), logger, syntaxProvider);
|
||||
_contentPreviewRepository = new ContentPreviewRepository<IContent>(work, CacheHelper.NoCache, logger, syntaxProvider);
|
||||
_contentXmlRepository = new ContentXmlRepository<IContent>(work, CacheHelper.NoCache, logger, syntaxProvider);
|
||||
|
||||
EnsureUniqueNaming = true;
|
||||
}
|
||||
@@ -53,7 +54,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
protected override IContent PerformGet(int id)
|
||||
{
|
||||
var sql = GetBaseQuery(false)
|
||||
var sql = GetBaseQuery(BaseQueryType.FullSingle)
|
||||
.Where(GetBaseWhereClause(), new { Id = id })
|
||||
.Where<DocumentDto>(x => x.Newest, SqlSyntax)
|
||||
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
|
||||
@@ -81,15 +82,15 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
return s;
|
||||
};
|
||||
|
||||
var sqlBaseFull = GetBaseQuery(BaseQueryType.Full);
|
||||
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
|
||||
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
|
||||
|
||||
return ProcessQuery(translate(sqlBaseFull), translate(sqlBaseIds));
|
||||
return ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds)));
|
||||
}
|
||||
|
||||
protected override IEnumerable<IContent> PerformGetByQuery(IQuery<IContent> query)
|
||||
{
|
||||
var sqlBaseFull = GetBaseQuery(BaseQueryType.Full);
|
||||
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
|
||||
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
|
||||
|
||||
Func<SqlTranslator<IContent>, Sql> translate = (translator) =>
|
||||
@@ -103,13 +104,25 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
var translatorFull = new SqlTranslator<IContent>(sqlBaseFull, query);
|
||||
var translatorIds = new SqlTranslator<IContent>(sqlBaseIds, query);
|
||||
|
||||
return ProcessQuery(translate(translatorFull), translate(translatorIds));
|
||||
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Overrides of PetaPocoRepositoryBase<IContent>
|
||||
|
||||
/// <summary>
|
||||
/// Returns the base query to return Content
|
||||
/// </summary>
|
||||
/// <param name="queryType"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// Content queries will differ depending on what needs to be returned:
|
||||
/// * FullSingle: When querying for a single document, this will include the Outer join to fetch the content item's published version info
|
||||
/// * FullMultiple: When querying for multiple documents, this will exclude the Outer join to fetch the content item's published version info - this info would need to be fetched separately
|
||||
/// * Ids: This would essentially be the same as FullMultiple however the columns specified will only return the Ids for the documents
|
||||
/// * Count: A query to return the count for documents
|
||||
/// </remarks>
|
||||
protected override Sql GetBaseQuery(BaseQueryType queryType)
|
||||
{
|
||||
var sql = new Sql();
|
||||
@@ -122,14 +135,14 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
.InnerJoin<NodeDto>(SqlSyntax)
|
||||
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
|
||||
|
||||
if (queryType == BaseQueryType.Full)
|
||||
if (queryType == BaseQueryType.FullSingle)
|
||||
{
|
||||
//The only reason we apply this left outer join is to be able to pull back the DocumentPublishedReadOnlyDto
|
||||
//information with the entire data set, so basically this will get both the latest document and also it's published
|
||||
//version if it has one. When performing a count or when just retrieving Ids like in paging, this is unecessary
|
||||
//version if it has one. When performing a count or when retrieving Ids like in paging, this is unecessary
|
||||
//and causes huge performance overhead for the SQL server, especially when sorting the result.
|
||||
//To fix this perf overhead we'd need another index on :
|
||||
// CREATE NON CLUSTERED INDEX ON cmsDocument.node + cmsDocument.published
|
||||
//We also don't include this outer join when querying for multiple entities since it is much faster to fetch this information
|
||||
//in a separate query. For a single entity this is ok.
|
||||
|
||||
var sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)",
|
||||
SqlSyntax.GetQuotedTableName("cmsDocument"),
|
||||
@@ -151,7 +164,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
|
||||
protected override Sql GetBaseQuery(bool isCount)
|
||||
{
|
||||
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.Full);
|
||||
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
|
||||
}
|
||||
|
||||
protected override string GetBaseWhereClause()
|
||||
@@ -222,10 +235,10 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
while (true)
|
||||
{
|
||||
// get the next group of nodes
|
||||
var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.Full));
|
||||
var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.FullMultiple));
|
||||
var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids));
|
||||
|
||||
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), SqlSyntax.SelectTop(sqlIds, groupSize))
|
||||
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(sqlFull, groupSize), new PagingSqlQuery(SqlSyntax.SelectTop(sqlIds, groupSize)))
|
||||
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
|
||||
.ToList();
|
||||
|
||||
@@ -245,7 +258,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
Logger.Error<MediaRepository>("Could not rebuild XML for nodeId=" + xmlItem.NodeId, e);
|
||||
}
|
||||
}
|
||||
baseId = xmlItems.Last().NodeId;
|
||||
baseId = xmlItems[xmlItems.Count - 1].NodeId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -257,15 +270,15 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
|
||||
};
|
||||
|
||||
var sqlFull = translate(GetBaseQuery(BaseQueryType.Full));
|
||||
var sqlFull = translate(GetBaseQuery(BaseQueryType.FullMultiple));
|
||||
var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids));
|
||||
|
||||
return ProcessQuery(sqlFull, sqlIds, true);
|
||||
return ProcessQuery(sqlFull, new PagingSqlQuery(sqlIds), true);
|
||||
}
|
||||
|
||||
public override IContent GetByVersion(Guid versionId)
|
||||
{
|
||||
var sql = GetBaseQuery(false);
|
||||
var sql = GetBaseQuery(BaseQueryType.FullSingle);
|
||||
sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId });
|
||||
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
|
||||
|
||||
@@ -675,12 +688,12 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
// ORDER BY substring(path, 1, len(path) - charindex(',', reverse(path))), sortOrder
|
||||
// but that's probably an overkill - sorting by level,sortOrder should be enough
|
||||
|
||||
var sqlFull = GetBaseQuery(BaseQueryType.Full);
|
||||
var sqlFull = GetBaseQuery(BaseQueryType.FullMultiple);
|
||||
var translatorFull = new SqlTranslator<IContent>(sqlFull, query);
|
||||
var sqlIds = GetBaseQuery(BaseQueryType.Ids);
|
||||
var translatorIds = new SqlTranslator<IContent>(sqlIds, query);
|
||||
|
||||
return ProcessQuery(translate(translatorFull), translate(translatorIds), true);
|
||||
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -860,7 +873,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
|
||||
return GetPagedResultsByQuery<DocumentDto>(query, pageIndex, pageSize, out totalRecords,
|
||||
new Tuple<string, string>("cmsDocument", "nodeId"),
|
||||
(sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField,
|
||||
(sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField,
|
||||
filterCallback);
|
||||
|
||||
}
|
||||
@@ -890,23 +903,58 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
|
||||
return base.GetDatabaseFieldNameForOrderBy(orderBy);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This is the underlying method that processes most queries for this repository
|
||||
/// </summary>
|
||||
/// <param name="sqlFull">
|
||||
/// The full SQL with the outer join to return all data required to create an IContent
|
||||
/// The FullMultiple SQL without the outer join to return all data required to create an IContent excluding it's published state data which this will query separately
|
||||
/// </param>
|
||||
/// <param name="sqlIds">
|
||||
/// <param name="pagingSqlQuery">
|
||||
/// The Id SQL without the outer join to just return all document ids - used to process the properties for the content item
|
||||
/// </param>
|
||||
/// <param name="withCache"></param>
|
||||
/// <returns></returns>
|
||||
private IEnumerable<IContent> ProcessQuery(Sql sqlFull, Sql sqlIds, bool withCache = false)
|
||||
private IEnumerable<IContent> ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false)
|
||||
{
|
||||
// fetch returns a list so it's ok to iterate it in this method
|
||||
var dtos = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(sqlFull);
|
||||
var dtos = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto>(sqlFull);
|
||||
if (dtos.Count == 0) return Enumerable.Empty<IContent>();
|
||||
|
||||
//Go and get all of the published version data separately for this data, this is because when we are querying
|
||||
//for multiple content items we don't include the outer join to fetch this data in the same query because
|
||||
//it is insanely slow. Instead we just fetch the published version data separately in one query.
|
||||
|
||||
//we need to parse the original SQL statement and reduce the columns to just cmsDocument.nodeId so that we can use
|
||||
// the statement to go get the published data for all of the items by using an inner join
|
||||
var parsedOriginalSql = "SELECT cmsDocument.nodeId " + sqlFull.SQL.Substring(sqlFull.SQL.IndexOf("FROM", StringComparison.Ordinal));
|
||||
//now remove everything from an Orderby clause and beyond
|
||||
if (parsedOriginalSql.InvariantContains("ORDER BY "))
|
||||
{
|
||||
parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
var publishedSql = new Sql(@"SELECT *
|
||||
FROM cmsDocument AS doc2
|
||||
INNER JOIN
|
||||
(" + parsedOriginalSql + @") as docData
|
||||
ON doc2.nodeId = docData.nodeId
|
||||
WHERE doc2.published = 1
|
||||
ORDER BY doc2.nodeId
|
||||
", sqlFull.Arguments);
|
||||
|
||||
//go and get the published version data, we do a Query here and not a Fetch so we are
|
||||
//not allocating a whole list to memory just to allocate another list in memory since
|
||||
//we are assigning this data to a keyed collection for fast lookup below
|
||||
var publishedData = Database.Query<DocumentPublishedReadOnlyDto>(publishedSql);
|
||||
var publishedDataCollection = new DocumentPublishedReadOnlyDtoCollection();
|
||||
foreach (var publishedDto in publishedData)
|
||||
{
|
||||
//double check that there's no corrupt db data, there should only be a single published item
|
||||
if (publishedDataCollection.Contains(publishedDto.NodeId) == false)
|
||||
publishedDataCollection.Add(publishedDto);
|
||||
}
|
||||
|
||||
|
||||
var content = new IContent[dtos.Count];
|
||||
var defs = new List<DocumentDefinition>();
|
||||
@@ -920,11 +968,13 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
for (var i = 0; i < dtos.Count; i++)
|
||||
{
|
||||
var dto = dtos[i];
|
||||
DocumentPublishedReadOnlyDto publishedDto;
|
||||
publishedDataCollection.TryGetValue(dto.NodeId, out publishedDto);
|
||||
|
||||
// if the cache contains the published version, use it
|
||||
if (withCache)
|
||||
{
|
||||
var cached = RuntimeCache.GetCacheItem<IContent>(GetCacheIdKey<IContent>(dto.NodeId));
|
||||
var cached = IsolatedCache.GetCacheItem<IContent>(GetCacheIdKey<IContent>(dto.NodeId));
|
||||
//only use this cached version if the dto returned is also the publish version, they must match
|
||||
if (cached != null && cached.Published && dto.Published)
|
||||
{
|
||||
@@ -947,7 +997,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType;
|
||||
}
|
||||
|
||||
content[i] = ContentFactory.BuildEntity(dto, contentType);
|
||||
content[i] = ContentFactory.BuildEntity(dto, contentType, publishedDto);
|
||||
|
||||
// need template
|
||||
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
|
||||
@@ -968,7 +1018,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
.ToDictionary(x => x.Id, x => x);
|
||||
|
||||
// load all properties for all documents from database in 1 query
|
||||
var propertyData = GetPropertyCollection(sqlIds, defs);
|
||||
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
|
||||
|
||||
// assign
|
||||
var dtoIndex = 0;
|
||||
@@ -1056,7 +1106,7 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
|
||||
return currentName;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Dispose disposable properties
|
||||
/// </summary>
|
||||
@@ -1071,5 +1121,26 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
|
||||
_contentPreviewRepository.Dispose();
|
||||
_contentXmlRepository.Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A keyed collection for fast lookup when retrieving a separate list of published version data
|
||||
/// </summary>
|
||||
private class DocumentPublishedReadOnlyDtoCollection : KeyedCollection<int, DocumentPublishedReadOnlyDto>
|
||||
{
|
||||
protected override int GetKeyForItem(DocumentPublishedReadOnlyDto item)
|
||||
{
|
||||
return item.NodeId;
|
||||
}
|
||||
|
||||
public bool TryGetValue(int key, out DocumentPublishedReadOnlyDto val)
|
||||
{
|
||||
if (Dictionary == null)
|
||||
{
|
||||
val = null;
|
||||
return false;
|
||||
}
|
||||
return Dictionary.TryGetValue(key, out val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
internal abstract class ContentTypeBaseRepository<TEntity> : PetaPocoRepositoryBase<int, TEntity>, IReadRepository<Guid, TEntity>
|
||||
where TEntity : class, IContentTypeComposition
|
||||
{
|
||||
protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
protected ContentTypeBaseRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
|
||||
: base(work, cache, logger, sqlSyntax)
|
||||
{
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user