Compare commits

...

248 Commits

Author SHA1 Message Date
Claus 771f8d92f2 adding in missing extension method stuff for some udis. 2017-02-10 14:44:56 +01:00
Claus 5226762528 Merge branch 'temp-U4-9351' into dev-v7.6
Conflicts:
	src/Umbraco.Core/Events/ImportPackageEventArgs.cs
	src/Umbraco.Core/Umbraco.Core.csproj
	src/Umbraco.Web/Editors/PackageInstallController.cs
2017-02-10 10:08:31 +01:00
Claus 7889f82a9a added a check so the 'ensure' wont fail if the path is already as it should be.
added unit test for this method.
2017-02-10 09:25:16 +01:00
Sebastiaan Janssen 3d1a44b32c Merge branch 'temp-U4-8998' into dev-v7.6
# Conflicts:
#	src/Umbraco.Core/Umbraco.Core.csproj
2017-02-10 08:25:52 +01:00
Sebastiaan Janssen 724ec9ab73 Merge pull request #1748 from umbraco/temp-U4-8835
U4-8835 cmsMember.LoginName needs a DB index
2017-02-10 08:22:36 +01:00
Shannon aa0eaf590c adds indexes to relation type 2017-02-10 15:51:41 +11:00
Shannon 2b82520067 U4-8998 umbracoRelation table needs unique index on columns: parentId, childId, relType 2017-02-10 15:38:53 +11:00
Shannon 5aed6334c4 missing file 2017-02-10 14:24:33 +11:00
Shannon 91157caf0e U4-8835 cmsMember.LoginName needs a DB index 2017-02-10 14:23:46 +11:00
Shannon d27fee8a5b Merge remote-tracking branch 'refs/remotes/origin/dev-v7' into dev-v7.6 2017-02-10 09:38:03 +11:00
Sebastiaan Janssen 7aef8cbb67 Merge branch 'temp-U4-9481' into dev-v7.6
# Conflicts:
#	src/Umbraco.Core/Services/FileService.cs
2017-02-09 21:55:17 +01:00
Sebastiaan Janssen a7e28a97d9 Merge pull request #1715 from umbraco/temp-U4-9450
U4-9450 Paged data queries return all property data in the entire dat…
2017-02-09 21:02:15 +01:00
Stephan 8e5c57eb60 U4-9322 - rename kabum in Scope.RobustExit 2017-02-09 15:58:17 +01:00
Claus 57945fcec6 fixing broken build - referencing files that aren't committed. 2017-02-09 14:41:22 +01:00
Sebastiaan Janssen ade3c4ee6c Merge pull request #1732 from rsoeteman/U4-9493
U4 9493
2017-02-09 14:26:44 +01:00
Sebastiaan Janssen 8fe904d780 Merge pull request #1744 from umbraco/temp-U4-9433
U4-9433 Add index to path column on umbracoNode
2017-02-09 13:43:44 +01:00
Shannon 1c0d4001ab U4-9433 Add index to path column on umbracoNode 2017-02-09 17:27:23 +11:00
Shannon f04d70f2d9 Merge remote-tracking branch 'refs/remotes/origin/dev-v7' into dev-v7.6 2017-02-09 17:04:41 +11:00
Shannon Deminick 55098deac0 Merge pull request #1728 from umbraco/temp-u4-9487
U4-9487 - optimize initialization of xml nodes
2017-02-09 16:56:05 +11:00
Shannon 58c5618d30 Adds benchmarks for XML perf testing - had to make the init xml code static with out params to test but that's ok. 2017-02-09 16:42:59 +11:00
Shannon d0cbeb4a0a Better optimized initialization of xml nodes with less allocations 2017-02-09 15:29:38 +11:00
Shannon ce0821e2d6 Merge remote-tracking branch 'refs/remotes/origin/dev-v7' into dev-v7.6
# Conflicts:
#	build/NuSpecs/UmbracoCms.Core.nuspec
#	src/Umbraco.Tests/Umbraco.Tests.csproj
2017-02-09 13:20:33 +11:00
Shannon Deminick b6e9db7363 Merge pull request #1726 from umbraco/temp-u4-9322
Scope Scope Scope!
2017-02-09 12:30:58 +11:00
Shannon 9f710a957d Merge remote-tracking branch 'refs/remotes/origin/dev-v7.6' into temp-u4-9322
# Conflicts:
#	src/Umbraco.Core/Services/FileService.cs
2017-02-09 12:30:01 +11:00
Shannon b7578e2b98 fixes tests 2017-02-09 12:07:16 +11:00
Shannon 97e8973cbd Ensures that the RelateOnTrashHandler doesn't attempt to create a relationship for an item that doesn't exist 2017-02-09 11:40:13 +11:00
Shannon 2dc45c3b9f Tons of code duplication removed, streamlines the content, media and member repositories to use the same paged methods and filtering mechanism (much bettter), adds some tests, creates a marker interface for IContentServiceBase so we can use extension methods for now to de-duplicate some of the code between media and content services 2017-02-09 10:34:40 +11:00
Sebastiaan Janssen bb31eb7c35 Emit only relative paths for files in a package 2017-02-08 16:19:47 +01:00
Shannon 4feb876463 Fixes issues of deleting a content item and the trashing events raised for content/media that is going to be deleted anyways, adds tests to support. Fixes MediaService to not delete the xml directly and instead rely on it's repository to do that (which is the correct way), streamlines both content/media service logic to be the same (need to start sharing code!) 2017-02-09 01:47:04 +11:00
Mikkel Holck Madsen a8914dcfdb Merge pull request #1740 from umbraco/temp-U4-9458
Adds unit tests for U4-9458 UmbracoExamine performance optimizations
2017-02-08 15:12:07 +01:00
Sebastiaan Janssen 7648c76f6f List full path of each file installed 2017-02-08 14:45:07 +01:00
Sebastiaan Janssen 308ed3bba7 Removed files that are not found on disk don't get updated to list their full path 2017-02-08 14:44:48 +01:00
Sebastiaan Janssen 7bcb2b11f6 Makes the (un)install summary accessible to events 2017-02-08 14:43:59 +01:00
Shannon e06cda98e9 fixes SQL IN expression 2017-02-08 22:25:07 +11:00
Shannon fb02a5b06d Fixes issue of removing content types in bulk (like when removing a package) and having the trashed event being raised for what will end up being a deleted node, Fixes a bug with the (very silly) extension methods for content types, removes singleton accessors from these (very silly) extension methods - which actually never worked for media types! 2017-02-08 21:59:16 +11:00
Sebastiaan Janssen 311edd3279 Adds missing dependency (it's in dev-v7, must have been a merge where it was forgotten to be merged into dev-v7.6) 2017-02-08 10:36:36 +01:00
Claus 1c89f8c8dd Merge branch 'temp-U4-1324' into dev-v7.6 2017-02-08 09:54:12 +01:00
Claus 24063c403a Merge remote-tracking branch 'origin/dev-v7.6' into temp-U4-1324
Conflicts:
	src/Umbraco.Core/packages.config
	src/Umbraco.Tests/packages.config
2017-02-08 09:14:27 +01:00
Claus 0f930d7342 Merge pull request #1741 from umbraco/temp-U4-9499
U4-9499 Update to latest Json.Net
2017-02-08 08:58:22 +01:00
Emil Wangaa c36000c4dd Adds create container endpoint for creation folders for scripts, partialviews, partialviewmacros 2017-02-08 08:52:36 +01:00
Mads Rasmussen 999de5ae1d add client side logic for partial view macros folder creation 2017-02-08 08:52:36 +01:00
Mads Rasmussen 00982d4278 add client side logic for partial views folder creation 2017-02-08 08:46:10 +01:00
Mads Rasmussen cda47ed536 add client side logic for scripts folder creation 2017-02-08 08:45:23 +01:00
Shannon f4f1f6774d U4-9499 Update to latest Json.Net 2017-02-08 17:47:32 +11:00
Warren Buckley 92acc3d331 Merge pull request #1736 from umbraco/temp-U4-9429
U4-9429 Make new create dialogs for code files (or rethink snippets)
2017-02-07 21:28:43 +00:00
Warren Buckley ed4db61288 Merge pull request #1738 from umbraco/temp-U4-9314
U4-9314 Update to .Net Framework 4.6.2
2017-02-07 17:49:17 +00:00
Warren Buckley ba6705a706 Merge branch 'dev-v7.6' into temp-U4-9314 2017-02-07 17:27:07 +00:00
Mads Rasmussen dc980bd78e remove null 2017-02-07 16:41:01 +01:00
Mads Rasmussen c9f99d7f42 localise create items 2017-02-07 16:35:33 +01:00
Sebastiaan Janssen 73e219b23c Fix unit tests 2017-02-07 16:24:34 +01:00
Sebastiaan Janssen 07e0de5824 Adds unit tests for U4-9458 UmbracoExamine performance optimizations 2017-02-07 16:20:40 +01:00
Mads Rasmussen 79a103662f remove alerts 2017-02-07 16:09:36 +01:00
Sebastiaan Janssen 389b29599b U4-9495 ImageProcessor high CPU usage when trimCache=true 2017-02-07 10:29:11 +01:00
Claus 925c343a30 U4-9351 Add events for package install/uninstall 2017-02-07 10:00:34 +01:00
Shannon 351796fd6b Created test to verify problem with deleting content of content type in a loop (i.e. on package uninstall) 2017-02-07 15:46:50 +11:00
Shannon e58d9a9535 Fixing tests, fixes PublishingStrategy to hvae the scope provider passed in 2017-02-07 14:40:45 +11:00
Shannon 3fa7c4d7ef Removes POC class, fixes content service trash/move events so there's not ambiguous 2017-02-07 14:08:55 +11:00
Shannon b6b2a53361 Fixes publishing strategy to use the normal event messages collection since we haven't actually wired those up to the scope for now. 2017-02-07 14:07:01 +11:00
Shannon 4647ed2551 Merge branch 'refs/heads/dev-v7.6' into temp-u4-9322
# Conflicts:
#	src/Umbraco.Core/DatabaseContext.cs
#	src/Umbraco.Core/UmbracoApplicationBase.cs
2017-02-07 13:44:17 +11:00
Shannon e7e6ad6d0b Some adjustments for U4-9477 2017-02-07 13:36:32 +11:00
Shannon a71ec8dd81 Merge remote-tracking branch 'refs/remotes/origin/dev-v7' into dev-v7.6 2017-02-07 12:50:44 +11:00
Shannon f816a63b54 Ok, reverting the correct commit this time: Revert "Fixes database context issue during startup when there is no db configured" 2017-02-07 12:49:26 +11:00
Shannon 138991d951 OMG Re-rever "DOH! Revert "Fixes database context issue during startup when there is no db configured", i reverted the wrong commit on the wrong branch 2017-02-07 12:43:06 +11:00
Shannon 92ce064d30 DOH! Revert "Fixes database context issue during startup when there is no db configured" pushed to the wrong branch 2017-02-07 12:40:28 +11:00
Shannon ab3bfa4e92 Fixes database context issue during startup when there is no db configured 2017-02-07 12:39:35 +11:00
Shannon fa8f3985b8 Fixes database context issue during startup when there is no db configured 2017-02-07 12:33:37 +11:00
Mikkel Holck Madsen 175c785705 Merge pull request #1722 from umbraco/temp-U4-9458
Changes IndexFieldPolicies to be a StaticFieldCollection which is a k…
2017-02-06 21:45:54 +01:00
Mikkel Holck Madsen 72aebd3ecf Merge branch 'dev-v7' into temp-U4-9458 2017-02-06 21:44:26 +01:00
Warren Buckley c8408760df Merge pull request #1737 from umbraco/temp-U4-9474
Fixes: U4-9474 Update macro picker to follow the new picker UI
2017-02-06 16:03:25 +00:00
Warren Buckley 5834649abc Merge pull request #1733 from umbraco/temp-U4-9466
fixes: U4-9466 Duplicate reorder links for Umbraco grid
2017-02-06 15:36:04 +00:00
Mads Rasmussen 2eda70542f rename macro container to macro picker 2017-02-06 16:16:26 +01:00
Mads Rasmussen a2a17faca1 Fixes: U4-9474 Update macro picker to follow the new picker UI 2017-02-06 16:11:51 +01:00
Shannon a6c2844d12 updates nuspec with correctly targeted framework 2017-02-07 01:03:50 +11:00
Mikkel Holck Madsen 802cbb82c4 Merge pull request #1723 from umbraco/temp-U4-9448
U4-9448 Slave Front End server requires write access to database when…
2017-02-06 15:01:30 +01:00
Shannon 6f16b98b32 regression issue from refactoring how media works with Deploy 2017-02-07 00:57:44 +11:00
Mikkel Holck Madsen 42f5507fe8 Merge pull request #1734 from umbraco/temp-U4-9492
U4-9492 Update to latest ImageProcessor
2017-02-06 14:39:01 +01:00
Shannon 72910cb35e Updates everything to .Net 4.6.2, had to update the semver reference too - no breaking changes though 2017-02-07 00:22:34 +11:00
Shannon 7be30ad6ac Moves all binding redirects via code to one place 2017-02-06 23:46:44 +11:00
Sebastiaan Janssen 5a8e37f5b4 U4-9492 Update to latest ImageProcessor 2017-02-06 13:46:10 +01:00
Shannon f87ccc0a53 Updates nuspec 2017-02-06 23:36:56 +11:00
Shannon 40ed77103f Updates to latest 1.x version of log4net (before the real 2.x version) with binding redirects and codebase binding redirects to redirect from non-signed to signed version 2017-02-06 23:07:20 +11:00
Mads Rasmussen 483762a984 fixes: U4-9466 Duplicate reorder links for Umbraco grid 2017-02-06 12:50:34 +01:00
Richard Soeteman a3c7b1fcfe Fixed issue U4-9494 2017-02-06 11:51:44 +01:00
Stephan 145ca54dcf Merge origin/dev-v7.6 into temp-u4-9322 2017-02-06 11:29:04 +01:00
Richard Soeteman 02785eea42 Fixed issue U4-9493 2017-02-06 11:26:58 +01:00
Stephan 1aecb66167 U4-9322 - restore DatabaseContext ctors, see U4-9477 2017-02-06 11:17:24 +01:00
Stephan 96244f9f7b U4-9322 - pushed DEBUG_SCOPES by mistake 2017-02-06 10:43:39 +01:00
Stephan e0cc59d212 Merge branch 'temp-u4-9322' of https://github.com/umbraco/Umbraco-CMS into temp-u4-9322 2017-02-06 10:09:31 +01:00
Stephan dd2946419b U4-9322 - fix broken DEBUG_SCOPES 2017-02-06 10:08:29 +01:00
Shannon d0ea8e83ad Merge remote-tracking branch 'refs/remotes/origin/dev-v7' into dev-v7.6 2017-02-06 19:49:00 +11:00
Shannon Deminick 3c7d3b2f63 Merge pull request #1724 from umbraco/temp-installer-url-update
Update NewInstallStep.cs
2017-02-06 19:42:23 +11:00
Shannon a65ad3c72a Adds equality members for all event args, updates equality check for EventDefinitionBase to include the args (since that is quite important!), creates an OrderedHashSet class which can deduplicate lists for us based on member equality and supports first in or last in + tests. 2017-02-06 18:46:23 +11:00
Shannon f6a7b25e87 Updates CacheRefresherEventHandler to have consistent events, adds logic to find the event based on definitions, adds tests to test them all 2017-02-06 17:26:06 +11:00
Shannon 0e06245177 Adds notes for stephen to look at 2017-02-06 14:46:02 +11:00
Stephan 5a81a9eed7 U4-9322 - various fixes 2017-02-04 15:44:56 +01:00
Stephan 38e7d7e697 Merge remote-tracking branch 'origin/temp-u4-9322' into temp-u4-9322 2017-02-03 21:54:55 +01:00
Stephan 820069d5d5 U4-9322 - filesystems & cleanup 2017-02-03 20:01:43 +01:00
Stephan 49f3979d18 U4-9487 - optimize initialization of xml nodes 2017-02-03 19:28:43 +01:00
Emil Wangaa 055e209e0b Fixed the path and virtualpath when creating and scaffolding codefiles
Ensuring that javascript files are created with the .js extension
2017-02-03 12:06:49 +01:00
Shannon 5579563105 Creates a test (which fails) so that stephen can have a look 2017-02-03 14:56:08 +11:00
Shannon cda9d0f9f2 pretty big perf boost so that we aren't going to the services user cache everytime we want to know the user's culture, this will already be set on the thread! 2017-02-03 14:42:06 +11:00
Shannon 49b7f15e3d fixes tests 2017-02-03 13:32:22 +11:00
Shannon 8ae700eedf code formatting and merge fixing 2017-02-03 12:23:32 +11:00
Shannon 411bbcc192 Merge branch 'refs/heads/dev-v7.6' into temp-u4-9322
# Conflicts:
#	build/UmbracoVersion.txt
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
#	src/Umbraco.Core/Services/ContentService.cs
#	src/Umbraco.Core/Services/ContentTypeService.cs
#	src/Umbraco.Core/Services/DataTypeService.cs
#	src/Umbraco.Core/Services/MacroService.cs
#	src/Umbraco.Core/Services/MediaService.cs
#	src/Umbraco.Core/Services/MemberService.cs
#	src/Umbraco.Core/Services/MemberTypeService.cs
#	src/Umbraco.Core/Services/UserService.cs
#	src/Umbraco.Web/Umbraco.Web.csproj
2017-02-03 12:14:38 +11:00
Shannon d598fdce54 Updates to latest Examine release 2017-02-03 12:03:56 +11:00
Shannon c16ce4ce1f Merge branch 'dev-v7.6' of https://github.com/umbraco/Umbraco-CMS into dev-v7.6 2017-02-03 10:21:10 +11:00
Shannon 86021c5052 Adds remaining core methods to make 2FA providers work if you know how to wire it up 2017-02-03 00:47:28 +11:00
Mads Rasmussen de87701c95 remove option to create xml file 2017-02-02 14:46:23 +01:00
Shannon 5060e709d1 Fixed merge conflicts, adds required methods to auth controllers. 2017-02-02 22:11:34 +11:00
Emil Wangaa c073191f69 Fixes creation of codefiles in folders 2017-02-02 11:47:46 +01:00
Emil Wangaa c21837e906 Merge remote-tracking branch 'origin/dev-v7.6' into temp-U4-9429
# Conflicts:
#	src/Umbraco.Web/Editors/CodeFileController.cs
2017-02-02 10:24:42 +01:00
hartvig 32892cf5e0 Update NewInstallStep.cs 2017-02-02 10:05:14 +01:00
Stephan 478ca30948 U4-9322 fs - create scope option 2017-02-02 09:49:23 +01:00
Emil Wangaa 6bf5f4dbdf Fixed up tree menus for scripts, partialviews and partialviewmacros 2017-02-02 09:20:42 +01:00
Shannon 32d757b13c Updates the ContentRepository to not fetch the published version info in the same query when returning a collection of documents, instead we fetch the published version information in a separate query 2017-02-02 17:05:33 +11:00
Shannon 882d5ec7f5 missing null check 2017-02-02 16:02:29 +11:00
Shannon ce69497a68 Update code to have zero breaking changes for old public APIs 2017-02-02 14:16:39 +11:00
Shannon 1d43cf491b U4-9448 Slave Front End server requires write access to database when master performs unpublish 2017-02-02 14:09:49 +11:00
Emil Wangaa c9082ce511 Changed PostSave method in CodeFileController to create or update instead of only updating - this is for keeping consistency with how other new editors work 2017-02-01 15:46:22 +01:00
Claus 46c7737c17 Merge pull request #1714 from umbraco/temp-u4-9441
Fixes tree syncing issues
2017-02-01 14:04:18 +01:00
Claus 06c829eb27 initializing stringbuilder with text instead of appending after init.
trimming end instead of both beginning and end, since we know whats in the beginning.
2017-02-01 14:03:03 +01:00
Mads Rasmussen e439302b93 add support for getScaffold for scripts, partialViews and partialViewMacros editor 2017-02-01 13:54:16 +01:00
Emil Wangaa 6a1d7d2586 Adds GetScaffold in CodeFileController to simplify initial view in backoffice
Refactored the FileService a bit to be able to get content of the snippets without duplicate code
2017-02-01 12:33:20 +01:00
Shannon 27283754c0 Changes IndexFieldPolicies to be a StaticFieldCollection which is a keyed collection instead of using Linq and iterating to find the values in critical code 2017-02-01 16:48:09 +11:00
Shannon 5b2212829c Small performance change which makes a fairly large impact when rebuilding very large indexes. 2017-02-01 13:02:22 +11:00
Stephan 4a6844b949 U4-9322 - repository caches, cleanup 2017-01-31 19:36:55 +01:00
Stephan ac9b484d3d U4-9322 - wire EventMessages, cleanup 2017-01-31 18:19:03 +01:00
Stephan 97a76d5ad4 U4-9322 - introduce ScopeContext that lives longer than Scope, now Xml does not multi-clone! 2017-01-31 16:42:01 +01:00
Mads Rasmussen d4fab66411 add UI for partial views create dialog 2017-01-31 15:00:37 +01:00
Mads Rasmussen 4cd287616e add UI for partial view macros create dialog 2017-01-31 14:44:52 +01:00
Mads Rasmussen d22afe07db add UI for scripts create dialog 2017-01-31 12:40:56 +01:00
Morten Christensen fa56f1a59d Merge branch 'dev-v7' into dev-v7.6 2017-01-31 12:27:40 +01:00
Morten Christensen 546209ddb5 Merge pull request #1716 from umbraco/temp-U4-9451
U4-9451 Package installation custom user control is not displayed aft…
2017-01-31 12:24:52 +01:00
Morten Christensen 1e7c44ead0 Merge remote-tracking branch 'origin/temp-U4-9451' into dev-v7.6 2017-01-31 11:38:41 +01:00
Per Ploug 90448f1c78 Added comments and made helper internal 2017-01-31 11:06:14 +01:00
Stephan 6dc34e1167 U4-9322 - cleanup EventExtensions 2017-01-31 10:37:37 +01:00
Warren Buckley 6956e01861 Merge pull request #1713 from umbraco/temp-U4-9413
U4-9413 moving links to document and media to top of generic properties.
2017-01-31 09:37:34 +00:00
Claus f3b364022a bumping version for deploy. 2017-01-31 10:33:57 +01:00
Claus 56ae2b709d adding deploy entity types for forms. 2017-01-31 10:31:45 +01:00
Emil Wangaa 6bd1a77b96 Simple wiring up of the create menu for partialview, partialviewmacro and script 2017-01-31 10:10:05 +01:00
Shannon 86b2dac231 Needed to change the 2nd query to use a new PagingSqlQuery object due to the way that < SQL 2012 formats it's paging query which is just not compatible with how we were parsing the queries for properties... i don't actually think it was working for a long tme. 2017-01-31 17:20:44 +11:00
Shannon 54bda6f12a U4-9451 Package installation custom user control is not displayed after a package is installed 2017-01-31 15:40:02 +11:00
Shannon 2f67a485aa Ensures that the sqlIds query is used for paging when processing members/media 2017-01-31 14:52:48 +11:00
Shannon f192f241cf U4-9450 Paged data queries return all property data in the entire database, not just for the paged subset 2017-01-31 13:48:47 +11:00
Mads Rasmussen 0acc202740 Merge pull request #1712 from umbraco/temp-U4-9410
U4-9410 Hook up delete dialog for Code Files
2017-01-30 20:50:36 +01:00
Emil Wangaa 431f91b900 Adds an api endpoint for getting the available snippets in the Umbraco folder 2017-01-30 14:57:08 +01:00
Stephan d4acc2c79b U4-9322 - EventsDispatchMode, fix tests 2017-01-30 13:53:31 +01:00
Per Ploug b05c4711ce Fixes tree syncing issues 2017-01-30 13:33:05 +01:00
Claus 74c167ed03 U4-9413 moving links to document and media to top of generic properties. 2017-01-30 13:18:30 +01:00
Claus 4ad0c1bb89 cleaning usings. 2017-01-30 13:14:19 +01:00
Claus 67da15330e U4-9410 Hook up delete dialog for Code Files
delete dialogs for partialviews, partialviewmacros and scripts.
+ fixed codefileResource.
+ fixed comments in MemberTypesDeleteController.
2017-01-30 11:56:30 +01:00
Sebastiaan Janssen 690af8e494 Merge pull request #1711 from umbraco/temp-U4-9444
U4-9444 SessionState is not available when rendering macro contents i…
2017-01-30 10:00:40 +01:00
Shannon a458e7daf0 Merge remote-tracking branch 'refs/remotes/origin/dev-v7' into dev-v7.6
# Conflicts:
#	build/UmbracoVersion.txt
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
#	src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
#	src/Umbraco.Web/Umbraco.Web.csproj
2017-01-30 16:55:12 +11:00
Shannon 9d06ce53a4 U4-9444 SessionState is not available when rendering macro contents in the Rich Text Editor 2017-01-30 13:22:47 +11:00
Stephan d0e8e77fc5 Fix FileSystemProviderManagerTests 2017-01-28 10:05:19 +01:00
Stephan d85cb9e15e Fix FileSystemProviderManagerTests 2017-01-28 10:04:43 +01:00
Stephan 798105998e U4-9322 - begin refactoring and fixing events 2017-01-27 18:17:21 +01:00
Stephan f4994a5133 Merge temp-u4-9322 (Shan Events) into temp-u4-9322a (Steph) - builds 2017-01-27 11:42:54 +01:00
Mads Rasmussen 19a4cf774b Merge pull request #1680 from umbraco/temp-U4-9352
Implements CodeFileController
2017-01-27 10:49:07 +01:00
Mads Rasmussen ab00ba8a12 Merge branch 'dev-v7.6' into temp-U4-9352
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js
2017-01-27 10:39:31 +01:00
Claus 57e2a19023 changing parameter name for IGridCellValueConnector.IsConnector. 2017-01-27 10:16:01 +01:00
Claus daa80a7bf6 Merge pull request #1709 from umbraco/temp-U4-9435-2
U4-9435 Fix breaking change made in U4-9312
2017-01-27 09:05:47 +01:00
Shannon 8152e7cb92 Doh, forgot a file change for tests - fixes build 2017-01-27 16:55:29 +11:00
Shannon Deminick adccb4161e Merge pull request #1707 from umbraco/temp-u4-9438
U4-9438 - fix nasty issue with reading properties in VersionableRepos…
2017-01-27 16:52:10 +11:00
Shannon 72a0187295 Writes unit test to confirm the correct property data iteration in GetPropertyCollection 2017-01-27 16:50:44 +11:00
Shannon 24d6287918 Adds string overloads just in case 2017-01-27 16:21:01 +11:00
Shannon 55900a3464 Updates MediaController and MediaTypeController to have the same method signature supporting both INT and GUID with a custom action selector to handle the ambiguity 2017-01-27 16:09:35 +11:00
Stephan 6bbfb65100 Merge temp-u4-9322 (Shan Events) into temp-u4-9322a (Steph) 2017-01-26 18:30:00 +01:00
Sebastiaan Janssen 36e9bb4d78 Merge pull request #1705 from umbraco/temp-v758-recyclebin-fix
Fixes U4-9437
2017-01-26 17:31:31 +01:00
Sebastiaan Janssen 639d04381e Bump version 2017-01-26 14:48:19 +01:00
Warren Buckley 2465eb233e Adds in same workaround as other trees like DataTypes & DocTypes where Folders/Containers set an inline piece of JS that does nothing & returns 2017-01-26 11:32:13 +00:00
Stephan 7f905bce0e U4-9438 - fix nasty issue with reading properties in VersionableRepositoryBase 2017-01-26 11:49:52 +01:00
Niels Hartvig 205ea22daa Fixes U4-9437 2017-01-25 17:34:34 +01:00
Stephan cca3b9032f U4-9322 - cleanup 2017-01-25 14:18:33 +01:00
Stephan 4fc3398e49 U4-9322 - refactor UnitOfWork pattern 2017-01-25 13:34:41 +01:00
Shannon 2d5254e169 Makes ScopedEventManager aware of it's outer manager (if any) so it knows when to raise events (if configured) 2017-01-25 20:17:39 +11:00
Shannon c5033710ca Updates all ambigous event raising when the event finder cannot determine the name based on the args, all tests pass apart from 3 now 2017-01-25 18:44:29 +11:00
Shannon c824d27728 Updates remaining services to use the event manager to emit events correctly 2017-01-25 16:52:32 +11:00
Shannon ac54affa57 Refactor IPublishingStrategy, create IPublishingStrategy2 as a work around, adds EventMessages to IScopeUnitOfWork, ensures all events raised with IPublishingStrategy are done with uow 2017-01-25 16:49:44 +11:00
Shannon f7d7a43720 Fixes BulkInsertRecords to not require a trans , removes inner trans from PermissionRepository, fixes up events in PermissionRepository and MemberGroupRepository, fixes up uow in UserService 2017-01-25 15:15:28 +11:00
Shannon 6028907a02 Fixes up relation service with correct even emitting and no nested uow 2017-01-25 14:45:49 +11:00
Shannon e14ca4f3d0 Fixes up memberservice service with correct even emitting and no nested uow and removes inner transactions in MemberGroupRepository 2017-01-25 14:40:24 +11:00
Stephan a97b566065 U4-9422 - complete implementing scoped xml cache 2017-01-24 07:59:46 +01:00
Stephan 26a95a8ca1 U4-9322 - cont. implementing scoped xml cache 2017-01-23 18:03:38 +01:00
Stephan e3308e86f0 U4-9322 - begin implementing scoped xml cache 2017-01-23 18:03:08 +01:00
Stephan 1d98507785 TEMP 2017-01-23 15:39:46 +01:00
Shannon 096c3aeec6 started fixing up MemberService but this one is odd.... 2017-01-23 23:56:39 +11:00
Shannon 7ca5c48ae7 Fixes up membergroup service with correct even emitting and no nested uow 2017-01-23 23:30:47 +11:00
Shannon 4a1a216b3c Fixes up macro service with correct even emitting and no nested uow 2017-01-23 23:28:50 +11:00
Shannon f96d6aed9d Fixes up localization service with correct even emitting and no nested uow 2017-01-23 23:25:39 +11:00
Shannon 192aae71a2 Fixes up file service with correct even emitting and no nested uow and fixes up strange thing with RepositoryFactory and stylesheets 2017-01-23 23:21:53 +11:00
Shannon bd8af539b4 Fixes up data type service with correct even emitting and no nested uow 2017-01-23 22:55:16 +11:00
Shannon 479d859a75 Fixes up member type service with correct even emitting and no nested uow 2017-01-23 22:48:52 +11:00
Shannon 1050af5376 Updates ContentTypeService with the correct event emitting and no nested uows 2017-01-23 22:45:45 +11:00
Shannon 82dbfb769e Updated media service with correct usage of emitting events and having non nested uow 2017-01-23 22:29:38 +11:00
Shannon 55eb996c19 Adds a couple needed hack methods, updates ContentService to have the proper event emmitting without any nested uow's and ensuring that readonly uow's are committed. 2017-01-23 22:16:27 +11:00
Shannon ac4de99f30 Initial commit of changing all IsRaisedEventCancelled to use the event manager and inside of a uow 2017-01-23 20:48:51 +11:00
Stephan a3150b7d4e U4-9322 - missing files 2017-01-20 18:33:21 +01:00
Stephan 1701eb2450 u4-9322 - events cleanup 2017-01-20 17:05:22 +01:00
Stephan 20ac21aef2 Merge origin/temp-U4-9322 into temp-u4-9322 2017-01-20 16:51:25 +01:00
Stephan 4ac20de1dd U4-9322 - complete scope repository caches 2017-01-20 16:29:13 +01:00
Stephan 848eaf5e27 U4-9322 - cont. implementing scoped repository caches 2017-01-20 12:08:55 +01:00
Warren Buckley f3e78e990f Fix up failing unit test for the number of Trees found - this has gone down due this test is for implementing interface ITree which is the legacy type of Trees, where the new PartialViews & PartialView Macros now use TreeController 2017-01-20 09:52:23 +00:00
Shannon a6996922b7 Adds more support for auto-finding event names, adds tests to support with examples of how to interrogate queued events 2017-01-20 17:45:46 +11:00
Stephan 434703f4ec U4-9322 - begin implementing scoped repository caches 2017-01-19 19:47:09 +01:00
Warren Buckley 2f7b532209 Adds in code summaries for C# doc generation. Removes unused references & some tidying up (mostly sensible R# suggestions) 2017-01-19 15:43:29 +00:00
Warren Buckley abebb40461 Update some copy/paste typos in code comments 2017-01-19 15:18:12 +00:00
Warren Buckley 3b54db9b49 Update main templat editor to use a localised notification 2017-01-19 15:14:46 +00:00
Warren Buckley 1defc1e0e4 Changed key for saving scripts - as it mentioned 'script view' Whats that?! So changed to more generic file 2017-01-19 15:14:14 +00:00
Warren Buckley a7e626f4a7 Localisation notification message for saving scripts 2017-01-19 15:05:05 +00:00
Warren Buckley 04f6a01196 Forgot to remove the old notification - as was display two 2017-01-19 15:04:36 +00:00
Warren Buckley ef64fbb839 Same change for the partialview editor - to display a localized success notifcation 2017-01-19 15:01:14 +00:00
Warren Buckley 2424a1c04b Adds in localised sucess notification when saving partial view macros 2017-01-19 14:57:23 +00:00
Warren Buckley c8c7de065d Forgot to update the var name in codeFileResource 2017-01-19 14:56:25 +00:00
Warren Buckley 61d71d0552 Update codeFileResource docs 2017-01-19 14:40:10 +00:00
Mads Rasmussen a09755ef7f remove unused code in partial view editor 2017-01-19 11:19:34 +01:00
Niels Hartvig afae06a866 Adds support for icons and encoding of directories for Code files 2017-01-19 10:41:08 +01:00
Niels Hartvig 051af5c48e Wire up the editors and controllers 2017-01-19 09:49:27 +01:00
Niels Hartvig 1fb05fcca7 Merge remote-tracking branch 'refs/remotes/origin/temp-file-editors' into temp-U4-9352 2017-01-19 08:45:23 +01:00
Shannon b51e571803 Adds unit tests and fixes others 2017-01-19 18:25:45 +11:00
Shannon a275567dba Adds equality members to the EventDefinitionBase so they are tracked by their unique id, makes IEventManager disposable and disposes it on scope end. 2017-01-19 17:34:15 +11:00
Shannon 2a95ed3c7a no need for explicit implementation for this since we changed the old one 2017-01-19 17:29:36 +11:00
Shannon 9965aa461a Updates ContentService to raise events all with the EventManager 2017-01-19 17:18:06 +11:00
Shannon 8de8b53039 missing interface implementation 2017-01-19 16:58:10 +11:00
Shannon 27eeb383b4 Updates all repos to use IScopeUnitOfWork 2017-01-19 16:51:39 +11:00
Shannon 4f5347fa4d Creates IEventManager, IEventDefinition uses explicit implementation and 'new' keyword to force already existing uow providers to be IScopeUnitOfWork, creates extensions for the event manager so there's less work to do to wire it all up 2017-01-19 16:31:59 +11:00
Shannon 994f91e496 Creates IScopeUnitOfWork and IScopeUnitOfWorkProvider as internal along with ScopeRepositoryService which all services inherit from, this is maintaining backwards compat too 2017-01-19 15:37:17 +11:00
Stephan 4ab7f768fa U4-9322 - backport repository cache policy changes from v8 2017-01-18 18:48:13 +01:00
Stephan a020a02b26 U4-9322 - more fixes 2017-01-18 17:44:16 +01:00
Mads Rasmussen 49d24dada7 add partial views macros editor 2017-01-18 15:39:08 +01:00
Mads Rasmussen ef9dd6d803 add scripts editor 2017-01-18 15:38:40 +01:00
Stephan ca777b26de U4-9322 - manage isolation levels, cleanup tests 2017-01-18 14:53:24 +01:00
Stephan 1f636d05d1 U4-9322 - fix scope leaks 2017-01-18 10:36:11 +01:00
Stephan 7495cd3f2c U4-9322 - cleanup 2017-01-17 16:46:34 +01:00
Stephan 5f68f26d2f U4-9322 - scope and database, getting tests to run 2017-01-17 16:32:40 +01:00
Stephan 1261f6b159 U4-9322 - getting tests to run 2017-01-16 19:54:09 +01:00
Niels Hartvig a162f21a70 Fixing missing mapping - let the green build lights appear 2017-01-16 19:49:57 +01:00
Niels Hartvig ace111aefb Adding support for get, save and rename to new Partial View editor 2017-01-16 16:50:22 +01:00
Niels Hartvig dfe3798aab More merge 2017-01-16 09:59:00 +01:00
Niels Hartvig 975535a9e2 Merge of insert overlay view 2017-01-13 13:33:55 +01:00
Niels Hartvig e7f7631d1e Fixes merge conflicts (needs updated ng-if in insert overlay) 2017-01-13 13:22:43 +01:00
Stephan 5446e4b0fc U4-9322 - IScopeProviderInternal 2017-01-12 19:32:44 +01:00
Stephan 79c2400d27 U4-9322 - scope 2017-01-12 18:28:25 +01:00
Mads Rasmussen 23304aca0b add unit tests for template helper 2017-01-10 15:16:16 +01:00
Mads Rasmussen 845dd4673c Add client side file for partial view editor 2017-01-10 15:15:56 +01:00
Niels Hartvig 14e402878a Merge conflict fixes 2017-01-10 14:49:19 +01:00
Niels Hartvig 5a2687d520 Merge of temp-template-editor 2017-01-10 14:47:33 +01:00
Niels Hartvig 4c330c8654 Implements CodeFileController 2017-01-09 22:54:28 +01:00
421 changed files with 17420 additions and 6933 deletions
+41 -40
View File
@@ -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\net462\businesslogic.dll" />
<file src="..\_BuildOutput\WebApp\bin\businesslogic.xml" target="lib\net462\businesslogic.xml" />
<file src="..\_BuildOutput\WebApp\bin\cms.dll" target="lib\net462\cms.dll" />
<file src="..\_BuildOutput\WebApp\bin\cms.xml" target="lib\net462\cms.xml" />
<file src="..\_BuildOutput\WebApp\bin\controls.dll" target="lib\net462\controls.dll" />
<file src="..\_BuildOutput\WebApp\bin\controls.xml" target="lib\net462\controls.xml" />
<file src="..\_BuildOutput\WebApp\bin\interfaces.dll" target="lib\net462\interfaces.dll" />
<file src="..\_BuildOutput\WebApp\bin\interfaces.xml" target="lib\net462\interfaces.xml" />
<file src="..\_BuildOutput\WebApp\bin\log4net.dll" target="lib\net462\log4net.dll" />
<file src="..\_BuildOutput\WebApp\bin\Microsoft.ApplicationBlocks.Data.dll" target="lib\net462\Microsoft.ApplicationBlocks.Data.dll" />
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.dll" target="lib\net462\SQLCE4Umbraco.dll" />
<file src="..\_BuildOutput\WebApp\bin\SQLCE4Umbraco.xml" target="lib\net462\SQLCE4Umbraco.xml" />
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.dll" target="lib\net462\System.Data.SqlServerCe.dll" />
<file src="..\_BuildOutput\WebApp\bin\System.Data.SqlServerCe.Entity.dll" target="lib\net462\System.Data.SqlServerCe.Entity.dll" />
<file src="..\_BuildOutput\WebApp\bin\TidyNet.dll" target="lib\net462\TidyNet.dll" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.dll" target="lib\net462\Umbraco.Core.dll" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Core.xml" target="lib\net462\Umbraco.Core.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.dll" target="lib\net462\umbraco.DataLayer.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.DataLayer.xml" target="lib\net462\umbraco.DataLayer.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.dll" target="lib\net462\umbraco.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.xml" target="lib\net462\umbraco.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.dll" target="lib\net462\umbraco.editorControls.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.editorControls.xml" target="lib\net462\umbraco.editorControls.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.dll" target="lib\net462\umbraco.MacroEngines.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.MacroEngines.xml" target="lib\net462\umbraco.MacroEngines.xml" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.dll" target="lib\net462\umbraco.providers.dll" />
<file src="..\_BuildOutput\WebApp\bin\umbraco.providers.xml" target="lib\net462\umbraco.providers.xml" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.dll" target="lib\net462\Umbraco.Web.UI.dll" />
<file src="..\_BuildOutput\WebApp\bin\Umbraco.Web.UI.xml" target="lib\net462\Umbraco.Web.UI.xml" />
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.dll" target="lib\net462\UmbracoExamine.dll" />
<file src="..\_BuildOutput\WebApp\bin\UmbracoExamine.xml" target="lib\net462\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>
+3 -1
View File
@@ -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 -1
View File
@@ -1,3 +1,3 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.6.0
alpha055
alpha061
+2 -2
View File
@@ -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>
+1 -1
View File
@@ -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
{
+2 -2
View File
@@ -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
View File
@@ -12,4 +12,4 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.6.0")]
[assembly: AssemblyInformationalVersion("7.6.0-alpha055")]
[assembly: AssemblyInformationalVersion("7.6.0-alpha061")]
+12 -1
View File
@@ -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;
+48
View File
@@ -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
}
}
}
+8 -1
View File
@@ -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 "alpha055"; } }
public static string CurrentComment { get { return "alpha061"; } }
// 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
+7 -1
View File
@@ -112,9 +112,15 @@
public const string Languages = "languages";
public const string PartialViews = "partialViews";
public const string PartialViewMacros = "partialViewMacros";
public const string Scripts = "scripts";
//TODO: Fill in the rest!
}
}
}
}
+18 -1
View File
@@ -4,7 +4,7 @@ using Umbraco.Core.Models;
namespace Umbraco.Core
{
public static partial class Constants
{
@@ -37,6 +37,11 @@ namespace Umbraco.Core
public const string MemberGroup = "member-group";
public const string RelationType = "relation-type";
// forms
public const string FormsForm = "forms-form";
public const string FormsWorkflow = "forms-workflow";
public const string FormsRecord = "forms-record";
// string entity types
@@ -82,6 +87,12 @@ namespace Umbraco.Core
return Stylesheet;
case UmbracoObjectTypes.RelationType:
return RelationType;
case UmbracoObjectTypes.FormsForm:
return FormsForm;
case UmbracoObjectTypes.FormsWorkflow:
return FormsWorkflow;
case UmbracoObjectTypes.FormsRecord:
return FormsRecord;
}
throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType));
}
@@ -118,6 +129,12 @@ namespace Umbraco.Core
return UmbracoObjectTypes.Stylesheet;
case RelationType:
return UmbracoObjectTypes.RelationType;
case FormsForm:
return UmbracoObjectTypes.FormsForm;
case FormsWorkflow:
return UmbracoObjectTypes.FormsWorkflow;
case FormsRecord:
return UmbracoObjectTypes.FormsRecord;
}
throw new NotSupportedException(
string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType));
+30
View File
@@ -162,6 +162,36 @@ namespace Umbraco.Core
/// Guid for a relation type.
/// </summary>
public static readonly Guid RelationTypeGuid = new Guid(RelationType);
/// <summary>
/// Guid for a Forms Form.
/// </summary>
public const string FormsForm = "F5A9F787-6593-46F0-B8FF-BFD9BCA9F6BB";
/// <summary>
/// Guid for a Forms Form.
/// </summary>
public static readonly Guid FormsFormGuid = new Guid(FormsForm);
/// <summary>
/// Guid for a Forms Workflow.
/// </summary>
public const string FormsWorkflow = "42D7BF9B-A362-4FEE-B45A-674D5C064B70";
/// <summary>
/// Guid for a Forms Workflow.
/// </summary>
public static readonly Guid FormsWorkflowGuid = new Guid(FormsWorkflow);
/// <summary>
/// Guid for a Forms Record.
/// </summary>
public const string FormsRecord = "CFED6CE4-9359-443E-9977-9956FEB1D867";
/// <summary>
/// Guid for a Forms Record.
/// </summary>
public static readonly Guid FormsRecordGuid = new Guid(FormsRecord);
}
}
}
+10 -7
View File
@@ -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);
+111 -15
View File
@@ -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);
}
}
*/
}
}
@@ -11,12 +11,12 @@ namespace Umbraco.Core.Deploy
public interface IGridCellValueConnector
{
/// <summary>
/// Gets a value indicating whether the connector supports a specified grid editor alias.
/// Gets a value indicating whether the connector supports a specified grid editor view.
/// </summary>
/// <param name="alias">The grid editor alias.</param>
/// <remarks>A value indicating whether the connector supports the grid editor alias.</remarks>
/// <remarks>Note that <paramref name="alias" /> can be string.Empty to indicate the "default" connector.</remarks>
bool IsConnector(string alias);
/// <param name="view">The grid editor view. It needs to be the view instead of the alias as the view is really what identifies what kind of connector should be used. Alias can be anything and you can have multiple different aliases using the same kind of view.</param>
/// <remarks>A value indicating whether the connector supports the grid editor view.</remarks>
/// <remarks>Note that <paramref name="view" /> can be string.Empty to indicate the "default" connector.</remarks>
bool IsConnector(string view);
/// <summary>
/// Gets the value to be deployed from the control value as a string.
@@ -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);
}
}
}
+41 -1
View File
@@ -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);
}
}
}
+70 -3
View File
@@ -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>>
{
/// <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
}
}
+30 -61
View File
@@ -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;
}
}
}
@@ -0,0 +1,29 @@
namespace Umbraco.Core.Events
{
public enum EventsDispatchMode
{
// in 7.5 we'd do:
//
// using (var uow = ...)
// { ... }
// Done.RaiseEvent(...);
//
// and so the event would trigger only *after* the transaction has completed,
// so actually PassThrough is more aggressive than what we had in 7.5 and should
// not be used. now in 7.6 we do:
//
// using (var uow = ...)
// {
// ...
// uow.Events.Dispatch(Done, ...);
// }
//
// so the event can be collected, so the default "kinda compatible" more has to be
// the Scope mode, and Passive is for Deploy only
Unspecified = 0,
PassThrough, // both Doing and Done trigger immediately
Scope, // Doing triggers immediately, Done queued and triggered when & if the scope completes
Passive // Doing never triggers, Done queued and needs to be handled by custom code
}
}
+36 -2
View File
@@ -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,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);
}
}
+36 -2
View File
@@ -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);
}
}
}
+39 -1
View File
@@ -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);
}
}
}
+34 -1
View File
@@ -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);
}
}
}
+40 -1
View File
@@ -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);
}
}
}
+40 -1
View File
@@ -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)
{ }
}
}
+36 -2
View File
@@ -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);
}
}
}
+40 -1
View File
@@ -5,7 +5,7 @@ using Umbraco.Core.Models;
namespace Umbraco.Core.Events
{
public class RecycleBinEventArgs : CancellableEventArgs
public class RecycleBinEventArgs : CancellableEventArgs, IEquatable<RecycleBinEventArgs>
{
public RecycleBinEventArgs(Guid nodeObjectType, Dictionary<int, IEnumerable<Property>> allPropertyData, bool emptiedSuccessfully)
: base(false)
@@ -122,5 +122,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; }
}
}
}
+2
View File
@@ -116,5 +116,7 @@ namespace Umbraco.Core.Events
{
get { return EventObject; }
}
}
}
@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
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 : IEventDispatcher
{
private readonly EventsDispatchMode _mode;
private List<IEventDefinition> _events;
public ScopeEventDispatcher(EventsDispatchMode mode)
{
_mode = mode;
}
private List<IEventDefinition> Events { get { return _events ?? (_events = new List<IEventDefinition>()); } }
private bool PassThroughCancelable { get { return _mode == EventsDispatchMode.PassThrough || _mode == EventsDispatchMode.Scope; } }
private bool PassThrough { get { return _mode == EventsDispatchMode.PassThrough; } }
private bool RaiseEvents { get { return _mode == EventsDispatchMode.Scope; } }
public bool DispatchCancelable(EventHandler eventHandler, object sender, CancellableEventArgs args, string eventName = null)
{
if (eventHandler == null) return args.Cancel;
if (PassThroughCancelable == 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 (PassThroughCancelable == 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 (PassThroughCancelable == 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;
if (PassThrough)
eventHandler(sender, args);
else
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;
if (PassThrough)
eventHandler(sender, args);
else
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;
if (PassThrough)
eventHandler(sender, args);
else
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)
{
// fixme - we'd need to de-duplicate events somehow, etc - and the deduplication should be last in wins
if (_events == null) return;
if (RaiseEvents && completed)
foreach (var e in _events)
e.RaiseEvent();
_events.Clear();
}
}
}
+1 -1
View File
@@ -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
View File
@@ -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
+50 -3
View File
@@ -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;
}
}
}
+73
View File
@@ -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);
}
}
}
}
}
+12 -2
View File
@@ -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)
+1 -1
View File
@@ -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")]
+1
View File
@@ -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; }
}
}
+22 -1
View File
@@ -143,6 +143,27 @@ namespace Umbraco.Core.Models
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.RelationType)]
[FriendlyName("Relation Type")]
RelationType
RelationType,
/// <summary>
/// Forms Form
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.FormsForm)]
[FriendlyName("Form")]
FormsForm,
/// <summary>
/// Forms Workflow
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.FormsWorkflow)]
[FriendlyName("Workflow")]
FormsWorkflow,
/// <summary>
/// Forms Record
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.FormsRecord)]
[FriendlyName("Record")]
FormsRecord
}
}
+50
View File
@@ -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
}
}
}
@@ -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");
}
}
}
@@ -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");
}
}
}
@@ -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");
}
}
}
+12 -4
View File
@@ -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)
@@ -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,8 @@ namespace Umbraco.Core.Persistence
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)
@@ -267,6 +284,7 @@ namespace Umbraco.Core.Persistence
if (commitTrans)
{
if (tr == null) throw new ArgumentNullException("The transaction cannot be null if commitTrans is true");
tr.Complete();
}
return processed;
@@ -275,6 +293,7 @@ namespace Umbraco.Core.Persistence
{
if (commitTrans)
{
if (tr == null) throw new ArgumentNullException("The transaction cannot be null if commitTrans is true");
tr.Dispose();
}
throw;
@@ -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":
@@ -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)
{
}
@@ -2,19 +2,13 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Cache;
using Umbraco.Core.Events;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Relators;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Services;
namespace Umbraco.Core.Persistence.Repositories
{
@@ -25,23 +19,15 @@ namespace Umbraco.Core.Persistence.Repositories
{
private readonly ITemplateRepository _templateRepository;
public ContentTypeRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, ITemplateRepository templateRepository)
public ContentTypeRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, ITemplateRepository templateRepository)
: base(work, cache, logger, sqlSyntax)
{
_templateRepository = templateRepository;
}
private FullDataSetRepositoryCachePolicyFactory<IContentType, int> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IContentType, int> CachePolicyFactory
protected override IRepositoryCachePolicy<IContentType, int> CreateCachePolicy(IRuntimeCacheProvider runtimeCache)
{
get
{
//Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection
return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory<IContentType, int>(
RuntimeCache, GetEntityId, () => PerformGetAll(),
//allow this cache to expire
expires:true));
}
return new FullDataSetRepositoryCachePolicy<IContentType, int>(runtimeCache, GetEntityId, /*expires:*/ true);
}
protected override IContentType PerformGet(int id)
@@ -17,7 +17,7 @@ namespace Umbraco.Core.Persistence.Repositories
internal class ContentXmlRepository<TContent> : PetaPocoRepositoryBase<int, ContentXmlEntity<TContent>>
where TContent : IContentBase
{
public ContentXmlRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
public ContentXmlRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
}
@@ -29,12 +29,12 @@ namespace Umbraco.Core.Persistence.Repositories
private readonly IContentTypeRepository _contentTypeRepository;
private readonly DataTypePreValueRepository _preValRepository;
public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax,
public DataTypeDefinitionRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax,
IContentTypeRepository contentTypeRepository)
: base(work, cache, logger, sqlSyntax)
{
_contentTypeRepository = contentTypeRepository;
_preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax);
_preValRepository = new DataTypePreValueRepository(work, CacheHelper.NoCache, logger, sqlSyntax);
}
#region Overrides of RepositoryBase<int,DataTypeDefinition>
@@ -237,7 +237,7 @@ AND umbracoNode.id <> @id",
//NOTE: This is a special case, we need to clear the custom cache for pre-values here so they are not stale if devs
// are querying for them in the Saved event (before the distributed call cache is clearing it)
RuntimeCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id));
IsolatedCache.ClearCacheItem(GetPrefixedCacheKey(entity.Id));
entity.ResetDirtyProperties();
}
@@ -276,7 +276,7 @@ AND umbracoNode.id <> @id",
public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId)
{
var cached = RuntimeCache.GetCacheItemsByKeySearch<PreValueCollection>(GetPrefixedCacheKey(dataTypeId));
var cached = IsolatedCache.GetCacheItemsByKeySearch<PreValueCollection>(GetPrefixedCacheKey(dataTypeId));
if (cached != null && cached.Any())
{
//return from the cache, ensure it's a cloned result
@@ -295,7 +295,7 @@ AND umbracoNode.id <> @id",
{
//We need to see if we can find the cached PreValueCollection based on the cache key above
var cached = RuntimeCache.GetCacheItemsByKeyExpression<PreValueCollection>(GetCacheKeyRegex(preValueId));
var cached = IsolatedCache.GetCacheItemsByKeyExpression<PreValueCollection>(GetCacheKeyRegex(preValueId));
if (cached != null && cached.Any())
{
//return from the cache
@@ -463,7 +463,7 @@ AND umbracoNode.id <> @id",
+ string.Join(",", collection.FormatAsDictionary().Select(x => x.Value.Id).ToArray());
//store into cache
RuntimeCache.InsertCacheItem(key, () => collection,
IsolatedCache.InsertCacheItem(key, () => collection,
//30 mins
new TimeSpan(0, 0, 30),
//sliding is true
@@ -519,10 +519,9 @@ AND umbracoNode.id <> @id",
/// </summary>
private class DataTypePreValueRepository : PetaPocoRepositoryBase<int, PreValueEntity>
{
public DataTypePreValueRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
public DataTypePreValueRepository(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 PreValueEntity PerformGet(int id)
@@ -20,25 +20,19 @@ namespace Umbraco.Core.Persistence.Repositories
/// </summary>
internal class DictionaryRepository : PetaPocoRepositoryBase<int, IDictionaryItem>, IDictionaryRepository
{
public DictionaryRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax)
public DictionaryRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider syntax)
: base(work, cache, logger, syntax)
{
}
{ }
private IRepositoryCachePolicyFactory<IDictionaryItem, int> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IDictionaryItem, int> CachePolicyFactory
protected override IRepositoryCachePolicy<IDictionaryItem, int> CreateCachePolicy(IRuntimeCacheProvider runtimeCache)
{
get
var options = new RepositoryCachePolicyOptions
{
//custom cache policy which will not cache any results for GetAll
return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory<IDictionaryItem, int>(
RuntimeCache,
new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
}));
}
//allow zero to be cached
GetAllCacheAllowZeroCount = true
};
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, int>(runtimeCache, options);
}
#region Overrides of RepositoryBase<int,DictionaryItem>
@@ -52,7 +46,7 @@ namespace Umbraco.Core.Persistence.Repositories
var dto = Database.Fetch<DictionaryDto, LanguageTextDto, DictionaryDto>(new DictionaryLanguageTextRelator().Map, sql).FirstOrDefault();
if (dto == null)
return null;
var entity = ConvertFromDto(dto);
//on initial construction we don't want to have dirty properties tracked
@@ -80,7 +74,7 @@ namespace Umbraco.Core.Persistence.Repositories
var translator = new SqlTranslator<IDictionaryItem>(sqlClause, query);
var sql = translator.Translate();
sql.OrderBy<DictionaryDto>(x => x.UniqueId, SqlSyntax);
return Database.Fetch<DictionaryDto, LanguageTextDto, DictionaryDto>(new DictionaryLanguageTextRelator().Map, sql)
.Select(x => ConvertFromDto(x));
}
@@ -149,7 +143,7 @@ namespace Umbraco.Core.Persistence.Repositories
translation.Key = dictionaryItem.Key;
}
dictionaryItem.ResetDirtyProperties();
dictionaryItem.ResetDirtyProperties();
}
protected override void PersistUpdatedItem(IDictionaryItem entity)
@@ -182,8 +176,8 @@ namespace Umbraco.Core.Persistence.Repositories
entity.ResetDirtyProperties();
//Clear the cache entries that exist by uniqueid/item key
RuntimeCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.ItemKey));
RuntimeCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.Key));
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.ItemKey));
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.Key));
}
protected override void PersistDeletedItem(IDictionaryItem entity)
@@ -194,8 +188,8 @@ namespace Umbraco.Core.Persistence.Repositories
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = entity.Key });
//Clear the cache entries that exist by uniqueid/item key
RuntimeCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.ItemKey));
RuntimeCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.Key));
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.ItemKey));
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(entity.Key));
}
private void RecursiveDelete(Guid parentId)
@@ -209,8 +203,8 @@ namespace Umbraco.Core.Persistence.Repositories
Database.Delete<DictionaryDto>("WHERE id = @Id", new { Id = dto.UniqueId });
//Clear the cache entries that exist by uniqueid/item key
RuntimeCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(dto.Key));
RuntimeCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(dto.UniqueId));
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(dto.Key));
IsolatedCache.ClearCacheItem(GetCacheIdKey<IDictionaryItem>(dto.UniqueId));
}
}
@@ -223,7 +217,7 @@ namespace Umbraco.Core.Persistence.Repositories
var list = new List<IDictionaryTranslation>();
foreach (var textDto in dto.LanguageTextDtos)
{
{
if (textDto.LanguageId <= 0)
continue;
@@ -237,7 +231,8 @@ namespace Umbraco.Core.Persistence.Repositories
public IDictionaryItem Get(Guid uniqueId)
{
using (var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
// note: normal to use GlobalCache here since we're passing it to a new repository
using (var uniqueIdRepo = new DictionaryByUniqueIdRepository(this, UnitOfWork, GlobalCache, Logger, SqlSyntax))
{
return uniqueIdRepo.Get(uniqueId);
}
@@ -245,12 +240,13 @@ namespace Umbraco.Core.Persistence.Repositories
public IDictionaryItem Get(string key)
{
using (var keyRepo = new DictionaryByKeyRepository(this, UnitOfWork, RepositoryCache, Logger, SqlSyntax))
// note: normal to use GlobalCache here since we're passing it to a new repository
using (var keyRepo = new DictionaryByKeyRepository(this, UnitOfWork, GlobalCache, Logger, SqlSyntax))
{
return keyRepo.Get(key);
}
}
private IEnumerable<IDictionaryItem> GetRootDictionaryItems()
{
var query = Query<IDictionaryItem>.Builder.Where(x => x.ParentId == null);
@@ -293,7 +289,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
private readonly DictionaryRepository _dictionaryRepository;
public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
public DictionaryByUniqueIdRepository(DictionaryRepository dictionaryRepository, IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
_dictionaryRepository = dictionaryRepository;
@@ -329,20 +325,15 @@ namespace Umbraco.Core.Persistence.Repositories
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("id") + " in (@ids)";
}
private IRepositoryCachePolicyFactory<IDictionaryItem, Guid> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IDictionaryItem, Guid> CachePolicyFactory
protected override IRepositoryCachePolicy<IDictionaryItem, Guid> CreateCachePolicy(IRuntimeCacheProvider runtimeCache)
{
get
var options = new RepositoryCachePolicyOptions
{
//custom cache policy which will not cache any results for GetAll
return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory<IDictionaryItem, Guid>(
RuntimeCache,
new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
}));
}
//allow zero to be cached
GetAllCacheAllowZeroCount = true
};
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, Guid>(runtimeCache, options);
}
}
@@ -350,7 +341,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
private readonly DictionaryRepository _dictionaryRepository;
public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
public DictionaryByKeyRepository(DictionaryRepository dictionaryRepository, IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
_dictionaryRepository = dictionaryRepository;
@@ -386,23 +377,16 @@ namespace Umbraco.Core.Persistence.Repositories
return "cmsDictionary." + SqlSyntax.GetQuotedColumnName("key") + " in (@ids)";
}
private IRepositoryCachePolicyFactory<IDictionaryItem, string> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IDictionaryItem, string> CachePolicyFactory
protected override IRepositoryCachePolicy<IDictionaryItem, string> CreateCachePolicy(IRuntimeCacheProvider runtimeCache)
{
get
var options = new RepositoryCachePolicyOptions
{
//custom cache policy which will not cache any results for GetAll
return _cachePolicyFactory ?? (_cachePolicyFactory = new OnlySingleItemsRepositoryCachePolicyFactory<IDictionaryItem, string>(
RuntimeCache,
new RepositoryCachePolicyOptions
{
//allow zero to be cached
GetAllCacheAllowZeroCount = true
}));
}
//allow zero to be cached
GetAllCacheAllowZeroCount = true
};
return new SingleItemsOnlyRepositoryCachePolicy<IDictionaryItem, string>(runtimeCache, options);
}
}
}
}
@@ -18,20 +18,14 @@ namespace Umbraco.Core.Persistence.Repositories
internal class DomainRepository : PetaPocoRepositoryBase<int, IDomain>, IDomainRepository
{
public DomainRepository(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
public DomainRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax)
: base(work, cache, logger, sqlSyntax)
{
}
private FullDataSetRepositoryCachePolicyFactory<IDomain, int> _cachePolicyFactory;
protected override IRepositoryCachePolicyFactory<IDomain, int> CachePolicyFactory
protected override IRepositoryCachePolicy<IDomain, int> CreateCachePolicy(IRuntimeCacheProvider runtimeCache)
{
get
{
//Use a FullDataSet cache policy - this will cache the entire GetAll result in a single collection
return _cachePolicyFactory ?? (_cachePolicyFactory = new FullDataSetRepositoryCachePolicyFactory<IDomain, int>(
RuntimeCache, GetEntityId, () => PerformGetAll(), false));
}
return new FullDataSetRepositoryCachePolicy<IDomain, int>(runtimeCache, GetEntityId, /*expires:*/ false);
}
protected override IDomain PerformGet(int id)

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