Compare commits

..

238 Commits

Author SHA1 Message Date
Shannon Deminick 218f523022 Merge pull request #1843 from umbraco/temp-U4-9691
Fixes the Examine mgmt dashboard to look better and to detect if an i…
2017-04-03 16:28:36 +10:00
Shannon Deminick 50c6fb036a Merge pull request #1839 from umbraco/temp-uaasscrum-827
uaasscrum-827 - more robust umbraco.config
2017-04-03 16:19:27 +10:00
Shannon Deminick bc2115ff0a Merge pull request #1847 from umbraco/temp-u4-9683
Upgrade to ModelsBuilder 3.0.6
2017-04-03 16:03:13 +10:00
Shannon Deminick 9eee5c2bc0 Merge pull request #1840 from umbraco/temp-U4-9689
U4-9689 Content type description isn't always saved the same way
2017-04-03 15:53:30 +10:00
Mads Rasmussen ed3096642d Merge pull request #1844 from ryanology/temp-U4-9697
U4-9697 - Listview create new node ordering
2017-03-30 10:31:51 +02:00
Stephan 95508c4fb7 Upgrade nuspec accordingly 2017-03-30 09:32:22 +02:00
Stephan 4677faff38 Upgrade to ModelsBuilder 3.0.6 2017-03-30 08:54:01 +02:00
Shannon 725b3c5eca Merge branch 'dev-v7' into temp-U4-9691 2017-03-30 16:37:24 +11:00
Shannon 25f5179b29 updates nuspec 2017-03-30 16:36:54 +11:00
Shannon bf6d084f82 Merge branch 'dev-v7' into temp-U4-9691 2017-03-30 15:50:16 +11:00
Shannon 1a03bd9624 Logs an error if the index is rebuilding due to being corrupt 2017-03-30 15:49:45 +11:00
Shannon 6a1d60c5a7 U4-9692 Update to Examine 0.1.82 2017-03-30 15:43:06 +11:00
Shannon 4fef17190f adds notes 2017-03-30 11:03:07 +11:00
rrueckl 08ca75bb35 U4-9697 - Listview create new node ordering 2017-03-29 13:59:23 -04:00
Shannon 0512eaa7fd Fixes the Examine mgmt dashboard to look better and to detect if an index is unreadable in which case it allows you to rebuild it. 2017-03-29 21:43:00 +11:00
Claus e8df5f9eb4 U4-9689 Content type description isn't always saved the same way
also fixed for member types (and added test for media types also using the ContentTypeService)
2017-03-29 11:08:57 +02:00
Stephan c46678c766 uaasscrum-827 - more robust umbraco.config 2017-03-29 10:03:02 +02:00
Shannon 7cdb48b6c9 bumps version 2017-03-29 16:35:37 +11:00
Shannon Deminick 4ffa6bb41b Merge pull request #1824 from umbraco/temp-u4-9617
U4-9617 - perf for published date
2017-03-29 16:29:31 +11:00
Shannon 77af16db55 Merge branch 'dev-v7' into temp-u4-9617
# Conflicts:
#	src/Umbraco.Web/Models/Mapping/ContentModelMapper.cs
2017-03-29 16:29:18 +11:00
Stephan 85d85fa587 Merge pull request #1819 from umbraco/temp-u4-9623
U4-9323 - fix DataTypeDefinitionRepository prevalues cache
2017-03-28 17:37:04 +02:00
Claus 7ade64f855 Merge pull request #1802 from umbraco/temp-U4-9637
U4-9637 addDashboardSection package action shouldn't add the dashboar…
2017-03-28 13:12:26 +02:00
Shannon Deminick 346e8dcaf0 Merge pull request #1820 from umbraco/temp-u4-9615
U4-9615 - implement way to find listviews in path fast
2017-03-28 16:05:25 +11:00
Shannon ddfd07bf4a Fixes count query 2017-03-28 16:04:06 +11:00
Shannon a9fe46da0c makes HasContainerInPath public 2017-03-28 15:55:45 +11:00
Shannon aca1f0d186 Adds comments 2017-03-28 15:46:33 +11:00
Shannon 9eb415d398 U4-9682 Repository's have a double nested DeepCloneRuntimeCacheProvider set which means all entities are double deep cloned in and out of the cache and ensures that PreValueCollections are not also manually deep cloned. 2017-03-28 15:41:55 +11:00
Shannon 8f6b728534 Moves paging value to a constant 2017-03-28 13:37:10 +11:00
Shannon Deminick 2e0c09ab39 Merge pull request #1827 from umbraco/temp-u4-9598
U4-9598 - fix paging in UmbracoContentIndexer
2017-03-28 13:31:37 +11:00
Shannon Deminick f211344786 Merge pull request #1825 from umbraco/temp-u4-9604
U4-9604 - fix current HttpContext in ManyObjectsResolverBase
2017-03-28 13:11:09 +11:00
Stephan 68702b98f5 U4-9604 - add nullcheck 2017-03-27 08:52:10 +02:00
Stephan f93caf277b Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into temp-u4-9604 2017-03-27 08:47:40 +02:00
Shannon Deminick e0a7698780 Merge pull request #1821 from andyneil/dev-v7-lang-fixes
Dev v7 lang fixes
2017-03-27 14:20:41 +11:00
Shannon Deminick 783c74d3ba Merge pull request #1829 from abryukhov/patch-6
Adding missing keys before v7.6
2017-03-27 14:12:08 +11:00
Alexander Bryukhov 21963c03a2 Adding missing keys before v7.6 2017-03-24 21:59:02 +07:00
Stephan 879a132077 U4-9598 - fix paging in UmbracoContentIndexer 2017-03-24 13:05:06 +01:00
Stephan 606ebcae92 U4-9604 - fix current HttpContext in ManyObjectsResolverBase 2017-03-24 11:07:15 +01:00
Stephan af10ae9e34 U4-9617 - fix 2017-03-23 18:00:58 +01:00
Stephan a23c150738 U4-9617 - fix 2017-03-23 17:51:30 +01:00
Stephan 085a678266 U4-9617 - perf for published date 2017-03-23 17:44:07 +01:00
andyneil cf7cafeb4c Update wording to be more grammatically correct 2017-03-23 13:41:48 +00:00
andyneil 870657affa Update wording to be more grammatically correct 2017-03-23 13:41:23 +00:00
Stephan fe11d4770d U4-9615 - implement way to find listviews in path fast 2017-03-23 14:07:45 +01:00
Stephan c8fe9030c7 U4-9323 - fix DataTypeDefinitionRepository prevalues cache 2017-03-23 10:39:28 +01:00
Shannon Deminick 3d81c73495 Merge pull request #1814 from umbraco/temp-u4-9658
U4-9658 - detect umbraco.config rogue content types, and fix
2017-03-23 12:16:10 +11:00
Shannon e7a540e717 Proper Exception handling 2017-03-23 12:15:14 +11:00
Shannon Deminick bb886c237e Merge pull request #1815 from umbraco/temp-u4-9610
U4-9610 - fix nullref in BulkPublishController
2017-03-23 11:28:03 +11:00
Shannon 6c1e271fa8 Fixes styling and fixing error message quotes to make it more clear 2017-03-23 11:27:46 +11:00
Shannon Deminick 4c1b807e4f Merge pull request #1812 from umbraco/temp-u4-6811
U4-6811 - fix preview set deletion
2017-03-23 11:06:33 +11:00
Shannon 3b22c0cdd7 Catches correct expected exception type, we'll log for other non expected errors. 2017-03-23 11:06:06 +11:00
Stephan 631404743f U4-9610 - fix nullref in BulkPublishController 2017-03-22 14:44:39 +01:00
Stephan d5c9639170 U4-9658 - detect umbraco.config rogue content types, and fix 2017-03-22 13:58:40 +01:00
Stephan 94b7c36d62 U4-6811 - fix preview set deletion 2017-03-22 12:16:33 +01:00
Claus 532b177d2e Merge pull request #1793 from Phosworks/U4-8418
Fixes jquery selector bug, http://issues.umbraco.org/issue/U4-8418
2017-03-20 14:43:43 +01:00
Shannon 4093fd9b79 U4-9637 addDashboardSection package action shouldn't add the dashboard if it already exists 2017-03-18 16:52:30 +11:00
Shannon 3d6f91cbef Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2017-03-18 15:47:56 +11:00
Shannon fffd508bb2 missing null check 2017-03-18 15:44:57 +11:00
Stephan 333b35d8b9 U4-9606 - hard cap of 8 minidump files 2017-03-16 15:51:19 +01:00
Claus 890673a197 Merge pull request #1797 from umbraco/temp-u4-9606
U4-9606 - New DumpOnTimeoutThreadAbort debug option
2017-03-16 15:38:01 +01:00
Stephan 27539d4aad U4-9606 - always minidump on Monitor.ReliableEnter exceptions 2017-03-16 14:04:28 +01:00
Stephan f7f23015d0 U4-9606 - missing file 2017-03-15 09:39:35 +01:00
Stephan e432b7061d U4-9606 - New DumpOnTimeoutThreadAbort debug option 2017-03-14 19:02:33 +01:00
Wincent fc92d40b3a Fixes jquery selector bug, http://issues.umbraco.org/issue/U4-8418 2017-03-13 11:25:02 +01:00
Shannon dc71f9cd5e Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2017-03-13 12:39:27 +11:00
Shannon 99ed4d6cb9 Makes umbraco.GlobalSettings.UmbracoMvcArea available since we haven't been able to get the GlobalSettings into the right public classes yet. 2017-03-13 12:38:08 +11:00
Shannon Deminick 3646cfb47e Merge pull request #1791 from soreng/patch-3
Removed weird comment
2017-03-12 21:37:44 +01:00
Søren Gregersen 1b7ce8bcde Removed weird comment
removed "this" as remark for NewPassword
2017-03-12 21:20:24 +01:00
Sebastiaan Janssen 67c6146f96 Merge pull request #1787 from umbraco/temp-u4-9595
U4-9595 - improve log error msg on thread aborts on timeouts
2017-03-09 15:59:47 +01:00
Stephan b8c0d4c4ea U4-9595 - improve log error msg on thread aborts on timeouts 2017-03-09 11:35:06 +01:00
Sebastiaan Janssen 6c624ca08a Merge pull request #1785 from CreunaAB/fix/add-missing-sv-translation
Adds missing swedish translation for document type without template
2017-03-08 15:21:55 +01:00
nengberg 8a133938e6 Adds missing swedish translation for document type without template 2017-03-08 09:57:32 +01:00
hartvig c1b3f1977d Merge pull request #1768 from danieland/dev-v7
U4-9571: Legacy icons look weird
2017-03-07 18:58:13 +01:00
hartvig f086156d48 Merge pull request #1781 from majken92/dev-v7
Updating sv.xml with correct translation
2017-03-07 18:53:43 +01:00
hartvig 7080013dd6 Merge pull request #1777 from jamescoxhead/U4-9373
U4-9373 Fix for duplicate dashboards after upgrading
2017-03-07 18:45:50 +01:00
Michael Razmgah b080825bb2 Updating sv.xml with correct translation 2017-03-06 21:31:42 +01:00
Stephan 8bb696bf9a Merge pull request #1776 from umbraco/temp-U4-9587
U4-9587 RebuildXmlStructures doesn't clear out stale data so there is…
2017-03-06 14:51:50 +01:00
Shannon 55e8451513 changes to in groups of 2000 2017-03-06 14:44:41 +01:00
Shannon 7e4e2a1d5d Fixes query when rebuilding xml structures for a content type 2017-03-06 14:42:02 +01:00
Stephan 79d4956b75 Merge branch 'dev-v7' into temp-U4-9587 2017-03-03 13:26:43 +01:00
James Coxhead 9acfd8f5d2 Updated dashboard config transform
Fix for dupicate dashboards after upgrading
2017-03-02 21:02:25 +00:00
Shannon 0b530ce181 Fixes rebuilding xml structures when a content type is specified, we need to filter on that when looking for orphaned items. 2017-03-02 19:17:39 +01:00
Shannon 402835d72d Merge branch 'Colletz-U4-9573' into dev-v7 2017-03-02 18:45:06 +01:00
Shannon 08d579ec87 Fixes a bunch of id assignments in PublicAccessRepository along with clearing out old entries that are flagged for removal, adds a lot more assertions for retrieving items and their rules. 2017-03-02 18:44:42 +01:00
Shannon 8b92755487 Merge branch 'U4-9573' of https://github.com/Colletz/Umbraco-CMS into Colletz-U4-9573 2017-03-02 17:55:16 +01:00
Shannon 01b48107f3 ensures that the content type is not used in the query when it is null. 2017-03-02 17:45:21 +01:00
Shannon a546803970 Need to also update the health check check not just the old endpoint for non trashed media. 2017-03-02 17:40:39 +01:00
Shannon 96c28c2611 U4-9587 RebuildXmlStructures doesn't clear out stale data so there is unpublished or trashed items remaining in the xml table, xml data integrity check is misleading due to the media lookup 2017-03-02 17:28:34 +01:00
Shannon 65a22417f7 Merge branch 'dev-v7' of https://github.com/umbraco/Umbraco-CMS into dev-v7 2017-03-02 16:54:37 +01:00
Shannon 6c271e7af9 Fixes issue with U4-9456 when we use the withCache flag we do not add that to the defs collection so an error would occur if one was resolved from cache. 2017-03-02 16:54:25 +01:00
Stephan a85cafe7ab Merge pull request #1772 from umbraco/temp-U4-9577
U4-9577 Ability to store the xml content file in diff storage locatio…
2017-03-02 11:42:44 +01:00
Stephan 41d91ae791 Merge pull request #1758 from umbraco/temp-U4-9536
Updates Content/Entity repos to deal with cases where there is corrupt newest/published flags
2017-03-02 11:17:56 +01:00
Shannon Deminick dca5a26fe4 Merge pull request #1774 from umbraco/temp-9586-async-content-xml-save
Removes async from filestream instance
2017-03-02 10:49:31 +01:00
Shannon 7e8f6d391f Just to be sure, moves the text replacement outside of the using - though that wouldn't make any diff 2017-03-02 10:48:11 +01:00
Shannon 7aa00ee6e0 Removes unused method that also uses the async flag for file stream - which we know in some cases causes fcn issues, fixes streams not being closed property if there are exceptions 2017-03-02 10:37:04 +01:00
Niels Hartvig 2e027e6195 Removes async from filestream instance 2017-03-02 10:33:00 +01:00
Shannon 337ba4ef34 U4-9577 Ability to store the xml content file in diff storage locations: Default, Environment Temp or ASP.NET temp location 2017-02-28 23:53:12 +01:00
Colletz 0f32c2d680 PublicAccess - Ensure correct ordering on join
Ensured the correct order on JOIN for the subsequent mapping on AccessDto and AccessRuleDto.
2017-02-28 12:43:49 +01:00
Shannon 96a18ec8e9 Merge remote-tracking branch 'refs/remotes/origin/dev-v7' into temp-U4-9536
# Conflicts:
#	src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs
2017-02-27 15:24:55 +01:00
Shannon 65c39a3117 Fixes issue with returning multiple versions and mapping their property values, adds more assertions 2017-02-27 15:19:59 +01:00
Claus 15150f9a16 Merge pull request #1763 from umbraco/temp-U4-9545
U4-9545 EntityRepository performs poorly for Media due to unnecessary outer joins
2017-02-27 12:00:06 +01:00
Claus 259879c964 just cleaning some using statements and fixing a few typos. 2017-02-27 11:59:19 +01:00
Daniel Sune Andreasen ed87789abe collides with .legacy-custom-file 2017-02-27 11:14:37 +01:00
Shannon 09492fa779 Ensures that the published data retrieved uses the latest content version PK instead of by date in case dates are exactly the same. 2017-02-24 14:14:28 +11:00
Shannon 8122c0b352 Simplifies the includeAllVersions check 2017-02-24 14:00:38 +11:00
Shannon 28f0ab1001 Fixes U4-9456, ensures that property sets are mapped to a content version instead of an Id, adds more unit tests, ensures that if this warning ever occurs in a test that the test will fail. 2017-02-24 13:51:14 +11:00
Shannon 266f82b6c5 Fixes issue with returning all versions, adds more tests/assertions, updates content and media repository to double check that the versions match before returning from cache. 2017-02-24 13:23:27 +11:00
Shannon 813a8956d3 Gets EntityRepository to deal with corrupted newest/published flags, adds tests and lots of assertions for each method 2017-02-24 12:27:36 +11:00
Shannon Deminick 0a8fa2be4d Merge pull request #1761 from abjerner/patch-16
Added support for ignoring HTTP errors directly from the Angular $http options
2017-02-23 15:50:22 +11:00
Anders Bjerner 6e347dd32c Added some extra validation 2017-02-22 17:28:40 +01:00
Sebastiaan Janssen 33c9e2b58d Merge pull request #1767 from umbraco/temp-u4-9560
U4-9560 - fix db issue
2017-02-22 16:27:20 +01:00
Stephan 16f8f6036c U4-9560 - fix db issue 2017-02-22 15:52:02 +01:00
Sebastiaan Janssen 76b696e3bf U4-9559 Security: files of type xhtml should not be allowed to be uploaded to the media section 2017-02-22 11:12:24 +01:00
Shannon 7cadbb6398 Backports some required EntityRepository changes, starts adding the custom collection to remove duplicate (throw err currently) 2017-02-22 21:09:25 +11:00
Emil Wangaa bf22399aba Fixup linendings for EntityRepository 2017-02-22 10:34:46 +01:00
Sebastiaan Janssen 0b9a3ef68a Bump version 2017-02-22 10:26:37 +01:00
Shannon f8d6c7e7d0 Fixes expression tree changes 2017-02-22 18:34:55 +11:00
Shannon 90ba9a1a3a Adds new overload to IMediaService.GetPagedChildren to filter on media type ids which turns out to be much easier than filtering on media type aliases, however i did all of the work to make that happen including unit tests and then it turned out to not be required but we now have the code if necessary, i've left comments about it. I've backported some updates from 7.6 for the SqlIn stuff for the ExpressionVisitor which we still use in the media type id filtering. I updated the JS to query for the folders which all works now. 2017-02-22 17:22:22 +11:00
Shannon 6815731da1 adds failing test that i'll need to fix 2017-02-22 13:31:04 +11:00
Sebastiaan Janssen 52dce66598 Bump version 2017-02-21 08:49:34 +01:00
Shannon 255fec84bd Merge branch 'refs/heads/dev-v7' into temp-U4-9536 2017-02-21 17:56:39 +11:00
Shannon 9142314d39 U4-9522 Media recycle bin does not show folders 2017-02-21 14:18:49 +11:00
Shannon 27164093ea FIxes: U4-9547, Fixes: U4-9546 even though GetChildFolders isn't used anymore i made it work slightly faster 2017-02-20 18:08:55 +11:00
Shannon 67312d03d0 Fixes media query in EntityRepository, it doesn't require outer joins to get the published version, this also enhances the content query to do an inner join on cmsDocument to get the newest instead of an outer join. 2017-02-20 17:24:10 +11:00
Anders Bjerner ad9ae867c0 Added support for ignoring HTTP errors directly from the Angular $http options 2017-02-16 19:22:26 +01:00
Shannon af287c387e Adds test to show that the content repo can deal with corrupted data, this fix actually simplifies a bunch of logic too and should consume slightly less memory, have added some TODOs in the code and need to then write more tests and code to deal with this in the EntityRepository. 2017-02-16 18:54:44 +11:00
Sebastiaan Janssen acf10eeef0 U4-9532 logic in ContentRepository.ClearPublished is inefficient - cherry picked from 7.6.0 2017-02-16 08:11:35 +01:00
Sebastiaan Janssen 759e444624 Merge pull request #1467 from ArnoldV/dev-v7
U4-8990 Updated Dutch Language File
2017-02-15 22:39:44 +01:00
Sebastiaan Janssen a68f4e284a Merge pull request #1752 from umbraco/temp-U4-9517
U4-9517 BackOfficeSignInManager doesn't automatically keep LastLoginD…
2017-02-13 10:38:41 +01:00
Shannon d554417fde U4-9517 BackOfficeSignInManager doesn't automatically keep LastLoginDate set on users 2017-02-13 16:47:04 +11:00
Claus eedcfc122b Merge pull request #1747 from umbraco/temp-U4-9513
U4-9513 Empty recycle bin SQL logic can SQL to timeout due to query i…
2017-02-10 11:42:14 +01:00
Claus 8cf3110708 fixing typo and missing file ref in csproj. 2017-02-10 11:41:46 +01:00
Shannon dbe489c0f2 U4-9513 Empty recycle bin SQL logic can SQL to timeout due to query inefficiencies 2017-02-10 12:27:50 +11: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
Sebastiaan Janssen ade3c4ee6c Merge pull request #1732 from rsoeteman/U4-9493
U4 9493
2017-02-09 14:26:44 +01:00
Arnold Visser 7da7c1e5a1 Addes all new/missing fields since last edit
Checked against the English version
2017-02-09 13:47:31 +01: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
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 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
Sebastiaan Janssen 389b29599b U4-9495 ImageProcessor high CPU usage when trimCache=true 2017-02-07 10:29:11 +01: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 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
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
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
Sebastiaan Janssen 5a8e37f5b4 U4-9492 Update to latest ImageProcessor 2017-02-06 13:46:10 +01:00
Richard Soeteman a3c7b1fcfe Fixed issue U4-9494 2017-02-06 11:51:44 +01:00
Richard Soeteman 02785eea42 Fixed issue U4-9493 2017-02-06 11:26:58 +01: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
Stephan 49f3979d18 U4-9487 - optimize initialization of xml nodes 2017-02-03 19:28:43 +01:00
Shannon d598fdce54 Updates to latest Examine release 2017-02-03 12:03:56 +11:00
hartvig 32892cf5e0 Update NewInstallStep.cs 2017-02-02 10:05:14 +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
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
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
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
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 9d06ce53a4 U4-9444 SessionState is not available when rendering macro contents in the Rich Text Editor 2017-01-30 13:22:47 +11: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
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
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
Shannon c7b505fd90 Updates the paging logic so that the ContentRepository uses 2x queries: A full query and a query for getting Ids. This is important because the query for fetching ids is used for paging and for getting property data and it can be much much faster than the full query which was previously used for both. There's not really any changes to the media/members respositories since their full queries don't have certain outer joins that make them run really slow 2017-01-24 16:04:26 +11:00
Sebastiaan Janssen c5f7ac18fd Merge pull request #1702 from umbraco/temp-U4-9427
Fixes: U4-9427 Unable to install local packages in various browsers
2017-01-23 16:38:11 +01:00
Sebastiaan Janssen 10b8fd9fb9 Merge pull request #1701 from umbraco/temp-U4-9420
U4-9420 Re-indexing Examine Indexes after Upgrade to 7.5.7 from 7.3.4 takes minutes when it used to take seconds
2017-01-23 16:18:45 +01:00
Mads Rasmussen 24c053d671 Fixes: U4-9427 Unable to install local packages in various browsers 2017-01-23 16:11:09 +01:00
Shannon 44fc8be49e Changes back to "path" default sorting but updates the UmbracoContentIndexer to ensure it's sorted by umbracoNode.id 2017-01-24 00:35:17 +11:00
Shannon 2a4e73c650 Reduced allocations for the Media/Content/Property Factories since we don't need to create new objects every time we want to map values.
Changes default sort order for paging from Path to umbracoNode.id since we have no index on Path and it doesn't make a lot of sense.
Fixes obsolete warnings for various Sql usages.
Reduces the amount of DeepClone calls required when looking up a content type for a content item, even though these are cached they are still deep cloned out of the cache.
Fixes the main issue of having nearly 100,000 rows of unsorted property data and then having to query those rows for every document being built, the re-iteration of these rows causes a lot of overhead and is unecessary, instead we ensure the property data set and the document data set is sorted by node id, then use a stored index to continue looking up the property data for the next content item found.
2017-01-23 00:40:24 +11:00
Sebastiaan Janssen f0a540eba2 Merge branch 'master-v7' into dev-v7 2017-01-20 09:55:50 +01:00
Sebastiaan Janssen 12922f8413 No need to warn if attribute is not present
Adds some logging about indexing being done.. the number of committed items seems to differ a little from the number of items in the index, not sure wh
2017-01-20 09:55:41 +01:00
Sebastiaan Janssen 85f054e3c1 No need to warn if attribute is not present
Adds some logging about indexing being done.. the number of committed items seems to differ a little from the number of items in the index, not sure wh
2017-01-20 09:54:59 +01:00
Sebastiaan Janssen 470bf86ec0 Merge branch 'dev-v7' into master-v7 2017-01-19 11:51:05 +01:00
Morten Christensen 9367f61952 Merge pull request #1697 from umbraco/temp-U4-9414
U4-9414 EntityContainer repository PerformGetAll query returns nothing
2017-01-19 11:49:19 +01:00
Sebastiaan Janssen 3cabd8c318 Merge branch 'dev-v7' into master-v7 2017-01-19 11:40:11 +01:00
Claus 4119380af5 U4-9414 EntityContainer repository PerformGetAll query returns nothing 2017-01-19 11:39:09 +01:00
Sebastiaan Janssen fc3b08e374 Merge pull request #1626 from Mark320/U4-9192
U4-9192 Add jQuery migrate for user controls used in tabs
2017-01-19 11:35:44 +01:00
Sebastiaan Janssen a57abce96e Manually applying https://github.com/umbraco/Umbraco-CMS/pull/1688 2017-01-19 11:30:14 +01:00
Sebastiaan Janssen b260f6c6de Merge pull request #1679 from umbraco/U-9333
Add support for GUIDs in media and content pickers
2017-01-19 11:25:34 +01:00
Shannon c96e017e65 Fixes PocoToSqlExpressionVisitor (oops), fixes examine tests by using the correct usings and NRT indexer/searcher to avoid waiting on thread timers 2017-01-19 14:00:33 +11:00
Shannon 4f596d20a9 refactors the fix for U4-9398 so it's not iterating multiple times over the descendants Enumerable 2017-01-19 13:13:49 +11:00
Shannon Deminick 343e462473 Merge pull request #1695 from mahgo/PerformIndexAll_paging_fix
Fixed UmbracoContentIndexer's PerformIndexAll SupportUnpublishedConte…
2017-01-19 13:10:30 +11:00
Shannon db414e8045 Fixes issue with implicitly non-published items getting into the index, fixes issue with sql syntax columns (i.e for mysql) for queries that query the cmsContentXml table 2017-01-19 12:51:56 +11:00
Sebastiaan Janssen 54e460cf5b U4-9392 Upgrade ImageProcessor with cache header and high CPU fix
U4-9393 Upgrade to Examine 0.1.80
2017-01-18 11:47:23 +01:00
Aaron Sturmfels ba64768d8d Fixed UmbracoContentIndexer's PerformIndexAll SupportUnpublishedContent = true paging breaking out of the loop early if filtering by node types. 2017-01-18 21:12:30 +11:00
Warren Buckley 6710ac8c85 Fix up camelCase method name to GetChildren 2017-01-18 09:43:21 +00:00
Warren Buckley 1f98b76da0 On app.authenticated JS event - we clear out LocalStorage values in case a different user is logging in 2017-01-18 09:34:02 +00:00
Sebastiaan Janssen 519eb9d641 Merge pull request #1694 from umbraco/temp-U4-9395
Temp u4 9395
2017-01-18 08:56:08 +01:00
Sebastiaan Janssen 931db6f0b1 U4-9395 - Handle errors with non-existing fields and invalide JSON data 2017-01-17 16:47:54 +01:00
Sebastiaan Janssen f24c56d1ba Merge pull request #1692 from umbraco/temp-U4-9395
U4-9395 When rebuilding content indexes that don't support unpublishe…
2017-01-17 11:45:17 +01:00
Sebastiaan Janssen 44608c3122 Merge branch 'dev-v7' into temp-U4-9395 2017-01-17 10:46:04 +01:00
Stephan 90f2fc4cd9 Merge pull request #1686 from umbraco/temp-U4-9388-2
Finds all other instances where a reader would be executed inside ano…
2017-01-17 08:18:54 +01:00
Shannon 24c7dee5d0 U4-9395 When rebuilding content indexes that don't support unpublished content and member indexes, use the cmsContentXml table as the data source 2017-01-17 17:57:49 +11:00
Shannon Deminick 12417c0c48 Merge pull request #1691 from umbraco/temp-UAASSCRUM-712
adds more logging for when indexes are rebuilt
2017-01-17 16:40:02 +11:00
Shannon Deminick d4919ff8c4 Merge pull request #1683 from umbraco/temp-U4-9371
U4-9371 Examine indexes inherited unpublished nodes on index rebuild …
2017-01-17 16:34:31 +11:00
Shannon b2acb97639 adds stopwatch timer to logging 2017-01-17 16:29:30 +11:00
Shannon 28debbda1b adds more logging for when indexes are rebuilt 2017-01-17 16:21:43 +11:00
Niels Hartvig c0a2a78f3d Fixes issue with entities not being returned from Keys (Guids) 2017-01-16 19:13:05 +01:00
Sebastiaan Janssen 3f7bfe0b3a Merge pull request #1685 from jamiepollock/U4-9387
U4-9387: Umbraco v7.5.x: Package Icon doesn't appear on Installed Packages screen when an icon URL is populated
2017-01-16 12:58:39 +01:00
Claus e0d281bb4b Merge pull request #1616 from abryukhov/patch-6
Update UI lang file for ru-ru
2017-01-16 11:26:23 +01:00
Sebastiaan Janssen b7d37af933 Bumps version 2017-01-16 11:04:28 +01:00
Claus 3f9cfbb422 Merge pull request #1642 from readingdancer/ChrisH-TypoFix
Small English grammar error.
2017-01-16 08:12:59 +01:00
Claus 2f3d926c08 Merge pull request #1638 from enkelmedia/dev-v7
Fixed english typo for modelsbuilder
2017-01-16 08:11:45 +01:00
Claus 594c3b8a85 Merge pull request #1630 from abjerner/patch-14
Typo (sorta)
2017-01-16 08:11:09 +01:00
Shannon Deminick 41244485b1 Merge pull request #1684 from umbraco/temp-U4-9377
U4-9377 It is possible to create/save entities with a blank names by …
2017-01-16 17:40:12 +11:00
Shannon ede342feba Finds all other instances where a reader would be executed inside another reader's loop and fixes 2017-01-16 16:38:23 +11:00
jamiepollock a15bdba5d7 View fix for icons not appearing in the installed package list 2017-01-15 18:26:20 -05:00
Claus 7d4a9998ca U4-9377 It is possible to create/save entities with a blank names by using the services directly
ensuring that when trying to save an entity with an empty name, directly through a service, an exception will be thrown.
2017-01-13 11:45:49 +01:00
Shannon bde439d102 fixes tests 2017-01-13 12:34:58 +11:00
Shannon ba501dbbb8 U4-9371 Examine indexes inherited unpublished nodes on index rebuild part 2 2017-01-13 10:43:34 +11:00
Niels Hartvig f75e0cf4ba Updates to PR based on Review 2017-01-10 12:47:00 +01:00
Sebastiaan Janssen 0dc3bff01a Merge branch 'dev-v7' into master-v7 2017-01-10 07:31:08 +01:00
Sebastiaan Janssen 77969960d9 Merge branch 'dev-v7' into master-v7
# Conflicts:
#	build/UmbracoVersion.txt
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
#	src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
2017-01-09 16:26:07 +01:00
Niels Hartvig 1a9f336a59 Add support for GUIDs in media and content pickers 2017-01-09 14:45:40 +01:00
Sebastiaan Janssen 5bc66c5b7f Bumps the version 2016-12-07 14:18:27 +01:00
Chris Houston 90136c524c Small English grammar error. 2016-12-06 11:05:59 +00:00
Enkel Media 8d7f496f7c Merge pull request #1 from enkelmedia/enkelmedia-patch-1
Fixed simple typo for modelsbuilder
2016-11-30 11:18:25 +01:00
Enkel Media f62a9ed199 Fixed simple typo for modelsbuilder 2016-11-30 11:17:21 +01:00
Anders Bjerner bc8b8a69b8 Typo (sorta) 2016-11-21 10:26:08 +01:00
Mark320 9de3acfae2 U4-9192 Add jQuery migrate for user controls used in tabs that will improve backwards compatibility 2016-11-17 14:24:46 +00:00
Alexander Bryukhov 87814fa43c Update UI lang file for ru-ru
All latest 7.5.5 changes
2016-11-12 12:12:41 +07:00
Arnold Visser ca1f43d048 Merge pull request #1 from ArnoldV/Dutch-Language-Update
Dutch Languagefile Update
2016-09-06 10:53:49 +02:00
Arnold Visser e962a48138 Dutch Languagefile Update
This updates the Dutch language file with new/missing translations and fixes some wrong translations used by the grid.
2016-09-06 10:53:09 +02:00
202 changed files with 6543 additions and 2375 deletions
+5 -5
View File
@@ -28,17 +28,17 @@
<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)" />
<dependency id="ClientDependency-Mvc5" version="[1.8.0.0, 2.0.0)" />
<!-- AutoMapper can not be updated due to: https://github.com/AutoMapper/AutoMapper/issues/373#issuecomment-127644405 -->
<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.70, 1.0.0)" />
<dependency id="ImageProcessor" version="[2.5.1, 3.0.0)" />
<dependency id="ImageProcessor.Web" version="[4.7.2, 5.0.0)" />
<dependency id="Examine" version="[0.1.82, 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="[1.1.2, 2.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)" />
</dependencies>
</metadata>
<files>
+3 -2
View File
@@ -17,8 +17,8 @@
<dependencies>
<dependency id="UmbracoCms.Core" version="[$version$]" />
<dependency id="Newtonsoft.Json" version="[6.0.8, 10.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.5, 4.0.0)" />
<dependency id="ImageProcessor.Web.Config" version="[2.3.0, 3.0.0)" />
<dependency id="Umbraco.ModelsBuilder" version="[3.0.6, 4.0.0)" />
<dependency id="ImageProcessor.Web.Config" version="[2.3.0, 3.0.0)" />
</dependencies>
</metadata>
<files>
@@ -45,6 +45,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>
@@ -41,16 +41,6 @@
views/dashboard/developer/examinemanagement.html
</control>
</tab>
<tab caption="Health Check" xdt:Transform="InsertIfMissing" xdt:Locator="Match(caption)">
<control>
views/dashboard/developer/healthcheck.html
</control>
</tab>
<tab caption="Redirect URL Management" xdt:Transform="InsertIfMissing" xdt:Locator="Match(caption)">
<control>
views/dashboard/developer/redirecturls.html
</control>
</tab>
</section>
<section alias="StartupMediaDashboardSection" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
@@ -80,4 +70,26 @@
<tab caption="Change Password" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
<tab caption="Last Edits" xdt:Locator="Match(caption)" xdt:Transform="Remove" />
</section>
<section alias="RedirectUrlManagement" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
<areas>
<area>content</area>
</areas>
<tab caption="Redirect URL Management">
<control>
views/dashboard/developer/redirecturls.html
</control>
</tab>
</section>
<section alias="UmbracoHealthCheck" xdt:Locator="Match(alias)" xdt:Transform="InsertIfMissing">
<areas>
<area>developer</area>
</areas>
<tab caption="Health Check">
<control>
views/dashboard/developer/healthcheck.html
</control>
</tab>
</section>
</dashBoard>
@@ -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,2 +1,2 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.5.7
7.5.12
+2 -2
View File
@@ -11,5 +11,5 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.5.7")]
[assembly: AssemblyInformationalVersion("7.5.7")]
[assembly: AssemblyFileVersion("7.5.12")]
[assembly: AssemblyInformationalVersion("7.5.12")]
+2 -1
View File
@@ -64,7 +64,8 @@ namespace Umbraco.Core.Cache
[UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
public const string ContentTypePropertiesCacheKey = "ContentType_PropertyTypes_Content:";
[UmbracoWillObsolete("This cache key is only used for legacy business logic caching, remove in v8")]
[Obsolete("No longer used and will be removed in v8")]
[EditorBrowsable(EditorBrowsableState.Never)]
public const string PropertyTypeCacheKey = "UmbracoPropertyTypeCache";
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
@@ -26,6 +26,9 @@ namespace Umbraco.Core.Cache
public DeepCloneRuntimeCacheProvider(IRuntimeCacheProvider innerProvider)
{
if (innerProvider.GetType() == typeof(DeepCloneRuntimeCacheProvider))
throw new InvalidOperationException("A " + typeof(DeepCloneRuntimeCacheProvider) + " cannot wrap another instance of " + typeof(DeepCloneRuntimeCacheProvider));
InnerProvider = innerProvider;
}
@@ -105,9 +108,11 @@ namespace Umbraco.Core.Cache
var value = result.Value; // force evaluation now - this may throw if cacheItem throws, and then nothing goes into cache
if (value == null) return null; // do not store null values (backward compat)
//Clone/reset to go into the cache
return CheckCloneableAndTracksChanges(value);
}, timeout, isSliding, priority, removedCallback, dependentFiles);
//Clone/reset to go out of the cache
return CheckCloneableAndTracksChanges(cached);
}
@@ -0,0 +1,9 @@
namespace Umbraco.Core.Configuration
{
internal enum ContentXmlStorage
{
Default,
AspNetTemp,
EnvironmentTemp
}
}
@@ -0,0 +1,27 @@
using System;
namespace Umbraco.Core.Configuration
{
internal static class CoreDebugExtensions
{
private static CoreDebug _coreDebug;
public static CoreDebug CoreDebug(this UmbracoConfig config)
{
return _coreDebug ?? (_coreDebug = new CoreDebug());
}
}
internal class CoreDebug
{
public CoreDebug()
{
var appSettings = System.Configuration.ConfigurationManager.AppSettings;
DumpOnTimeoutThreadAbort = string.Equals("true", appSettings["Umbraco.CoreDebug.DumpOnTimeoutThreadAbort"], StringComparison.OrdinalIgnoreCase);
}
// when true, the Logger creates a minidump of w3wp in ~/App_Data/MiniDump whenever it logs
// an error due to a ThreadAbortException that is due to a timeout.
public bool DumpOnTimeoutThreadAbort { get; private set; }
}
}
@@ -520,12 +520,25 @@ namespace Umbraco.Core.Configuration
}
internal static bool ContentCacheXmlStoredInCodeGen
{
get { return ContentCacheXmlStorageLocation == ContentXmlStorage.AspNetTemp; }
}
internal static ContentXmlStorage ContentCacheXmlStorageLocation
{
get
{
//defaults to false
return ConfigurationManager.AppSettings.ContainsKey("umbracoContentXMLUseLocalTemp")
&& bool.Parse(ConfigurationManager.AppSettings["umbracoContentXMLUseLocalTemp"]); //default to false
if (ConfigurationManager.AppSettings.ContainsKey("umbracoContentXMLStorage"))
{
return Enum<ContentXmlStorage>.Parse(ConfigurationManager.AppSettings["umbracoContentXMLStorage"]);
}
if (ConfigurationManager.AppSettings.ContainsKey("umbracoContentXMLUseLocalTemp"))
{
return bool.Parse(ConfigurationManager.AppSettings["umbracoContentXMLUseLocalTemp"])
? ContentXmlStorage.AspNetTemp
: ContentXmlStorage.Default;
}
return ContentXmlStorage.Default;
}
}
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration
{
public class UmbracoVersion
{
private static readonly Version Version = new Version("7.5.7");
private static readonly Version Version = new Version("7.5.12");
/// <summary>
/// Gets the current version of Umbraco.
+1 -1
View File
@@ -415,7 +415,7 @@ namespace Umbraco.Core
if (currentTry == 5)
{
throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but the Umbraco cannot connect to the database.");
throw new UmbracoStartupFailedException("Umbraco cannot start. A connection string is configured but Umbraco cannot connect to the database.");
}
}
+4 -9
View File
@@ -203,15 +203,10 @@ namespace Umbraco.Core
var path = Path.Combine(GlobalSettings.FullpathToRoot, "App_Data", "Umbraco.sdf");
if (File.Exists(path) == false)
{
var engine = new SqlCeEngine(connectionString);
engine.CreateDatabase();
// SD: Pretty sure this should be in a using clause but i don't want to cause unknown side-effects here
// since it's been like this for quite some time
//using (var engine = new SqlCeEngine(connectionString))
//{
// engine.CreateDatabase();
//}
using (var engine = new SqlCeEngine(connectionString))
{
engine.CreateDatabase();
}
}
Initialize(providerName);
+135
View File
@@ -0,0 +1,135 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Umbraco.Core.IO;
namespace Umbraco.Core.Diagnostics
{
// taken from https://blogs.msdn.microsoft.com/dondu/2010/10/24/writing-minidumps-in-c/
// and https://blogs.msdn.microsoft.com/dondu/2010/10/31/writing-minidumps-from-exceptions-in-c/
// which itself got it from http://blog.kalmbach-software.de/2008/12/13/writing-minidumps-in-c/
internal static class MiniDump
{
private static readonly object LockO = new object();
[Flags]
public enum Option : uint
{
// From dbghelp.h:
Normal = 0x00000000,
WithDataSegs = 0x00000001,
WithFullMemory = 0x00000002,
WithHandleData = 0x00000004,
FilterMemory = 0x00000008,
ScanMemory = 0x00000010,
WithUnloadedModules = 0x00000020,
WithIndirectlyReferencedMemory = 0x00000040,
FilterModulePaths = 0x00000080,
WithProcessThreadData = 0x00000100,
WithPrivateReadWriteMemory = 0x00000200,
WithoutOptionalData = 0x00000400,
WithFullMemoryInfo = 0x00000800,
WithThreadInfo = 0x00001000,
WithCodeSegs = 0x00002000,
WithoutAuxiliaryState = 0x00004000,
WithFullAuxiliaryState = 0x00008000,
WithPrivateWriteCopyMemory = 0x00010000,
IgnoreInaccessibleMemory = 0x00020000,
ValidTypeFlags = 0x0003ffff,
}
//typedef struct _MINIDUMP_EXCEPTION_INFORMATION {
// DWORD ThreadId;
// PEXCEPTION_POINTERS ExceptionPointers;
// BOOL ClientPointers;
//} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION;
[StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64!
public struct MiniDumpExceptionInformation
{
public uint ThreadId;
public IntPtr ExceptionPointers;
[MarshalAs(UnmanagedType.Bool)]
public bool ClientPointers;
}
//BOOL
//WINAPI
//MiniDumpWriteDump(
// __in HANDLE hProcess,
// __in DWORD ProcessId,
// __in HANDLE hFile,
// __in MINIDUMP_TYPE DumpType,
// __in_opt PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
// __in_opt PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam,
// __in_opt PMINIDUMP_CALLBACK_INFORMATION CallbackParam
// );
// Overload requiring MiniDumpExceptionInformation
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam);
// Overload supporting MiniDumpExceptionInformation == NULL
[DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam);
[DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)]
private static extern uint GetCurrentThreadId();
private static bool Write(SafeHandle fileHandle, Option options, bool withException = false)
{
var currentProcess = Process.GetCurrentProcess();
var currentProcessHandle = currentProcess.Handle;
var currentProcessId = (uint)currentProcess.Id;
MiniDumpExceptionInformation exp;
exp.ThreadId = GetCurrentThreadId();
exp.ClientPointers = false;
exp.ExceptionPointers = IntPtr.Zero;
if (withException)
exp.ExceptionPointers = Marshal.GetExceptionPointers();
var bRet = exp.ExceptionPointers == IntPtr.Zero
? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero)
: MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, ref exp, IntPtr.Zero, IntPtr.Zero);
return bRet;
}
public static bool Dump(Option options = Option.WithFullMemory, bool withException = false)
{
lock (LockO)
{
// work around "stack trace is not available while minidump debugging",
// by making sure a local var (that we can inspect) contains the stack trace.
// getting the call stack before it is unwound would require a special exception
// filter everywhere in our code = not!
var stacktrace = withException ? Environment.StackTrace : string.Empty;
var filepath = IOHelper.MapPath("~/App_Data/MiniDump");
if (Directory.Exists(filepath) == false)
Directory.CreateDirectory(filepath);
var filename = Path.Combine(filepath, string.Format("{0:yyyyMMddTHHmmss}.{1}.dmp", DateTime.UtcNow, Guid.NewGuid().ToString("N").Substring(0, 4)));
using (var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Write))
{
return Write(stream.SafeFileHandle, options, withException);
}
}
}
public static bool OkToDump()
{
lock (LockO)
{
var filepath = IOHelper.MapPath("~/App_Data/MiniDump");
if (Directory.Exists(filepath) == false) return true;
var count = Directory.GetFiles(filepath, "*.dmp").Length;
return count < 8;
}
}
}
}
+18 -4
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.IO;
using System.Linq;
@@ -72,15 +73,28 @@ namespace Umbraco.Core.IO
{
get
{
if (GlobalSettings.ContentCacheXmlStoredInCodeGen && SystemUtilities.GetCurrentTrustLevel() == AspNetHostingPermissionLevel.Unrestricted)
{
return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config");
switch (GlobalSettings.ContentCacheXmlStorageLocation)
{
case ContentXmlStorage.AspNetTemp:
return Path.Combine(HttpRuntime.CodegenDir, @"UmbracoData\umbraco.config");
case ContentXmlStorage.EnvironmentTemp:
var appDomainHash = HttpRuntime.AppDomainAppId.ToSHA1();
var cachePath = Path.Combine(Environment.ExpandEnvironmentVariables("%temp%"), "UmbracoXml",
//include the appdomain hash is just a safety check, for example if a website is moved from worker A to worker B and then back
// to worker A again, in theory the %temp% folder should already be empty but we really want to make sure that its not
// utilizing an old path
appDomainHash);
return Path.Combine(cachePath, "umbraco.config");
case ContentXmlStorage.Default:
return IOHelper.ReturnPath("umbracoContentXML", "~/App_Data/umbraco.config");
default:
throw new ArgumentOutOfRangeException();
}
return IOHelper.ReturnPath("umbracoContentXML", "~/App_Data/umbraco.config");
}
}
[Obsolete("Use GlobalSettings.ContentCacheXmlStoredInCodeGen instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
internal static bool ContentCacheXmlStoredInCodeGen
{
get { return GlobalSettings.ContentCacheXmlStoredInCodeGen; }
+65 -8
View File
@@ -2,10 +2,13 @@
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Web;
using log4net;
using log4net.Config;
using Umbraco.Core.Configuration;
using Umbraco.Core.Diagnostics;
namespace Umbraco.Core.Logging
{
@@ -56,18 +59,72 @@ namespace Umbraco.Core.Logging
internal ILog LoggerFor(object getTypeFromInstance)
{
if (getTypeFromInstance == null) throw new ArgumentNullException("getTypeFromInstance");
return LogManager.GetLogger(getTypeFromInstance.GetType());
}
public void Error(Type callingType, string message, Exception exception)
{
var logger = LogManager.GetLogger(callingType);
if (logger != null)
logger.Error((message), exception);
var logger = LogManager.GetLogger(callingType);
if (logger == null) return;
var dump = false;
if (IsTimeoutThreadAbortException(exception))
{
message += "\r\nThe thread has been aborted, because the request has timed out.";
// dump if configured, or if stacktrace contains Monitor.ReliableEnter
dump = UmbracoConfig.For.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception);
// dump if it is ok to dump (might have a cap on number of dump...)
dump &= MiniDump.OkToDump();
}
if (dump)
{
try
{
var dumped = MiniDump.Dump(withException: true);
message += dumped
? "\r\nA minidump was created in App_Data/MiniDump"
: "\r\nFailed to create a minidump";
}
catch (Exception e)
{
message += string.Format("\r\nFailed to create a minidump ({0}: {1})", e.GetType().FullName, e.Message);
}
}
logger.Error(message, exception);
}
public void Warn(Type callingType, string message, params Func<object>[] formatItems)
private static bool IsMonitorEnterThreadAbortException(Exception exception)
{
var abort = exception as ThreadAbortException;
if (abort == null) return false;
var stacktrace = abort.StackTrace;
return stacktrace.Contains("System.Threading.Monitor.ReliableEnter");
}
private static bool IsTimeoutThreadAbortException(Exception exception)
{
var abort = exception as ThreadAbortException;
if (abort == null) return false;
if (abort.ExceptionState == null) return false;
var stateType = abort.ExceptionState.GetType();
if (stateType.FullName != "System.Web.HttpApplication+CancelModuleException") return false;
var timeoutField = stateType.GetField("_timeout", BindingFlags.Instance | BindingFlags.NonPublic);
if (timeoutField == null) return false;
return (bool) timeoutField.GetValue(abort.ExceptionState);
}
public void Warn(Type callingType, string message, params Func<object>[] formatItems)
{
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsWarnEnabled == false) return;
@@ -82,7 +139,7 @@ namespace Umbraco.Core.Logging
if (showHttpTrace && HttpContext.Current != null)
{
HttpContext.Current.Trace.Warn(callingType.Name, string.Format(message, formatItems.Select(x => x.Invoke()).ToArray()));
}
}
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsWarnEnabled == false) return;
@@ -99,7 +156,7 @@ namespace Umbraco.Core.Logging
var logger = LogManager.GetLogger(callingType);
if (logger == null || logger.IsWarnEnabled == false) return;
var executedParams = formatItems.Select(x => x.Invoke()).ToArray();
logger.WarnFormat((message) + ". Exception: " + e, executedParams);
logger.WarnFormat((message) + ". Exception: " + e, executedParams);
}
/// <summary>
+3
View File
@@ -274,6 +274,9 @@ namespace Umbraco.Core.Models
/// </summary>
public bool HasPublishedVersion { get { return PublishedVersionGuid != default(Guid); } }
[IgnoreDataMember]
internal DateTime PublishedDate { get; set; }
/// <summary>
/// Changes the Trashed state of the content object
/// </summary>
+1 -1
View File
@@ -4,7 +4,7 @@ namespace Umbraco.Core.Models
{
/// <summary>
/// Defines a ContentType, which Media is based on
/// </summary
/// </summary>
public interface IMediaType : IContentTypeComposition
{
@@ -13,6 +13,7 @@ namespace Umbraco.Core.Models.Identity
public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
{
config.CreateMap<IUser, BackOfficeIdentityUser>()
.ForMember(user => user.LastLoginDateUtc, expression => expression.MapFrom(user => user.LastLoginDate.ToUniversalTime()))
.ForMember(user => user.Email, expression => expression.MapFrom(user => user.Email))
.ForMember(user => user.Id, expression => expression.MapFrom(user => user.Id))
.ForMember(user => user.LockoutEndDateUtc, expression => expression.MapFrom(user => user.IsLockedOut ? DateTime.MaxValue.ToUniversalTime() : (DateTime?)null))
@@ -24,12 +24,17 @@ namespace Umbraco.Core.Models.Identity
///
/// </summary>
public IdentityUser()
{
{
this.Claims = new List<TClaim>();
this.Roles = new List<TRole>();
this.Logins = new List<TLogin>();
}
/// <summary>
/// Last login date
/// </summary>
public virtual DateTime? LastLoginDateUtc { get; set; }
/// <summary>
/// Email
///
@@ -69,6 +69,7 @@ namespace Umbraco.Core.Models
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
//TODO: Instead of 'new' this should explicitly implement one of the collection interfaces members
internal new void Add(PropertyType item)
{
using (new WriteLock(_addLocker))
@@ -110,6 +110,12 @@ namespace Umbraco.Core.Models
_ruleCollection.Clear();
}
internal void ClearRemovedRules()
{
_removedRules.Clear();
}
[DataMember]
public int LoginNodeId
{
@@ -179,7 +179,7 @@ namespace Umbraco.Core.Models.PublishedContent
}
if (contentType == null)
throw new Exception(string.Format("ContentTypeService failed to find a {0} type with alias \"{1}\".",
throw new InvalidOperationException(string.Format("ContentTypeService failed to find a {0} type with alias \"{1}\".",
itemType.ToString().ToLower(), alias));
return new PublishedContentType(contentType);
@@ -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
@@ -19,5 +19,8 @@ namespace Umbraco.Core.Models.Rdbms
[Column("newest")]
public bool Newest { get; set; }
[Column("updateDate")]
public DateTime VersionDate { get; set; }
}
}
@@ -22,6 +22,7 @@ namespace Umbraco.Core.ObjectResolution
private readonly string _httpContextKey;
private readonly List<Type> _instanceTypes = new List<Type>();
private IEnumerable<TResolved> _sortedValues;
private readonly Func<HttpContextBase> _httpContextGetter;
private int _defaultPluginWeight = 100;
@@ -42,12 +43,7 @@ namespace Umbraco.Core.ObjectResolution
if (logger == null) throw new ArgumentNullException("logger");
CanResolveBeforeFrozen = false;
if (scope == ObjectLifetimeScope.HttpRequest)
{
if (HttpContext.Current == null)
throw new InvalidOperationException("Use alternative constructor accepting a HttpContextBase object in order to set the lifetime scope to HttpRequest when HttpContext.Current is null");
CurrentHttpContext = new HttpContextWrapper(HttpContext.Current);
}
_httpContextGetter = () => new HttpContextWrapper(HttpContext.Current);
ServiceProvider = serviceProvider;
Logger = logger;
@@ -84,7 +80,7 @@ namespace Umbraco.Core.ObjectResolution
LifetimeScope = ObjectLifetimeScope.HttpRequest;
_httpContextKey = GetType().FullName;
ServiceProvider = serviceProvider;
CurrentHttpContext = httpContext;
_httpContextGetter = () => httpContext;
_instanceTypes = new List<Type>();
InitializeAppInstances();
@@ -160,7 +156,16 @@ namespace Umbraco.Core.ObjectResolution
/// Gets or sets the <see cref="HttpContextBase"/> used to initialize this object, if any.
/// </summary>
/// <remarks>If not null, then <c>LifetimeScope</c> will be <c>ObjectLifetimeScope.HttpRequest</c>.</remarks>
protected HttpContextBase CurrentHttpContext { get; private set; }
protected HttpContextBase CurrentHttpContext
{
get
{
var context = _httpContextGetter == null ? null : _httpContextGetter();
if (context == null)
throw new InvalidOperationException("Cannot use this resolver with lifetime 'HttpRequest' when there is no current HttpContext. Either use the ctor accepting an HttpContextBase, or use the resolver from within a request exclusively.");
return context;
}
}
/// <summary>
/// Returns the service provider used to instantiate objects
@@ -196,7 +201,7 @@ namespace Umbraco.Core.ObjectResolution
/// <summary>
/// Gets or sets the default type weight.
/// </summary>
/// <remarks>Determines the weight of types that do not have a <c>WeightAttribute</c> set on
/// <remarks>Determines the weight of types that do not have a <c>WeightAttribute</c> set on
/// them, when calling <c>GetSortedValues</c>.</remarks>
protected virtual int DefaultPluginWeight
{
@@ -276,7 +281,7 @@ namespace Umbraco.Core.ObjectResolution
/// Removes a type.
/// </summary>
/// <param name="value">The type to remove.</param>
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
/// the type is not a valid type for the resolver.</exception>
public virtual void RemoveType(Type value)
{
@@ -296,7 +301,7 @@ namespace Umbraco.Core.ObjectResolution
/// Removes a type.
/// </summary>
/// <typeparam name="T">The type to remove.</typeparam>
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
/// <exception cref="InvalidOperationException">the resolver does not support removing types, or
/// the type is not a valid type for the resolver.</exception>
public void RemoveType<T>()
where T : TResolved
@@ -309,7 +314,7 @@ namespace Umbraco.Core.ObjectResolution
/// </summary>
/// <param name="types">The types to add.</param>
/// <remarks>The types are appended at the end of the list.</remarks>
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// a type is not a valid type for the resolver, or a type is already in the collection of types.</exception>
protected void AddTypes(IEnumerable<Type> types)
{
@@ -336,7 +341,7 @@ namespace Umbraco.Core.ObjectResolution
/// </summary>
/// <param name="value">The type to add.</param>
/// <remarks>The type is appended at the end of the list.</remarks>
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
public virtual void AddType(Type value)
{
@@ -362,7 +367,7 @@ namespace Umbraco.Core.ObjectResolution
/// </summary>
/// <typeparam name="T">The type to add.</typeparam>
/// <remarks>The type is appended at the end of the list.</remarks>
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// <exception cref="InvalidOperationException">the resolver does not support adding types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
public void AddType<T>()
where T : TResolved
@@ -404,7 +409,7 @@ namespace Umbraco.Core.ObjectResolution
/// </summary>
/// <param name="index">The zero-based index at which the type should be inserted.</param>
/// <param name="value">The type to insert.</param>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is out of range.</exception>
public virtual void InsertType(int index, Type value)
@@ -430,7 +435,7 @@ namespace Umbraco.Core.ObjectResolution
/// Inserts a type at the beginning of the list.
/// </summary>
/// <param name="value">The type to insert.</param>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// the type is not a valid type for the resolver, or the type is already in the collection of types.</exception>
public virtual void InsertType(Type value)
{
@@ -464,7 +469,7 @@ namespace Umbraco.Core.ObjectResolution
/// </summary>
/// <param name="existingType">The existing type before which to insert.</param>
/// <param name="value">The type to insert.</param>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// one of the types is not a valid type for the resolver, or the existing type is not in the collection,
/// or the new type is already in the collection of types.</exception>
public virtual void InsertTypeBefore(Type existingType, Type value)
@@ -498,7 +503,7 @@ namespace Umbraco.Core.ObjectResolution
/// </summary>
/// <typeparam name="TExisting">The existing type before which to insert.</typeparam>
/// <typeparam name="T">The type to insert.</typeparam>
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// <exception cref="InvalidOperationException">the resolver does not support inserting types, or
/// one of the types is not a valid type for the resolver, or the existing type is not in the collection,
/// or the new type is already in the collection of types.</exception>
public void InsertTypeBefore<TExisting, T>()
@@ -27,15 +27,28 @@ namespace Umbraco.Core.Persistence.Factories
#region Implementation of IEntityFactory<IContent,DocumentDto>
public IContent BuildEntity(DocumentDto dto)
/// <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);
var content = new Content(dto.Text, dto.ContentVersionDto.ContentDto.NodeDto.ParentId, contentType);
try
{
content.DisableChangeTracking();
content.Id = _id;
content.Id = dto.NodeId;
content.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId;
content.Name = dto.Text;
content.NodeName = dto.ContentVersionDto.ContentDto.NodeDto.Text;
@@ -49,11 +62,19 @@ namespace Umbraco.Core.Persistence.Factories
content.Published = dto.Published;
content.CreateDate = dto.ContentVersionDto.ContentDto.NodeDto.CreateDate;
content.UpdateDate = dto.ContentVersionDto.VersionDate;
content.ExpireDate = dto.ExpiresDate.HasValue ? dto.ExpiresDate.Value : (DateTime?) null;
content.ReleaseDate = dto.ReleaseDate.HasValue ? dto.ReleaseDate.Value : (DateTime?) null;
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;
content.PublishedDate = publishedDto == null
? (dto.DocumentPublishedReadOnlyDto == null ? default(DateTime) : dto.DocumentPublishedReadOnlyDto.VersionDate)
: publishedDto.VersionDate;
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -64,6 +85,13 @@ namespace Umbraco.Core.Persistence.Factories
{
content.EnableChangeTracking();
}
}
[Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")]
public IContent BuildEntity(DocumentDto dto)
{
return BuildEntity(dto, _contentType);
}
public DocumentDto BuildDto(IContent entity)
@@ -27,15 +27,15 @@ namespace Umbraco.Core.Persistence.Factories
#region Implementation of IEntityFactory<IMedia,ContentVersionDto>
public IMedia BuildEntity(ContentVersionDto dto)
public static IMedia BuildEntity(ContentVersionDto dto, IMediaType contentType)
{
var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, _contentType);
var media = new Models.Media(dto.ContentDto.NodeDto.Text, dto.ContentDto.NodeDto.ParentId, contentType);
try
{
media.DisableChangeTracking();
media.Id = _id;
media.Id = dto.NodeId;
media.Key = dto.ContentDto.NodeDto.UniqueId;
media.Path = dto.ContentDto.NodeDto.Path;
media.CreatorId = dto.ContentDto.NodeDto.UserId.Value;
@@ -55,6 +55,13 @@ namespace Umbraco.Core.Persistence.Factories
{
media.EnableChangeTracking();
}
}
[Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")]
public IMedia BuildEntity(ContentVersionDto dto)
{
return BuildEntity(dto, _contentType);
}
public ContentVersionDto BuildDto(IMedia entity)
@@ -28,17 +28,17 @@ namespace Umbraco.Core.Persistence.Factories
#region Implementation of IEntityFactory<IMedia,ContentVersionDto>
public IMember BuildEntity(MemberDto dto)
public static IMember BuildEntity(MemberDto dto, IMemberType contentType)
{
var member = new Member(
dto.ContentVersionDto.ContentDto.NodeDto.Text,
dto.Email, dto.LoginName, dto.Password, _contentType);
dto.Email, dto.LoginName, dto.Password, contentType);
try
{
member.DisableChangeTracking();
member.Id = _id;
member.Id = dto.NodeId;
member.Key = dto.ContentVersionDto.ContentDto.NodeDto.UniqueId;
member.Path = dto.ContentVersionDto.ContentDto.NodeDto.Path;
member.CreatorId = dto.ContentVersionDto.ContentDto.NodeDto.UserId.Value;
@@ -62,6 +62,12 @@ namespace Umbraco.Core.Persistence.Factories
}
}
[Obsolete("Use the static BuildEntity instead so we don't have to allocate one of these objects everytime we want to map values")]
public IMember BuildEntity(MemberDto dto)
{
return BuildEntity(dto, _contentType);
}
public MemberDto BuildDto(IMember entity)
{
var dto = new MemberDto
@@ -30,11 +30,11 @@ namespace Umbraco.Core.Persistence.Factories
_updateDate = updateDate;
}
public IEnumerable<Property> BuildEntity(PropertyDataDto[] dtos)
public static IEnumerable<Property> BuildEntity(IReadOnlyCollection<PropertyDataDto> dtos, PropertyType[] compositionTypeProperties, DateTime createDate, DateTime updateDate)
{
var properties = new List<Property>();
foreach (var propertyType in _compositionTypeProperties)
foreach (var propertyType in compositionTypeProperties)
{
var propertyDataDto = dtos.LastOrDefault(x => x.PropertyTypeId == propertyType.Id);
var property = propertyDataDto == null
@@ -47,8 +47,8 @@ namespace Umbraco.Core.Persistence.Factories
//on initial construction we don't want to have dirty properties tracked
property.DisableChangeTracking();
property.CreateDate = _createDate;
property.UpdateDate = _updateDate;
property.CreateDate = createDate;
property.UpdateDate = updateDate;
// http://issues.umbraco.org/issue/U4-1946
property.ResetDirtyProperties(false);
properties.Add(property);
@@ -57,12 +57,18 @@ namespace Umbraco.Core.Persistence.Factories
{
property.EnableChangeTracking();
}
}
return properties;
}
[Obsolete("Use the static method instead, there's no reason to allocate one of these classes everytime we want to map values")]
public IEnumerable<Property> BuildEntity(PropertyDataDto[] dtos)
{
return BuildEntity(dtos, _compositionTypeProperties, _createDate, _updateDate);
}
public IEnumerable<PropertyDataDto> BuildDto(IEnumerable<Property> properties)
{
var propertyDataDtos = new List<PropertyDataDto>();
@@ -6,6 +6,7 @@ using System.Reflection;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Strings;
namespace Umbraco.Core.Persistence.Factories
{
@@ -17,7 +18,7 @@ namespace Umbraco.Core.Persistence.Factories
//figure out what extra properties we have that are not on the IUmbracoEntity and add them to additional data
foreach (var k in originalEntityProperties.Keys
.Select(x => new { orig = x, title = x.ConvertCase(StringAliasCaseType.PascalCase) })
.Select(x => new { orig = x, title = x.ToCleanString(CleanStringType.PascalCase | CleanStringType.Ascii | CleanStringType.ConvertCase) })
.Where(x => entityProps.InvariantContains(x.title) == false))
{
entity.AdditionalData[k.title] = originalEntityProperties[k.orig];
@@ -75,65 +76,6 @@ namespace Umbraco.Core.Persistence.Factories
entity.EnableChangeTracking();
}
}
public UmbracoEntity BuildEntity(EntityRepository.UmbracoEntityDto dto)
{
var entity = new UmbracoEntity(dto.Trashed)
{
CreateDate = dto.CreateDate,
CreatorId = dto.UserId.Value,
Id = dto.NodeId,
Key = dto.UniqueId,
Level = dto.Level,
Name = dto.Text,
NodeObjectTypeId = dto.NodeObjectType.Value,
ParentId = dto.ParentId,
Path = dto.Path,
SortOrder = dto.SortOrder,
HasChildren = dto.Children > 0,
ContentTypeAlias = dto.Alias ?? string.Empty,
ContentTypeIcon = dto.Icon ?? string.Empty,
ContentTypeThumbnail = dto.Thumbnail ?? string.Empty,
};
entity.IsPublished = dto.PublishedVersion != default(Guid) || (dto.NewestVersion != default(Guid) && dto.PublishedVersion == dto.NewestVersion);
entity.IsDraft = dto.NewestVersion != default(Guid) && (dto.PublishedVersion == default(Guid) || dto.PublishedVersion != dto.NewestVersion);
entity.HasPendingChanges = (dto.PublishedVersion != default(Guid) && dto.NewestVersion != default(Guid)) && dto.PublishedVersion != dto.NewestVersion;
if (dto.UmbracoPropertyDtos != null)
{
foreach (var propertyDto in dto.UmbracoPropertyDtos)
{
entity.AdditionalData[propertyDto.PropertyAlias] = new UmbracoEntity.EntityProperty
{
PropertyEditorAlias = propertyDto.PropertyEditorAlias,
Value = propertyDto.NTextValue.IsNullOrWhiteSpace()
? propertyDto.NVarcharValue
: propertyDto.NTextValue.ConvertToJsonIfPossible()
};
}
}
return entity;
}
public EntityRepository.UmbracoEntityDto BuildDto(UmbracoEntity entity)
{
var node = new EntityRepository.UmbracoEntityDto
{
CreateDate = entity.CreateDate,
Level = short.Parse(entity.Level.ToString(CultureInfo.InvariantCulture)),
NodeId = entity.Id,
NodeObjectType = entity.NodeObjectTypeId,
ParentId = entity.ParentId,
Path = entity.Path,
SortOrder = entity.SortOrder,
Text = entity.Name,
Trashed = entity.Trashed,
UniqueId = entity.Key,
UserId = entity.CreatorId
};
return node;
}
}
}
@@ -31,7 +31,7 @@ namespace Umbraco.Core.Persistence.Mappers
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
internal BaseMapper ResolveMapperByType(Type type)
public virtual BaseMapper ResolveMapperByType(Type type)
{
return _mapperCache.GetOrAdd(type, type1 =>
{
@@ -67,7 +67,7 @@ namespace Umbraco.Core.Persistence.Mappers
return Attempt<BaseMapper>.Succeed(mapper);
}
internal string GetMapping(Type type, string propertyName)
public virtual string GetMapping(Type type, string propertyName)
{
var mapper = ResolveMapperByType(type);
var result = mapper.Map(propertyName);
@@ -581,6 +581,18 @@ namespace Umbraco.Core.Persistence.Querying
case "InvariantContains":
case "InvariantEquals":
//special case, if it is 'Contains' and the argument that Contains is being called on is
//Enumerable and the methodArgs is the actual member access, then it's an SQL IN clause
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 +609,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 +710,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,5 +1,6 @@
using System;
using System.Linq.Expressions;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Persistence.SqlSyntax;
@@ -12,16 +13,19 @@ namespace Umbraco.Core.Persistence.Querying
/// <remarks>This object is stateful and cannot be re-used to parse an expression.</remarks>
internal class ModelToSqlExpressionVisitor<T> : ExpressionVisitorBase
{
private readonly MappingResolver _mappingResolver;
private readonly BaseMapper _mapper;
public ModelToSqlExpressionVisitor(ISqlSyntaxProvider sqlSyntax, BaseMapper mapper)
public ModelToSqlExpressionVisitor(ISqlSyntaxProvider sqlSyntax, MappingResolver mappingResolver)
: base(sqlSyntax)
{
_mapper = mapper;
_mapper = mappingResolver.ResolveMapperByType(typeof(T));
_mappingResolver = mappingResolver;
}
[Obsolete("Use the overload the specifies a SqlSyntaxProvider")]
public ModelToSqlExpressionVisitor()
: this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current.ResolveMapperByType(typeof(T)))
: this(SqlSyntaxContext.SqlSyntaxProvider, MappingResolver.Current)
{ }
protected override string VisitMemberAccess(MemberExpression m)
@@ -35,7 +39,7 @@ namespace Umbraco.Core.Persistence.Querying
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
throw new InvalidOperationException(string.Format("The mapper returned an empty field for the member name: {0} for type: {1}", m.Member.Name, m.Expression.Type));
return field;
}
//already compiled, return
@@ -49,13 +53,42 @@ namespace Umbraco.Core.Persistence.Querying
{
var field = _mapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException("The mapper returned an empty field for the member name: " + m.Member.Name);
throw new InvalidOperationException(string.Format("The mapper returned an empty field for the member name: {0} for type: {1}", m.Member.Name, m.Expression.Type));
return field;
}
//already compiled, return
return string.Empty;
}
if (m.Expression != null
&& m.Expression.Type != typeof(T)
&& TypeHelper.IsTypeAssignableFrom<IUmbracoEntity>(m.Expression.Type)
&& EndsWithConstant(m) == false)
{
//if this is the case, it means we have a sub expression / nested property access, such as: x.ContentType.Alias == "Test";
//and since the sub type (x.ContentType) is not the same as x, we need to resolve a mapper for x.ContentType to get it's mapped SQL column
//don't execute if compiled
if (Visited == false)
{
var subMapper = _mappingResolver.ResolveMapperByType(m.Expression.Type);
if (subMapper == null)
throw new NullReferenceException("No mapper found for type " + m.Expression.Type);
var field = subMapper.Map(m.Member.Name, true);
if (field.IsNullOrWhiteSpace())
throw new InvalidOperationException(string.Format("The mapper returned an empty field for the member name: {0} for type: {1}", m.Member.Name, m.Expression.Type));
return field;
}
//already compiled, return
return string.Empty;
}
//TODO: When m.Expression.NodeType == ExpressionType.Constant and it's an expression like: content => aliases.Contains(content.ContentType.Alias);
// then an SQL parameter will be added for aliases as an array, however in SqlIn on the subclass it will manually add these SqlParameters anyways,
// however the query will still execute because the SQL that is written will only contain the correct indexes of SQL parameters, this would be ignored,
// I'm just unsure right now due to time constraints how to make it correct. It won't matter right now and has been working already with this bug but I've
// only just discovered what it is actually doing.
var member = Expression.Convert(m, typeof(object));
var lambda = Expression.Lambda<Func<object>>(member);
var getter = lambda.Compile();
@@ -70,5 +103,24 @@ namespace Umbraco.Core.Persistence.Querying
return string.Empty;
}
/// <summary>
/// Determines if the MemberExpression ends in a Constant value
/// </summary>
/// <param name="m"></param>
/// <returns></returns>
private bool EndsWithConstant(MemberExpression m)
{
Expression expr = m;
while (expr is MemberExpression)
{
var memberExpr = expr as MemberExpression;
expr = memberExpr.Expression;
}
var constExpr = expr as ConstantExpression;
return constExpr != null;
}
}
}
@@ -18,14 +18,13 @@ namespace Umbraco.Core.Persistence.Querying
public PocoToSqlExpressionVisitor(ISqlSyntaxProvider syntaxProvider)
: base(syntaxProvider)
{
_pd = new Database.PocoData(typeof(T));
}
[Obsolete("Use the overload the specifies a SqlSyntaxProvider")]
public PocoToSqlExpressionVisitor()
: base(SqlSyntaxContext.SqlSyntaxProvider)
{
_pd = new Database.PocoData(typeof(T));
: this(SqlSyntaxContext.SqlSyntaxProvider)
{
}
protected override string VisitMemberAccess(MemberExpression m)
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace Umbraco.Core.Persistence.Querying
@@ -6,8 +7,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).
@@ -0,0 +1,31 @@
namespace Umbraco.Core.Persistence.Repositories
{
internal enum BaseQueryType
{
/// <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
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using System.Xml;
@@ -53,9 +54,9 @@ 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)
.Where<DocumentDto>(x => x.Newest, SqlSyntax)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault();
@@ -63,70 +64,112 @@ namespace Umbraco.Core.Persistence.Repositories
if (dto == null)
return null;
var content = CreateContentFromDto(dto, dto.ContentVersionDto.VersionId, sql);
var content = CreateContentFromDto(dto, sql);
return content;
}
protected override IEnumerable<IContent> PerformGetAll(params int[] ids)
{
var sql = GetBaseQuery(false);
if (ids.Any())
Func<Sql, Sql> translate = s =>
{
sql.Where("umbracoNode.id in (@ids)", new { ids });
}
if (ids.Any())
{
s.Where("umbracoNode.id in (@ids)", new { ids });
}
//we only want the newest ones with this method
s.Where<DocumentDto>(x => x.Newest, SqlSyntax);
return s;
};
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
//we only want the newest ones with this method
sql.Where<DocumentDto>(x => x.Newest);
return ProcessQuery(sql);
return ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds)));
}
protected override IEnumerable<IContent> PerformGetByQuery(IQuery<IContent> query)
{
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IContent>(sqlClause, query);
var sql = translator.Translate()
.Where<DocumentDto>(x => x.Newest)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate)
.OrderBy<NodeDto>(x => x.SortOrder);
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
return ProcessQuery(sql);
Func<SqlTranslator<IContent>, Sql> translate = (translator) =>
{
return translator.Translate()
.Where<DocumentDto>(x => x.Newest, SqlSyntax)
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax)
.OrderBy<NodeDto>(x => x.SortOrder, SqlSyntax);
};
var translatorFull = new SqlTranslator<IContent>(sqlBaseFull, query);
var translatorIds = new SqlTranslator<IContent>(sqlBaseIds, query);
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)));
}
#endregion
#region Overrides of PetaPocoRepositoryBase<IContent>
protected override Sql GetBaseQuery(bool isCount)
/// <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 sqlx = string.Format("LEFT OUTER JOIN {0} {1} ON ({1}.{2}={0}.{2} AND {1}.{3}=1)",
var sql = new Sql();
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsDocument.nodeId" : "*"))
.From<DocumentDto>(SqlSyntax)
.InnerJoin<ContentVersionDto>(SqlSyntax)
.On<DocumentDto, ContentVersionDto>(SqlSyntax, left => left.VersionId, right => right.VersionId)
.InnerJoin<ContentDto>(SqlSyntax)
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
//TODO: IF we want to enable querying on content type information this will need to be joined
//.InnerJoin<ContentTypeDto>(SqlSyntax)
//.On<ContentDto, ContentTypeDto>(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax);
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 retrieving Ids like in paging, this is unecessary
//and causes huge performance overhead for the SQL server, especially when sorting the result.
//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"),
SqlSyntax.GetQuotedTableName("cmsDocument2"),
SqlSyntax.GetQuotedColumnName("nodeId"),
SqlSyntax.GetQuotedColumnName("published"));
var sql = new Sql();
sql.Select(isCount ? "COUNT(*)" : "*")
.From<DocumentDto>()
.InnerJoin<ContentVersionDto>()
.On<DocumentDto, ContentVersionDto>(left => left.VersionId, right => right.VersionId)
.InnerJoin<ContentDto>()
.On<ContentVersionDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>()
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
// cannot do this because PetaPoco does not know how to alias the table
//.LeftOuterJoin<DocumentPublishedReadOnlyDto>()
//.On<DocumentDto, DocumentPublishedReadOnlyDto>(left => left.NodeId, right => right.NodeId)
// so have to rely on writing our own SQL
.Append(sqlx/*, new { @published = true }*/)
sql.Append(sqlx /*, new { @published = true }*/);
}
sql.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
return sql;
}
protected override Sql GetBaseQuery(bool isCount)
{
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
}
protected override string GetBaseWhereClause()
{
return "umbracoNode.id = @Id";
@@ -173,20 +216,32 @@ namespace Umbraco.Core.Persistence.Repositories
// not bring much safety - so this reverts to updating each record individually,
// and it may be slower in the end, but should be more resilient.
var baseId = 0;
var contentTypeIdsA = contentTypeIds == null ? new int[0] : contentTypeIds.ToArray();
Func<int, Sql, Sql> translate = (bId, sql) =>
{
if (contentTypeIdsA.Length > 0)
{
sql.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
}
sql
.Where<NodeDto>(x => x.NodeId > bId && x.Trashed == false, SqlSyntax)
.Where<DocumentDto>(x => x.Published, SqlSyntax)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
return sql;
};
var baseId = 0;
while (true)
{
// get the next group of nodes
var query = GetBaseQuery(false);
if (contentTypeIdsA.Length > 0)
query = query
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
query = query
.Where<NodeDto>(x => x.NodeId > baseId && x.Trashed == false)
.Where<DocumentDto>(x => x.Published)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
var sqlFull = translate(baseId, GetBaseQuery(BaseQueryType.FullMultiple));
var sqlIds = translate(baseId, GetBaseQuery(BaseQueryType.Ids));
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();
@@ -206,30 +261,70 @@ 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;
}
//now delete the items that shouldn't be there
var sqlAllIds = translate(0, GetBaseQuery(BaseQueryType.Ids));
var allContentIds = Database.Fetch<int>(sqlAllIds);
var docObjectType = Guid.Parse(Constants.ObjectTypes.Document);
var xmlIdsQuery = new Sql()
.Select("DISTINCT cmsContentXml.nodeId")
.From<ContentXmlDto>(SqlSyntax)
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentXmlDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
if (contentTypeIdsA.Length > 0)
{
xmlIdsQuery.InnerJoin<ContentDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentTypeDto>(SqlSyntax)
.On<ContentTypeDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.ContentTypeId)
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
}
xmlIdsQuery.Where<NodeDto>(dto => dto.NodeObjectType == docObjectType, SqlSyntax);
var allXmlIds = Database.Fetch<int>(xmlIdsQuery);
var toRemove = allXmlIds.Except(allContentIds).ToArray();
if (toRemove.Length > 0)
{
foreach (var idGroup in toRemove.InGroupsOf(2000))
{
Database.Execute("DELETE FROM cmsContentXml WHERE nodeId IN (@ids)", new { ids = idGroup });
}
}
}
public override IEnumerable<IContent> GetAllVersions(int id)
{
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, true);
Func<Sql, Sql> translate = s =>
{
return s.Where(GetBaseWhereClause(), new {Id = id})
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
};
var sqlFull = translate(GetBaseQuery(BaseQueryType.FullMultiple));
var sqlIds = translate(GetBaseQuery(BaseQueryType.Ids));
return ProcessQuery(sqlFull, new PagingSqlQuery(sqlIds), true, includeAllVersions:true);
}
public override IContent GetByVersion(Guid versionId)
{
var sql = GetBaseQuery(false);
var sql = GetBaseQuery(BaseQueryType.FullSingle);
//TODO: cmsContentVersion.VersionId has a Unique Index constraint applied, seems silly then to also add OrderByDescending since it would be impossible to return more than one.
sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId });
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(sql).FirstOrDefault();
if (dto == null)
return null;
var content = CreateContentFromDto(dto, versionId, sql);
var content = CreateContentFromDto(dto, sql);
return content;
}
@@ -238,10 +333,11 @@ namespace Umbraco.Core.Persistence.Repositories
{
var sql = new Sql()
.Select("*")
.From<DocumentDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, DocumentDto>(left => left.VersionId, right => right.VersionId)
.Where<ContentVersionDto>(x => x.VersionId == versionId)
.Where<DocumentDto>(x => x.Newest != true);
.From<DocumentDto>(SqlSyntax)
.InnerJoin<ContentVersionDto>(SqlSyntax)
.On<ContentVersionDto, DocumentDto>(SqlSyntax, left => left.VersionId, right => right.VersionId)
.Where<ContentVersionDto>(x => x.VersionId == versionId, SqlSyntax)
.Where<DocumentDto>(x => x.Newest != true, SqlSyntax);
var dto = Database.Fetch<DocumentDto, ContentVersionDto>(sql).FirstOrDefault();
if (dto == null) return;
@@ -259,7 +355,8 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = new Sql()
.Select("*")
.From<DocumentDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, DocumentDto>(left => left.VersionId, right => right.VersionId)
.InnerJoin<ContentVersionDto>()
.On<ContentVersionDto, DocumentDto>(left => left.VersionId, right => right.VersionId)
.Where<ContentVersionDto>(x => x.NodeId == id)
.Where<ContentVersionDto>(x => x.VersionDate < versionDate)
.Where<DocumentDto>(x => x.Newest != true);
@@ -416,11 +513,13 @@ namespace Umbraco.Core.Persistence.Repositories
dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto
{
VersionId = dto.VersionId,
VersionDate = dto.UpdateDate,
Newest = true,
NodeId = dto.NodeId,
Published = true
Published = true
};
((Content)entity).PublishedVersionGuid = dto.VersionId;
((Content) entity).PublishedVersionGuid = dto.VersionId;
((Content) entity).PublishedDate = dto.UpdateDate;
}
entity.ResetDirtyProperties();
@@ -499,6 +598,7 @@ namespace Umbraco.Core.Persistence.Repositories
//if (((ICanBeDirty)entity).IsPropertyDirty("Published") && (entity.Published || publishedState == PublishedState.Unpublished))
if (entity.ShouldClearPublishedFlagForPreviousVersions(publishedState, shouldCreateNewVersion))
{
//TODO: This perf can be improved, it could easily be UPDATE WHERE.... (one SQL call instead of many)
var publishedDocs = Database.Fetch<DocumentDto>("WHERE nodeId = @Id AND published = @IsPublished", new { Id = entity.Id, IsPublished = true });
foreach (var doc in publishedDocs)
{
@@ -512,6 +612,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
//Look up (newest) entries by id in cmsDocument table to set newest = false
//TODO: This perf can be improved, it could easily be UPDATE WHERE.... (one SQL call instead of many)
var documentDtos = Database.Fetch<DocumentDto>("WHERE nodeId = @Id AND newest = @IsNewest", new { Id = entity.Id, IsNewest = true });
foreach (var documentDto in documentDtos)
{
@@ -588,22 +689,26 @@ namespace Umbraco.Core.Persistence.Repositories
dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto
{
VersionId = dto.VersionId,
VersionDate = dto.UpdateDate,
Newest = true,
NodeId = dto.NodeId,
Published = true
};
((Content)entity).PublishedVersionGuid = dto.VersionId;
((Content) entity).PublishedVersionGuid = dto.VersionId;
((Content) entity).PublishedDate = dto.UpdateDate;
}
else if (publishedStateChanged)
{
dto.DocumentPublishedReadOnlyDto = new DocumentPublishedReadOnlyDto
{
VersionId = default(Guid),
VersionId = default (Guid),
VersionDate = default (DateTime),
Newest = false,
NodeId = dto.NodeId,
Published = false
};
((Content)entity).PublishedVersionGuid = default(Guid);
((Content) entity).PublishedVersionGuid = default(Guid);
((Content) entity).PublishedDate = default (DateTime);
}
entity.ResetDirtyProperties();
@@ -616,21 +721,27 @@ namespace Umbraco.Core.Persistence.Repositories
public IEnumerable<IContent> GetByPublishedVersion(IQuery<IContent> query)
{
Func<SqlTranslator<IContent>, Sql> translate = t =>
{
return t.Translate()
.Where<DocumentDto>(x => x.Published, SqlSyntax)
.OrderBy<NodeDto>(x => x.Level, SqlSyntax)
.OrderBy<NodeDto>(x => x.SortOrder, SqlSyntax);
};
// we WANT to return contents in top-down order, ie parents should come before children
// ideal would be pure xml "document order" which can be achieved with:
// 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 sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IContent>(sqlClause, query);
var sql = translator.Translate()
.Where<DocumentDto>(x => x.Published)
.OrderBy<NodeDto>(x => x.Level, SqlSyntax)
.OrderBy<NodeDto>(x => x.SortOrder, SqlSyntax);
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(sql, true);
return ProcessQuery(translate(translatorFull), new PagingSqlQuery(translate(translatorIds)), true);
}
/// <summary>
/// This builds the Xml document used for the XML cache
/// </summary>
@@ -662,10 +773,14 @@ namespace Umbraco.Core.Persistence.Repositories
parent.Attributes.Append(pIdAtt);
xmlDoc.AppendChild(parent);
const string sql = @"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.xml, umbracoNode.level from umbracoNode
//Ensure that only nodes that have published versions are selected
var sql = string.Format(@"select umbracoNode.id, umbracoNode.parentID, umbracoNode.sortOrder, cmsContentXml.{0}, umbracoNode.{1} from umbracoNode
inner join cmsContentXml on cmsContentXml.nodeId = umbracoNode.id and umbracoNode.nodeObjectType = @type
where umbracoNode.id in (select cmsDocument.nodeId from cmsDocument where cmsDocument.published = 1)
order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder",
SqlSyntax.GetQuotedColumnName("xml"),
SqlSyntax.GetQuotedColumnName("level"),
SqlSyntax.GetQuotedColumnName("level"));
XmlElement last = null;
@@ -716,13 +831,8 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
public void ClearPublished(IContent content)
{
// race cond!
var documentDtos = Database.Fetch<DocumentDto>("WHERE nodeId=@id AND published=@published", new { id = content.Id, published = true });
foreach (var documentDto in documentDtos)
{
documentDto.Published = false;
Database.Update(documentDto);
}
var sql = "UPDATE cmsDocument SET published=0 WHERE nodeId=@id AND published=1";
Database.Execute(sql, new {id = content.Id});
}
/// <summary>
@@ -802,9 +912,9 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
Func<Tuple<string, object[]>> filterCallback = () => new Tuple<string, object[]>(filterSql.SQL, filterSql.Arguments);
return GetPagedResultsByQuery<DocumentDto, Content>(query, pageIndex, pageSize, out totalRecords,
return GetPagedResultsByQuery<DocumentDto>(query, pageIndex, pageSize, out totalRecords,
new Tuple<string, string>("cmsDocument", "nodeId"),
sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField,
(sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -835,50 +945,110 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
return base.GetDatabaseFieldNameForOrderBy(orderBy);
}
private IEnumerable<IContent> ProcessQuery(Sql sql, bool withCache = false)
/// <summary>
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <param name="sqlFull">
/// 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="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>
/// <param name="includeAllVersions">
/// Generally when querying for content we only want to return the most recent version of the content item, however in some cases like when
/// we want to return all versions of a content item, we can't simply return the latest
/// </param>
/// <returns></returns>
private IEnumerable<IContent> ProcessQuery(Sql sqlFull, PagingSqlQuery pagingSqlQuery, bool withCache = false, bool includeAllVersions = false)
{
// fetch returns a list so it's ok to iterate it in this method
var dtos = Database.Fetch<DocumentDto, ContentVersionDto, ContentDto, NodeDto, DocumentPublishedReadOnlyDto>(sql);
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.
var content = new IContent[dtos.Count];
var defs = new List<DocumentDefinition>();
var templateIds = new List<int>();
for (var i = 0; i < dtos.Count; i++)
//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 "))
{
var dto = dtos[i];
parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal));
}
//order by update date DESC, if there is corrupted published flags we only want the latest!
var publishedSql = new Sql(@"SELECT cmsDocument.nodeId, cmsDocument.published, cmsDocument.versionId, cmsDocument.updateDate, cmsDocument.newest
FROM cmsDocument INNER JOIN cmsContentVersion ON cmsContentVersion.VersionId = cmsDocument.versionId
WHERE cmsDocument.published = 1 AND cmsDocument.nodeId IN
(" + parsedOriginalSql + @")
ORDER BY cmsContentVersion.id DESC
", 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);
}
//This is a tuple list identifying if the content item came from the cache or not
var content = new List<Tuple<IContent, bool>>();
var defs = new DocumentDefinitionCollection(includeAllVersions);
var templateIds = new List<int>();
//track the looked up content types, even though the content types are cached
// they still need to be deep cloned out of the cache and we don't want to add
// the overhead of deep cloning them on every item in this loop
var contentTypes = new Dictionary<int, IContentType>();
foreach (var dto in dtos)
{
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));
//only use this cached version if the dto returned is also the publish version, they must match
if (cached != null && cached.Published && dto.Published)
//only use this cached version if the dto returned is also the publish version, they must match and be teh same version
if (cached != null && cached.Version == dto.VersionId && cached.Published && dto.Published)
{
content[i] = cached;
content.Add(new Tuple<IContent, bool>(cached, true));
continue;
}
}
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId);
content[i] = factory.BuildEntity(dto);
// need template
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
templateIds.Add(dto.TemplateId.Value);
IContentType contentType;
if (contentTypes.ContainsKey(dto.ContentVersionDto.ContentDto.ContentTypeId))
{
contentType = contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId];
}
else
{
contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
contentTypes[dto.ContentVersionDto.ContentDto.ContentTypeId] = contentType;
}
// need properties
defs.Add(new DocumentDefinition(
dto.NodeId,
dto.VersionId,
dto.ContentVersionDto.VersionDate,
dto.ContentVersionDto.ContentDto.NodeDto.CreateDate,
contentType
));
// track the definition and if it's successfully added or updated then processed
if (defs.AddOrUpdate(new DocumentDefinition(dto, contentType)))
{
// assign template
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
templateIds.Add(dto.TemplateId.Value);
content.Add(new Tuple<IContent, bool>(ContentFactory.BuildEntity(dto, contentType, publishedDto), false));
}
}
// load all required templates in 1 query
@@ -886,45 +1056,44 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
.ToDictionary(x => x.Id, x => x);
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(sql, defs);
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
foreach (var def in defs)
// assign template and property data
foreach (var contentItem in content)
{
// move to corresponding item (which has to exist)
while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++;
var cc = contentItem.Item1;
var fromCache = contentItem.Item2;
//if this has come from cache, we do not need to build up it's structure
if (fromCache) continue;
var def = defs[includeAllVersions ? (ValueType)cc.Version : cc.Id];
// complete the item
var cc = content[dtoIndex];
var dto = dtos[dtoIndex];
ITemplate template = null;
if (dto.TemplateId.HasValue)
templates.TryGetValue(dto.TemplateId.Value, out template); // else null
if (def.DocumentDto.TemplateId.HasValue)
templates.TryGetValue(def.DocumentDto.TemplateId.Value, out template); // else null
cc.Template = template;
cc.Properties = propertyData[cc.Id];
cc.Properties = propertyData[cc.Version];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity) cc).ResetDirtyProperties(false);
cc.ResetDirtyProperties(false);
}
return content;
return content.Select(x => x.Item1).ToArray();
}
/// <summary>
/// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion.
/// </summary>
/// <param name="dto"></param>
/// <param name="versionId"></param>
/// <param name="docSql"></param>
/// <returns></returns>
private IContent CreateContentFromDto(DocumentDto dto, Guid versionId, Sql docSql)
private IContent CreateContentFromDto(DocumentDto dto, Sql docSql)
{
var contentType = _contentTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId);
var content = factory.BuildEntity(dto);
var content = ContentFactory.BuildEntity(dto, contentType);
//Check if template id is set on DocumentDto, and get ITemplate if it is.
if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0)
@@ -932,11 +1101,11 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
content.Template = _templateRepository.Get(dto.TemplateId.Value);
}
var docDef = new DocumentDefinition(dto.NodeId, versionId, content.UpdateDate, content.CreateDate, contentType);
var docDef = new DocumentDefinition(dto, contentType);
var properties = GetPropertyCollection(docSql, new[] { docDef });
content.Properties = properties[dto.NodeId];
content.Properties = properties[dto.VersionId];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -975,7 +1144,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder";
return currentName;
}
/// <summary>
/// Dispose disposable properties
/// </summary>
@@ -990,5 +1159,26 @@ order by umbracoNode.level, 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);
}
}
}
}
@@ -1250,5 +1250,19 @@ WHERE cmsContentType." + aliasColumn + @" LIKE @pattern",
while (aliases.Contains(test = alias + i)) i++;
return test;
}
/// <summary>
/// Given the path of a content item, this will return true if the content item exists underneath a list view content item
/// </summary>
/// <param name="contentPath"></param>
/// <returns></returns>
public bool HasContainerInPath(string contentPath)
{
var ids = contentPath.Split(',').Select(int.Parse);
var sql = new Sql(@"SELECT COUNT(*) FROM cmsContentType
INNER JOIN cmsContent ON cmsContentType.nodeId=cmsContent.contentType
WHERE cmsContent.nodeId IN (@ids) AND cmsContentType.isContainer=@isContainer", new { ids, isContainer = true });
return Database.ExecuteScalar<int>(sql) > 0;
}
}
}
@@ -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
{
@@ -118,7 +112,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (objectTypes.Any())
{
sql = sql.Where("umbracoNode.nodeObjectType IN (@objectTypes)", objectTypes);
sql = sql.Where("umbracoNode.nodeObjectType IN (@objectTypes)", new {objectTypes = objectTypes});
}
return Database.Fetch<string>(sql);
@@ -34,7 +34,7 @@ namespace Umbraco.Core.Persistence.Repositories
: base(work, cache, logger, sqlSyntax)
{
_contentTypeRepository = contentTypeRepository;
_preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax);
_preValRepository = new DataTypePreValueRepository(work, CacheHelper.CreateDisabledCacheHelper(), logger, sqlSyntax);
}
#region Overrides of RepositoryBase<int,DataTypeDefinition>
@@ -276,47 +276,33 @@ AND umbracoNode.id <> @id",
public PreValueCollection GetPreValuesCollectionByDataTypeId(int dataTypeId)
{
var cached = RuntimeCache.GetCacheItemsByKeySearch<PreValueCollection>(GetPrefixedCacheKey(dataTypeId));
if (cached != null && cached.Any())
{
//return from the cache, ensure it's a cloned result
return (PreValueCollection)cached.First().DeepClone();
}
return GetAndCachePreValueCollection(dataTypeId);
}
internal static string GetCacheKeyRegex(int preValueId)
{
return CacheKeys.DataTypePreValuesCacheKey + @"[-\d]+-([\d]*,)*" + preValueId + @"(?!\d)[,\d$]*";
var collection = GetCachedPreValueCollection(dataTypeId);
return collection;
}
/// <summary>
/// Gets a specific PreValue by its Id
/// </summary>
/// <param name="preValueId">Id of the PreValue to retrieve the value from</param>
/// <returns>PreValue as a string</returns>
public string GetPreValueAsString(int preValueId)
{
//We need to see if we can find the cached PreValueCollection based on the cache key above
var collections = RuntimeCache.GetCacheItemsByKeySearch<PreValueCollection>(CacheKeys.DataTypePreValuesCacheKey + "_");
var cached = RuntimeCache.GetCacheItemsByKeyExpression<PreValueCollection>(GetCacheKeyRegex(preValueId));
if (cached != null && cached.Any())
{
//return from the cache
var collection = cached.First();
var preVal = collection.FormatAsDictionary().Single(x => x.Value.Id == preValueId);
return preVal.Value.Value;
}
var preValue = collections.SelectMany(x => x.FormatAsDictionary().Values).FirstOrDefault(x => x.Id == preValueId);
if (preValue != null)
return preValue.Value;
//go and find the data type id for the pre val id passed in
var dto = Database.FirstOrDefault<DataTypePreValueDto>("WHERE id = @preValueId", new { preValueId = preValueId });
var dto = Database.FirstOrDefault<DataTypePreValueDto>("WHERE id = @preValueId", new { preValueId });
if (dto == null)
{
return string.Empty;
}
// go cache the collection
var preVals = GetAndCachePreValueCollection(dto.DataTypeNodeId);
//return the single value for this id
var pv = preVals.FormatAsDictionary().Single(x => x.Value.Id == preValueId);
return pv.Value.Value;
var collection = GetCachedPreValueCollection(dto.DataTypeNodeId);
if (collection == null)
return string.Empty;
preValue = collection.FormatAsDictionary().Values.FirstOrDefault(x => x.Id == preValueId);
return preValue == null ? string.Empty : preValue.Value;
}
public void AddOrUpdatePreValues(int dataTypeId, IDictionary<string, PreValue> values)
@@ -441,40 +427,28 @@ AND umbracoNode.id <> @id",
sortOrder++;
}
}
private string GetPrefixedCacheKey(int dataTypeId)
private static string GetPrefixedCacheKey(int dataTypeId)
{
return CacheKeys.DataTypePreValuesCacheKey + dataTypeId + "-";
return CacheKeys.DataTypePreValuesCacheKey + "_" + dataTypeId;
}
private PreValueCollection GetAndCachePreValueCollection(int dataTypeId)
private PreValueCollection GetCachedPreValueCollection(int datetypeId)
{
//go get the data
var dtos = Database.Fetch<DataTypePreValueDto>("WHERE datatypeNodeId = @Id", new { Id = dataTypeId });
var list = dtos.Select(x => new Tuple<PreValue, string, int>(new PreValue(x.Id, x.Value, x.SortOrder), x.Alias, x.SortOrder)).ToList();
var collection = PreValueConverter.ConvertToPreValuesCollection(list);
//now create the cache key, this needs to include all pre-value ids so that we can use this cached item in the GetPreValuesAsString method
//the key will be: "UmbracoPreValDATATYPEID-CSVOFPREVALIDS
var key = GetPrefixedCacheKey(dataTypeId)
+ string.Join(",", collection.FormatAsDictionary().Select(x => x.Value.Id).ToArray());
//store into cache
RuntimeCache.InsertCacheItem(key, () => collection,
//30 mins
new TimeSpan(0, 0, 30),
//sliding is true
true);
return collection;
var key = GetPrefixedCacheKey(datetypeId);
return RuntimeCache.GetCacheItem<PreValueCollection>(key, () =>
{
var dtos = Database.Fetch<DataTypePreValueDto>("WHERE datatypeNodeId = @Id", new { Id = datetypeId });
var list = dtos.Select(x => new Tuple<PreValue, string, int>(new PreValue(x.Id, x.Value, x.SortOrder), x.Alias, x.SortOrder)).ToList();
var collection = PreValueConverter.ConvertToPreValuesCollection(list);
return collection;
}, TimeSpan.FromMinutes(20), isSliding: true);
}
private string EnsureUniqueNodeName(string nodeName, int id = 0)
{
var sql = new Sql();
sql.Select("*")
@@ -576,7 +550,7 @@ AND umbracoNode.id <> @id",
}
//NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT
// in reality there is no need to check the uniqueness of this alias because the only way that this code executes is
// in reality there is no need to check the uniqueness of this alias because the only way that this code executes is
// based on an IDictionary<string, PreValue> dictionary being passed to this repository and a dictionary
// must have unique aliases by definition, so there is no need for this additional check
@@ -596,10 +570,10 @@ AND umbracoNode.id <> @id",
{
throw new InvalidOperationException("Cannot update a pre value for a data type that has no identity");
}
//NOTE: We used to check that the Alias was unique for the given DataTypeNodeId prevalues list, BUT
// this causes issues when sorting the pre-values (http://issues.umbraco.org/issue/U4-5670) but in reality
// there is no need to check the uniqueness of this alias because the only way that this code executes is
// there is no need to check the uniqueness of this alias because the only way that this code executes is
// based on an IDictionary<string, PreValue> dictionary being passed to this repository and a dictionary
// must have unique aliases by definition, so there is no need for this additional check
@@ -614,7 +588,7 @@ AND umbracoNode.id <> @id",
Database.Update(dto);
}
}
internal static class PreValueConverter
@@ -62,17 +62,27 @@ namespace Umbraco.Core.Persistence.Repositories
protected override IEnumerable<EntityContainer> PerformGetAll(params int[] ids)
{
//we need to batch these in groups of 2000 so we don't exceed the max 2100 limit
return ids.InGroupsOf(2000).SelectMany(@group =>
if (ids.Any())
{
//we need to batch these in groups of 2000 so we don't exceed the max 2100 limit
return ids.InGroupsOf(2000).SelectMany(@group =>
{
var sql = GetBaseQuery(false)
.Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId})
.Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new {ids = @group});
sql.OrderBy<NodeDto>(x => x.Level, SqlSyntax);
return Database.Fetch<NodeDto>(sql).Select(CreateEntity);
});
}
else
{
var sql = GetBaseQuery(false)
.Where("nodeObjectType=@umbracoObjectTypeId", new { umbracoObjectTypeId = NodeObjectTypeId })
.Where(string.Format("{0} IN (@ids)", SqlSyntax.GetQuotedColumnName("id")), new { ids = @group });
.Where("nodeObjectType=@umbracoObjectTypeId", new {umbracoObjectTypeId = NodeObjectTypeId});
sql.OrderBy<NodeDto>(x => x.Level, SqlSyntax);
return Database.Fetch<NodeDto>(sql).Select(CreateEntity);
});
}
}
protected override IEnumerable<EntityContainer> PerformGetByQuery(IQuery<EntityContainer> query)
@@ -1,18 +1,13 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Globalization;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using Umbraco.Core.Models;
using Umbraco.Core;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Factories;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Strings;
namespace Umbraco.Core.Persistence.Repositories
{
@@ -68,11 +63,12 @@ namespace Umbraco.Core.Persistence.Repositories
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
var sql = GetFullSqlForEntityType(key, isContent, isMedia, objectTypeId);
var factory = new UmbracoEntityFactory();
if (isMedia)
{
//for now treat media differently
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
//for now treat media differently and include all property data too
var entities = _work.Database.Fetch<dynamic, UmbracoPropertyDto, UmbracoEntity>(
new UmbracoEntityRelator().Map, sql);
@@ -80,14 +76,16 @@ namespace Umbraco.Core.Persistence.Repositories
}
else
{
var nodeDto = _work.Database.FirstOrDefault<dynamic>(sql);
if (nodeDto == null)
return null;
var factory = new UmbracoEntityFactory();
var entity = factory.BuildEntityFromDynamic(nodeDto);
return entity;
//query = read forward data reader, do not load everything into mem
var dtos = _work.Database.Query<dynamic>(sql);
var collection = new EntityDefinitionCollection();
foreach (var dto in dtos)
{
collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false));
}
var found = collection.FirstOrDefault();
return found != null ? found.BuildFromDynamic() : null;
}
@@ -112,29 +110,29 @@ namespace Umbraco.Core.Persistence.Repositories
bool isMedia = objectTypeId == new Guid(Constants.ObjectTypes.Media);
var sql = GetFullSqlForEntityType(id, isContent, isMedia, objectTypeId);
var factory = new UmbracoEntityFactory();
if (isMedia)
{
//for now treat media differently
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
//for now treat media differently and include all property data too
var entities = _work.Database.Fetch<dynamic, UmbracoPropertyDto, UmbracoEntity>(
new UmbracoEntityRelator().Map, sql);
return entities.FirstOrDefault();
}
else
{
var nodeDto = _work.Database.FirstOrDefault<dynamic>(sql);
if (nodeDto == null)
return null;
var factory = new UmbracoEntityFactory();
var entity = factory.BuildEntityFromDynamic(nodeDto);
return entity;
{
//query = read forward data reader, do not load everything into mem
var dtos = _work.Database.Query<dynamic>(sql);
var collection = new EntityDefinitionCollection();
foreach (var dto in dtos)
{
collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false));
}
var found = collection.FirstOrDefault();
return found != null ? found.BuildFromDynamic() : null;
}
}
public virtual IEnumerable<IUmbracoEntity> GetAll(Guid objectTypeId, params int[] ids)
@@ -171,22 +169,21 @@ namespace Umbraco.Core.Persistence.Repositories
if (isMedia)
{
//for now treat media differently
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
//for now treat media differently and include all property data too
var entities = _work.Database.Fetch<dynamic, UmbracoPropertyDto, UmbracoEntity>(
new UmbracoEntityRelator().Map, sql);
foreach (var entity in entities)
{
yield return entity;
}
return entities;
}
else
{
var dtos = _work.Database.Fetch<dynamic>(sql);
foreach (var entity in dtos.Select(dto => factory.BuildEntityFromDynamic(dto)))
//query = read forward data reader, do not load everything into mem
var dtos = _work.Database.Query<dynamic>(sql);
var collection = new EntityDefinitionCollection();
foreach (var dto in dtos)
{
yield return entity;
collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false));
}
return collection.Select(x => x.BuildFromDynamic()).ToList();
}
}
@@ -231,8 +228,7 @@ namespace Umbraco.Core.Persistence.Repositories
}
});
//treat media differently for now
//TODO: We should really use this methodology for Content/Members too!! since it includes properties and ALL of the dynamic db fields
//for now treat media differently and include all property data too
var entities = _work.Database.Fetch<dynamic, UmbracoPropertyDto, UmbracoEntity>(
new UmbracoEntityRelator().Map, mediaSql);
return entities;
@@ -241,8 +237,15 @@ namespace Umbraco.Core.Persistence.Repositories
{
//use dynamic so that we can get ALL properties from the SQL so we can chuck that data into our AdditionalData
var finalSql = entitySql.Append(GetGroupBy(isContent, false));
var dtos = _work.Database.Fetch<dynamic>(finalSql);
return dtos.Select(factory.BuildEntityFromDynamic).Cast<IUmbracoEntity>().ToList();
//query = read forward data reader, do not load everything into mem
var dtos = _work.Database.Query<dynamic>(finalSql);
var collection = new EntityDefinitionCollection();
foreach (var dto in dtos)
{
collection.AddOrUpdate(new EntityDefinition(factory, dto, isContent, false));
}
return collection.Select(x => x.BuildFromDynamic()).ToList();
}
}
@@ -315,25 +318,31 @@ namespace Umbraco.Core.Persistence.Repositories
protected virtual Sql GetBase(bool isContent, bool isMedia, Action<Sql> customFilter)
{
var columns = new List<object>
{
"umbracoNode.id",
"umbracoNode.trashed",
"umbracoNode.parentID",
"umbracoNode.nodeUser",
"umbracoNode.level",
"umbracoNode.path",
"umbracoNode.sortOrder",
"umbracoNode.uniqueID",
"umbracoNode.text",
"umbracoNode.nodeObjectType",
"umbracoNode.createDate",
"COUNT(parent.parentID) as children"
};
{
"umbracoNode.id",
"umbracoNode.trashed",
"umbracoNode.parentID",
"umbracoNode.nodeUser",
"umbracoNode.level",
"umbracoNode.path",
"umbracoNode.sortOrder",
"umbracoNode.uniqueID",
"umbracoNode.text",
"umbracoNode.nodeObjectType",
"umbracoNode.createDate",
"COUNT(parent.parentID) as children"
};
if (isContent || isMedia)
{
columns.Add("published.versionId as publishedVersion");
columns.Add("latest.versionId as newestVersion");
if (isContent)
{
//only content has/needs this info
columns.Add("published.versionId as publishedVersion");
columns.Add("document.versionId as newestVersion");
columns.Add("contentversion.id as versionId");
}
columns.Add("contenttype.alias");
columns.Add("contenttype.icon");
columns.Add("contenttype.thumbnail");
@@ -345,17 +354,22 @@ namespace Umbraco.Core.Persistence.Repositories
var entitySql = new Sql()
.Select(columns.ToArray())
.From("umbracoNode umbracoNode");
if (isContent || isMedia)
{
entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id")
.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType")
.LeftJoin(
"(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1 GROUP BY nodeId, versionId) as published")
.On("umbracoNode.id = published.nodeId")
.LeftJoin(
"(SELECT nodeId, versionId FROM cmsDocument WHERE newest = 1 GROUP BY nodeId, versionId) as latest")
.On("umbracoNode.id = latest.nodeId");
entitySql.InnerJoin("cmsContent content").On("content.nodeId = umbracoNode.id");
if (isContent)
{
//only content has/needs this info
entitySql
.InnerJoin("cmsDocument document").On("document.nodeId = umbracoNode.id")
.InnerJoin("cmsContentVersion contentversion").On("contentversion.VersionId = document.versionId")
.LeftJoin("(SELECT nodeId, versionId FROM cmsDocument WHERE published = 1) as published")
.On("umbracoNode.id = published.nodeId");
}
entitySql.LeftJoin("cmsContentType contenttype").On("contenttype.nodeId = content.contentType");
}
entitySql.LeftJoin("umbracoNode parent").On("parent.parentID = umbracoNode.id");
@@ -372,22 +386,42 @@ namespace Umbraco.Core.Persistence.Repositories
{
var sql = baseQuery(isContent, isMedia, filter)
.Where("umbracoNode.nodeObjectType = @NodeObjectType", new { NodeObjectType = nodeObjectType });
if (isContent)
{
sql.Where("document.newest = 1");
}
return sql;
}
protected virtual Sql GetBaseWhere(Func<bool, bool, Action<Sql>, Sql> baseQuery, bool isContent, bool isMedia, int id)
{
var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.id = @Id", new { Id = id })
.Append(GetGroupBy(isContent, isMedia));
.Where("umbracoNode.id = @Id", new { Id = id });
if (isContent)
{
sql.Where("document.newest = 1");
}
sql.Append(GetGroupBy(isContent, isMedia));
return sql;
}
protected virtual Sql GetBaseWhere(Func<bool, bool, Action<Sql>, Sql> baseQuery, bool isContent, bool isMedia, Guid key)
{
var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.uniqueID = @UniqueID", new { UniqueID = key })
.Append(GetGroupBy(isContent, isMedia));
.Where("umbracoNode.uniqueID = @UniqueID", new {UniqueID = key});
if (isContent)
{
sql.Where("document.newest = 1");
}
sql.Append(GetGroupBy(isContent, isMedia));
return sql;
}
@@ -396,6 +430,12 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.id = @Id AND umbracoNode.nodeObjectType = @NodeObjectType",
new {Id = id, NodeObjectType = nodeObjectType});
if (isContent)
{
sql.Where("document.newest = 1");
}
return sql;
}
@@ -404,36 +444,46 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = baseQuery(isContent, isMedia, null)
.Where("umbracoNode.uniqueID = @UniqueID AND umbracoNode.nodeObjectType = @NodeObjectType",
new { UniqueID = key, NodeObjectType = nodeObjectType });
if (isContent)
{
sql.Where("document.newest = 1");
}
return sql;
}
protected virtual Sql GetGroupBy(bool isContent, bool isMedia, bool includeSort = true)
{
var columns = new List<object>
{
"umbracoNode.id",
"umbracoNode.trashed",
"umbracoNode.parentID",
"umbracoNode.nodeUser",
"umbracoNode.level",
"umbracoNode.path",
"umbracoNode.sortOrder",
"umbracoNode.uniqueID",
"umbracoNode.text",
"umbracoNode.nodeObjectType",
"umbracoNode.createDate"
};
{
"umbracoNode.id",
"umbracoNode.trashed",
"umbracoNode.parentID",
"umbracoNode.nodeUser",
"umbracoNode.level",
"umbracoNode.path",
"umbracoNode.sortOrder",
"umbracoNode.uniqueID",
"umbracoNode.text",
"umbracoNode.nodeObjectType",
"umbracoNode.createDate"
};
if (isContent || isMedia)
{
columns.Add("published.versionId");
columns.Add("latest.versionId");
if (isContent)
{
columns.Add("published.versionId");
columns.Add("document.versionId");
columns.Add("contentversion.id");
}
columns.Add("contenttype.alias");
columns.Add("contenttype.icon");
columns.Add("contenttype.thumbnail");
columns.Add("contenttype.isContainer");
columns.Add("contenttype.isContainer");
}
var sql = new Sql()
.GroupBy(columns.ToArray());
@@ -444,7 +494,7 @@ namespace Umbraco.Core.Persistence.Repositories
return sql;
}
#endregion
/// <summary>
@@ -458,33 +508,7 @@ namespace Umbraco.Core.Persistence.Repositories
UnitOfWork.DisposeIfDisposable();
}
#region umbracoNode POCO - Extends NodeDto
[TableName("umbracoNode")]
[PrimaryKey("id")]
[ExplicitColumns]
internal class UmbracoEntityDto : NodeDto
{
[Column("children")]
public int Children { get; set; }
[Column("publishedVersion")]
public Guid PublishedVersion { get; set; }
[Column("newestVersion")]
public Guid NewestVersion { get; set; }
[Column("alias")]
public string Alias { get; set; }
[Column("icon")]
public string Icon { get; set; }
[Column("thumbnail")]
public string Thumbnail { get; set; }
[ResultColumn]
public List<UmbracoPropertyDto> UmbracoPropertyDtos { get; set; }
}
#region private classes
[ExplicitColumns]
internal class UmbracoPropertyDto
@@ -568,6 +592,99 @@ namespace Umbraco.Core.Persistence.Repositories
return prev;
}
}
private class EntityDefinitionCollection : KeyedCollection<int, EntityDefinition>
{
protected override int GetKeyForItem(EntityDefinition item)
{
return item.Id;
}
/// <summary>
/// if this key already exists if it does then we need to check
/// if the existing item is 'older' than the new item and if that is the case we'll replace the older one
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool AddOrUpdate(EntityDefinition item)
{
if (Dictionary == null)
{
base.Add(item);
return true;
}
var key = GetKeyForItem(item);
EntityDefinition found;
if (TryGetValue(key, out found))
{
//it already exists and it's older so we need to replace it
if (item.VersionId > found.VersionId)
{
var currIndex = Items.IndexOf(found);
if (currIndex == -1)
throw new IndexOutOfRangeException("Could not find the item in the list: " + found.Id);
//replace the current one with the newer one
SetItem(currIndex, item);
return true;
}
//could not add or update
return false;
}
base.Add(item);
return true;
}
private bool TryGetValue(int key, out EntityDefinition val)
{
if (Dictionary == null)
{
val = null;
return false;
}
return Dictionary.TryGetValue(key, out val);
}
}
private class EntityDefinition
{
private readonly UmbracoEntityFactory _factory;
private readonly dynamic _entity;
private readonly bool _isContent;
private readonly bool _isMedia;
public EntityDefinition(UmbracoEntityFactory factory, dynamic entity, bool isContent, bool isMedia)
{
_factory = factory;
_entity = entity;
_isContent = isContent;
_isMedia = isMedia;
}
public IUmbracoEntity BuildFromDynamic()
{
return _factory.BuildEntityFromDynamic(_entity);
}
public int Id
{
get { return _entity.id; }
}
public int VersionId
{
get
{
if (_isContent || _isMedia)
{
return _entity.versionId;
}
return _entity.id;
}
}
}
#endregion
}
}
@@ -96,5 +96,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IContent> GetPagedResultsByQuery(IQuery<IContent> query, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, IQuery<IContent> filter = null);
}
}
@@ -8,6 +8,13 @@ namespace Umbraco.Core.Persistence.Repositories
{
public interface IContentTypeRepository : IContentTypeCompositionRepository<IContentType>
{
/// <summary>
/// Given the path of a content item, this will return true if the content item exists underneath a list view content item
/// </summary>
/// <param name="contentPath"></param>
/// <returns></returns>
bool HasContainerInPath(string contentPath);
/// <summary>
/// Gets all entities of the specified <see cref="PropertyType"/> query
/// </summary>
@@ -38,15 +38,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// <returns>An Enumerable list of <see cref="IMedia"/> objects</returns>
IEnumerable<IMedia> GetPagedResultsByQuery(IQuery<IMedia> query, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, string filter = "");
/// <summary>
/// Gets paged media descendants as XML by path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of media items</returns>
IEnumerable<XElement> GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords);
}
}
@@ -73,6 +73,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="content"></param>
/// <param name="xml"></param>
void AddOrUpdatePreviewXml(IMember content, Func<IMember, XElement> xml);
}
}
@@ -20,7 +20,7 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="serializer">The serializer to convert TEntity to Xml</param>
/// <param name="groupSize">Structures will be rebuilt in chunks of this size</param>
/// <param name="contentTypeIds"></param>
void RebuildXmlStructures(Func<TEntity, XElement> serializer, int groupSize = 5000, IEnumerable<int> contentTypeIds = null);
void RebuildXmlStructures(Func<TEntity, XElement> serializer, int groupSize = 200, IEnumerable<int> contentTypeIds = null);
/// <summary>
/// Get the total count of entities
@@ -67,5 +67,16 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="id">Id of the <see cref="TEntity"/> object to delete versions from</param>
/// <param name="versionDate">Latest version date</param>
void DeleteVersions(int id, DateTime versionDate);
/// <summary>
/// Gets paged content descendants as XML by path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="orderBy"></param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
IEnumerable<XElement> GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords);
}
}
@@ -55,7 +55,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (dto == null)
return null;
var content = CreateMediaFromDto(dto, dto.VersionId, sql);
var content = CreateMediaFromDto(dto, sql);
return content;
}
@@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Where("umbracoNode.id in (@ids)", new { ids = ids });
}
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
protected override IEnumerable<IMedia> PerformGetByQuery(IQuery<IMedia> query)
@@ -76,26 +76,34 @@ namespace Umbraco.Core.Persistence.Repositories
var sqlClause = GetBaseQuery(false);
var translator = new SqlTranslator<IMedia>(sqlClause, query);
var sql = translator.Translate()
.OrderBy<NodeDto>(x => x.SortOrder);
.OrderBy<NodeDto>(x => x.SortOrder, SqlSyntax);
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
#endregion
#region Overrides of PetaPocoRepositoryBase<int,IMedia>
protected override Sql GetBaseQuery(BaseQueryType queryType)
{
var sql = new Sql();
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsContentVersion.contentId" : "*"))
.From<ContentVersionDto>(SqlSyntax)
.InnerJoin<ContentDto>(SqlSyntax)
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId, SqlSyntax)
//TODO: IF we want to enable querying on content type information this will need to be joined
//.InnerJoin<ContentTypeDto>(SqlSyntax)
//.On<ContentDto, ContentTypeDto>(SqlSyntax, left => left.ContentTypeId, right => right.NodeId, SqlSyntax);
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
return sql;
}
protected override Sql GetBaseQuery(bool isCount)
{
var sql = new Sql();
sql.Select(isCount ? "COUNT(*)" : "*")
.From<ContentVersionDto>()
.InnerJoin<ContentDto>()
.On<ContentVersionDto, ContentDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<NodeDto>()
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
return sql;
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
}
protected override string GetBaseWhereClause()
@@ -138,93 +146,106 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, true);
return ProcessQuery(sql, new PagingSqlQuery(sql), true);
}
private IEnumerable<IMedia> ProcessQuery(Sql sql, bool withCache = false)
/// <summary>
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <param name="sqlFull">
/// The full SQL to select all media data
/// </param>
/// <param name="pagingSqlQuery">
/// The Id SQL to just return all media ids - used to process the properties for the media item
/// </param>
/// <param name="withCache"></param>
/// <returns></returns>
private IEnumerable<IMedia> 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<ContentVersionDto, ContentDto, NodeDto>(sql);
var content = new IMedia[dtos.Count];
var defs = new List<DocumentDefinition>();
var dtos = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(sqlFull);
//This is a tuple list identifying if the content item came from the cache or not
var content = new List<Tuple<IMedia, bool>>();
var defs = new DocumentDefinitionCollection();
for (var i = 0; i < dtos.Count; i++)
//track the looked up content types, even though the content types are cached
// they still need to be deep cloned out of the cache and we don't want to add
// the overhead of deep cloning them on every item in this loop
var contentTypes = new Dictionary<int, IMediaType>();
foreach (var dto in dtos)
{
var dto = dtos[i];
// if the cache contains the item, use it
if (withCache)
{
var cached = RuntimeCache.GetCacheItem<IMedia>(GetCacheIdKey<IMedia>(dto.NodeId));
if (cached != null)
//only use this cached version if the dto returned is the same version - this is just a safety check, media doesn't
//store different versions, but just in case someone corrupts some data we'll double check to be sure.
if (cached != null && cached.Version == dto.VersionId)
{
content[i] = cached;
content.Add(new Tuple<IMedia, bool>(cached, true));
continue;
}
}
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId);
content[i] = factory.BuildEntity(dto);
// need properties
defs.Add(new DocumentDefinition(
dto.NodeId,
dto.VersionId,
dto.VersionDate,
dto.ContentDto.NodeDto.CreateDate,
contentType
));
IMediaType contentType;
if (contentTypes.ContainsKey(dto.ContentDto.ContentTypeId))
{
contentType = contentTypes[dto.ContentDto.ContentTypeId];
}
else
{
contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
contentTypes[dto.ContentDto.ContentTypeId] = contentType;
}
// track the definition and if it's successfully added or updated then processed
if (defs.AddOrUpdate(new DocumentDefinition(dto, contentType)))
{
content.Add(new Tuple<IMedia, bool>(MediaFactory.BuildEntity(dto, contentType), false));
}
}
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(sql, defs);
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
foreach (var def in defs)
// assign property data
foreach (var contentItem in content)
{
// move to corresponding item (which has to exist)
while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++;
var cc = contentItem.Item1;
var fromCache = contentItem.Item2;
// complete the item
var cc = content[dtoIndex];
cc.Properties = propertyData[cc.Id];
//if this has come from cache, we do not need to build up it's structure
if (fromCache) continue;
cc.Properties = propertyData[cc.Version];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity) cc).ResetDirtyProperties(false);
cc.ResetDirtyProperties(false);
}
return content;
return content.Select(x => x.Item1).ToArray();
}
public override IMedia GetByVersion(Guid versionId)
{
var sql = GetBaseQuery(false);
sql.Where("cmsContentVersion.VersionId = @VersionId", new { VersionId = versionId });
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate);
sql.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
var dto = Database.Fetch<ContentVersionDto, ContentDto, NodeDto>(sql).FirstOrDefault();
if (dto == null)
return null;
var mediaType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
var content = CreateMediaFromDto(dto, sql);
var factory = new MediaFactory(mediaType, NodeObjectTypeId, dto.NodeId);
var media = factory.BuildEntity(dto);
var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.VersionId, media.UpdateDate, media.CreateDate, mediaType) });
media.Properties = properties[dto.NodeId];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)media).ResetDirtyProperties(false);
return media;
return content;
}
public void RebuildXmlStructures(Func<IMedia, XElement> serializer, int groupSize = 200, IEnumerable<int> contentTypeIds = null)
@@ -242,12 +263,15 @@ namespace Umbraco.Core.Persistence.Repositories
// get the next group of nodes
var query = GetBaseQuery(false);
if (contentTypeIdsA.Length > 0)
query = query
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
{
query = query.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
}
query = query
.Where<NodeDto>(x => x.NodeId > baseId)
.Where<NodeDto>(x => x.NodeId > baseId, SqlSyntax)
.Where<NodeDto>(x => x.Trashed == false, SqlSyntax)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
var sql = SqlSyntax.SelectTop(query, groupSize);
var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
.ToList();
@@ -268,7 +292,38 @@ 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;
}
//now delete the items that shouldn't be there
var allMediaIds = Database.Fetch<int>(GetBaseQuery(BaseQueryType.Ids).Where<NodeDto>(x => x.Trashed == false, SqlSyntax));
var mediaObjectType = Guid.Parse(Constants.ObjectTypes.Media);
var xmlIdsQuery = new Sql()
.Select("DISTINCT cmsContentXml.nodeId")
.From<ContentXmlDto>(SqlSyntax)
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentXmlDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId);
if (contentTypeIdsA.Length > 0)
{
xmlIdsQuery.InnerJoin<ContentDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentTypeDto>(SqlSyntax)
.On<ContentTypeDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.ContentTypeId)
.WhereIn<ContentDto>(x => x.ContentTypeId, contentTypeIdsA, SqlSyntax);
}
xmlIdsQuery.Where<NodeDto>(dto => dto.NodeObjectType == mediaObjectType, SqlSyntax);
var allXmlIds = Database.Fetch<int>(xmlIdsQuery);
var toRemove = allXmlIds.Except(allMediaIds).ToArray();
if (toRemove.Length > 0)
{
foreach (var idGroup in toRemove.InGroupsOf(2000))
{
Database.Execute("DELETE FROM cmsContentXml WHERE nodeId IN (@ids)", new { ids = idGroup });
}
}
}
@@ -495,56 +550,30 @@ namespace Umbraco.Core.Persistence.Repositories
filterCallback = () => new Tuple<string, object[]>(sbWhere.ToString().Trim(), args.ToArray());
}
return GetPagedResultsByQuery<ContentVersionDto, Models.Media>(query, pageIndex, pageSize, out totalRecords,
return GetPagedResultsByQuery<ContentVersionDto>(query, pageIndex, pageSize, out totalRecords,
new Tuple<string, string>("cmsContentVersion", "contentId"),
sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField,
(sqlFull, pagingSqlQuery) => ProcessQuery(sqlFull, pagingSqlQuery), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
/// <summary>
/// Gets paged media descendants as XML by path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of media items</returns>
public IEnumerable<XElement> GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, out long totalRecords)
{
Sql query;
if (path == "-1")
{
query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE nodeObjectType = @0)", Guid.Parse(Constants.ObjectTypes.Media)).OrderBy("nodeId");
}
else
{
query = new Sql().Select("nodeId, xml").From("cmsContentXml").Where("nodeId IN (SELECT id FROM umbracoNode WHERE path LIKE @0)", path.EnsureEndsWith(",%")).OrderBy("nodeId");
}
var pagedResult = Database.Page<ContentXmlDto>(pageIndex+1, pageSize, query);
totalRecords = pagedResult.TotalItems;
return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml));
}
/// <summary>
/// Private method to create a media object from a ContentDto
/// </summary>
/// <param name="d"></param>
/// <param name="versionId"></param>
/// <param name="dto"></param>
/// <param name="docSql"></param>
/// <returns></returns>
private IMedia CreateMediaFromDto(ContentVersionDto dto, Guid versionId, Sql docSql)
private IMedia CreateMediaFromDto(ContentVersionDto dto, Sql docSql)
{
var contentType = _mediaTypeRepository.Get(dto.ContentDto.ContentTypeId);
var media = MediaFactory.BuildEntity(dto, contentType);
var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId);
var media = factory.BuildEntity(dto);
var docDef = new DocumentDefinition(dto, contentType);
var docDef = new DocumentDefinition(dto.NodeId, versionId, media.UpdateDate, media.CreateDate, contentType);
var properties = GetPropertyCollection(new PagingSqlQuery(docSql), new[] { docDef });
var properties = GetPropertyCollection(docSql, new[] { docDef });
media.Properties = properties[dto.NodeId];
media.Properties = properties[dto.VersionId];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -54,7 +54,7 @@ namespace Umbraco.Core.Persistence.Repositories
if (dto == null)
return null;
var content = CreateMemberFromDto(dto, dto.ContentVersionDto.VersionId, sql);
var content = CreateMemberFromDto(dto, sql);
return content;
@@ -68,7 +68,7 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Where("umbracoNode.id in (@ids)", new { ids = ids });
}
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
@@ -90,7 +90,7 @@ namespace Umbraco.Core.Persistence.Repositories
baseQuery.Append(new Sql("WHERE umbracoNode.id IN (" + sql.SQL + ")", sql.Arguments))
.OrderBy<NodeDto>(x => x.SortOrder);
return ProcessQuery(baseQuery);
return ProcessQuery(baseQuery, new PagingSqlQuery(baseQuery));
}
else
{
@@ -98,7 +98,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = translator.Translate()
.OrderBy<NodeDto>(x => x.SortOrder);
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
}
@@ -107,24 +107,29 @@ namespace Umbraco.Core.Persistence.Repositories
#region Overrides of PetaPocoRepositoryBase<int,IMembershipUser>
protected override Sql GetBaseQuery(bool isCount)
protected override Sql GetBaseQuery(BaseQueryType queryType)
{
var sql = new Sql();
sql.Select(isCount ? "COUNT(*)" : "*")
.From<MemberDto>()
.InnerJoin<ContentVersionDto>()
.On<ContentVersionDto, MemberDto>(left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentDto>()
.On<ContentVersionDto, ContentDto>(left => left.NodeId, right => right.NodeId)
sql.Select(queryType == BaseQueryType.Count ? "COUNT(*)" : (queryType == BaseQueryType.Ids ? "cmsMember.nodeId" : "*"))
.From<MemberDto>(SqlSyntax)
.InnerJoin<ContentVersionDto>(SqlSyntax)
.On<ContentVersionDto, MemberDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.InnerJoin<ContentDto>(SqlSyntax)
.On<ContentVersionDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
//We're joining the type so we can do a query against the member type - not sure if this adds much overhead or not?
// the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content
// types by default on the document and media repo's so we can query by content type there too.
.InnerJoin<ContentTypeDto>().On<ContentTypeDto, ContentDto>(left => left.NodeId, right => right.ContentTypeId)
.InnerJoin<NodeDto>()
.On<ContentDto, NodeDto>(left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
.InnerJoin<ContentTypeDto>(SqlSyntax)
.On<ContentTypeDto, ContentDto>(SqlSyntax, left => left.NodeId, right => right.ContentTypeId)
.InnerJoin<NodeDto>(SqlSyntax)
.On<ContentDto, NodeDto>(SqlSyntax, left => left.NodeId, right => right.NodeId)
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId, SqlSyntax);
return sql;
}
protected override Sql GetBaseQuery(bool isCount)
{
return GetBaseQuery(isCount ? BaseQueryType.Count : BaseQueryType.FullSingle);
}
protected override string GetBaseWhereClause()
@@ -380,8 +385,8 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = GetBaseQuery(false)
.Where(GetBaseWhereClause(), new { Id = id })
.OrderByDescending<ContentVersionDto>(x => x.VersionDate, SqlSyntax);
return ProcessQuery(sql, true);
}
return ProcessQuery(sql, new PagingSqlQuery(sql), true);
}
public void RebuildXmlStructures(Func<IMember, XElement> serializer, int groupSize = 200, IEnumerable<int> contentTypeIds = null)
{
@@ -403,7 +408,8 @@ namespace Umbraco.Core.Persistence.Repositories
query = query
.Where<NodeDto>(x => x.NodeId > baseId)
.OrderBy<NodeDto>(x => x.NodeId, SqlSyntax);
var xmlItems = ProcessQuery(SqlSyntax.SelectTop(query, groupSize))
var sql = SqlSyntax.SelectTop(query, groupSize);
var xmlItems = ProcessQuery(sql, new PagingSqlQuery(sql))
.Select(x => new ContentXmlDto { NodeId = x.Id, Xml = serializer(x).ToString() })
.ToList();
@@ -442,16 +448,16 @@ namespace Umbraco.Core.Persistence.Repositories
var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.NodeId);
var media = factory.BuildEntity(dto);
var member = factory.BuildEntity(dto);
var properties = GetPropertyCollection(sql, new[] { new DocumentDefinition(dto.NodeId, dto.ContentVersionDto.VersionId, media.UpdateDate, media.CreateDate, memberType) });
var properties = GetPropertyCollection(new PagingSqlQuery(sql), new[] { new DocumentDefinition(dto.ContentVersionDto, memberType) });
media.Properties = properties[dto.NodeId];
member.Properties = properties[dto.ContentVersionDto.VersionId];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)media).ResetDirtyProperties(false);
return media;
((Entity)member).ResetDirtyProperties(false);
return member;
}
@@ -535,7 +541,7 @@ namespace Umbraco.Core.Persistence.Repositories
.OrderByDescending<ContentVersionDto>(x => x.VersionDate)
.OrderBy<NodeDto>(x => x.SortOrder);
return ProcessQuery(sql);
return ProcessQuery(sql, new PagingSqlQuery(sql));
}
@@ -617,9 +623,9 @@ namespace Umbraco.Core.Persistence.Repositories
filterCallback = () => new Tuple<string, object[]>(sbWhere.ToString().Trim(), args.ToArray());
}
return GetPagedResultsByQuery<MemberDto, Member>(query, pageIndex, pageSize, out totalRecords,
return GetPagedResultsByQuery<MemberDto>(query, pageIndex, pageSize, out totalRecords,
new Tuple<string, string>("cmsMember", "nodeId"),
sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField,
(sqlFull, sqlIds) => ProcessQuery(sqlFull, sqlIds), orderBy, orderDirection, orderBySystemField,
filterCallback);
}
@@ -659,25 +665,37 @@ namespace Umbraco.Core.Persistence.Repositories
return base.GetEntityPropertyNameForOrderBy(orderBy);
}
private IEnumerable<IMember> ProcessQuery(Sql sql, bool withCache = false)
/// <summary>
/// This is the underlying method that processes most queries for this repository
/// </summary>
/// <param name="sqlFull">
/// The full SQL to select all member data
/// </param>
/// <param name="pagingSqlQuery">
/// The Id SQL to just return all member ids - used to process the properties for the member item
/// </param>
/// <param name="withCache"></param>
/// <returns></returns>
private IEnumerable<IMember> 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<MemberDto, ContentVersionDto, ContentDto, NodeDto>(sql);
var dtos = Database.Fetch<MemberDto, ContentVersionDto, ContentDto, NodeDto>(sqlFull);
var content = new IMember[dtos.Count];
var defs = new List<DocumentDefinition>();
//This is a tuple list identifying if the content item came from the cache or not
var content = new List<Tuple<IMember, bool>>();
var defs = new DocumentDefinitionCollection();
for (var i = 0; i < dtos.Count; i++)
foreach (var dto in dtos)
{
var dto = dtos[i];
// if the cache contains the item, use it
if (withCache)
{
var cached = RuntimeCache.GetCacheItem<IMember>(GetCacheIdKey<IMember>(dto.NodeId));
if (cached != null)
//only use this cached version if the dto returned is the same version - this is just a safety check, members dont
//store different versions, but just in case someone corrupts some data we'll double check to be sure.
if (cached != null && cached.Version == dto.ContentVersionDto.VersionId)
{
content[i] = cached;
content.Add(new Tuple<IMember, bool>(cached, true));
continue;
}
}
@@ -685,60 +703,54 @@ namespace Umbraco.Core.Persistence.Repositories
// else, need to fetch from the database
// content type repository is full-cache so OK to get each one independently
var contentType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.NodeId);
content[i] = factory.BuildEntity(dto);
// need properties
defs.Add(new DocumentDefinition(
dto.NodeId,
dto.ContentVersionDto.VersionId,
dto.ContentVersionDto.VersionDate,
dto.ContentVersionDto.ContentDto.NodeDto.CreateDate,
contentType
));
if (defs.AddOrUpdate(new DocumentDefinition(dto.ContentVersionDto, contentType)))
{
content.Add(new Tuple<IMember, bool>(MemberFactory.BuildEntity(dto, contentType), false));
}
}
// load all properties for all documents from database in 1 query
var propertyData = GetPropertyCollection(sql, defs);
var propertyData = GetPropertyCollection(pagingSqlQuery, defs);
// assign
var dtoIndex = 0;
foreach (var def in defs)
// assign property data
foreach (var contentItem in content)
{
// move to corresponding item (which has to exist)
while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++;
var cc = contentItem.Item1;
var fromCache = contentItem.Item2;
// complete the item
var cc = content[dtoIndex];
cc.Properties = propertyData[cc.Id];
//if this has come from cache, we do not need to build up it's structure
if (fromCache) continue;
cc.Properties = propertyData[cc.Version];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
((Entity)cc).ResetDirtyProperties(false);
cc.ResetDirtyProperties(false);
}
return content;
return content.Select(x => x.Item1).ToArray();
}
/// <summary>
/// Private method to create a member object from a MemberDto
/// </summary>
/// <param name="dto"></param>
/// <param name="versionId"></param>
/// <param name="docSql"></param>
/// <returns></returns>
private IMember CreateMemberFromDto(MemberDto dto, Guid versionId, Sql docSql)
private IMember CreateMemberFromDto(MemberDto dto, Sql docSql)
{
var memberType = _memberTypeRepository.Get(dto.ContentVersionDto.ContentDto.ContentTypeId);
var factory = new MemberFactory(memberType, NodeObjectTypeId, dto.ContentVersionDto.NodeId);
var member = factory.BuildEntity(dto);
var docDef = new DocumentDefinition(dto.ContentVersionDto.NodeId, versionId, member.UpdateDate, member.CreateDate, memberType);
var docDef = new DocumentDefinition(dto.ContentVersionDto, memberType);
var properties = GetPropertyCollection(docSql, new[] { docDef });
member.Properties = properties[dto.ContentVersionDto.NodeId];
member.Properties = properties[dto.ContentVersionDto.VersionId];
//on initial construction we don't want to have dirty properties tracked
// http://issues.umbraco.org/issue/U4-1946
@@ -44,10 +44,9 @@ namespace Umbraco.Core.Persistence.Repositories
{
sql.Where("umbracoAccess.id IN (@ids)", new { ids = ids });
}
sql.OrderBy<AccessDto>(x => x.NodeId, SqlSyntax);
var factory = new PublicAccessEntryFactory();
//MUST be ordered by this GUID ID for the AccessRulesRelator to work
var dtos = Database.Fetch<AccessDto, AccessRuleDto, AccessDto>(new AccessRulesRelator().Map, sql);
return dtos.Select(factory.BuildEntity);
}
@@ -59,6 +58,7 @@ namespace Umbraco.Core.Persistence.Repositories
var sql = translator.Translate();
var factory = new PublicAccessEntryFactory();
//MUST be ordered by this GUID ID for the AccessRulesRelator to work
var dtos = Database.Fetch<AccessDto, AccessRuleDto, AccessDto>(new AccessRulesRelator().Map, sql);
return dtos.Select(factory.BuildEntity);
}
@@ -69,7 +69,9 @@ namespace Umbraco.Core.Persistence.Repositories
sql.Select("*")
.From<AccessDto>(SqlSyntax)
.LeftJoin<AccessRuleDto>(SqlSyntax)
.On<AccessDto, AccessRuleDto>(SqlSyntax, left => left.Id, right => right.AccessId);
.On<AccessDto, AccessRuleDto>(SqlSyntax, left => left.Id, right => right.AccessId)
//MUST be ordered by this GUID ID for the AccessRulesRelator to work
.OrderBy<AccessDto>(dto => dto.Id, SqlSyntax);
return sql;
}
@@ -110,6 +112,11 @@ namespace Umbraco.Core.Persistence.Repositories
{
rule.AccessId = entity.Key;
Database.Insert(rule);
//update the id so HasEntity is correct
var entityRule = entity.Rules.First(x => x.Key == rule.Id);
entityRule.Id = entityRule.Key.GetHashCode();
//double make sure that this is set since it is possible to add rules via ctor without AddRule
entityRule.AccessEntryId = entity.Key;
}
entity.ResetDirtyProperties();
@@ -130,7 +137,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
if (rule.HasIdentity)
{
var count = Database.Update(dto.Rules.Single(x => x.Id == rule.Key));
var count = Database.Update(dto.Rules.First(x => x.Id == rule.Key));
if (count == 0)
{
throw new InvalidOperationException("No rows were updated for the access rule");
@@ -156,6 +163,8 @@ namespace Umbraco.Core.Persistence.Repositories
Database.Delete<AccessRuleDto>("WHERE id=@Id", new {Id = removedRule});
}
entity.ClearRemovedRules();
entity.ResetDirtyProperties();
}
@@ -164,4 +173,4 @@ namespace Umbraco.Core.Persistence.Repositories
return entity.Key;
}
}
}
}
@@ -63,6 +63,7 @@ namespace Umbraco.Core.Persistence.Repositories
FormatDeleteStatement("cmsContentVersion", "ContentId"),
FormatDeleteStatement("cmsContentXml", "nodeId"),
FormatDeleteStatement("cmsContent", "nodeId"),
//TODO: Why is this being done? We just delete this exact data in the next line
"UPDATE umbracoNode SET parentID = '" + RecycleBinId + "' WHERE trashed = '1' AND nodeObjectType = @NodeObjectType",
"DELETE FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType"
};
@@ -91,14 +92,18 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
/// <summary>
/// A delete statement that will delete anything in the table specified where its PK (keyName) is found in the
/// list of umbracoNode.id that have trashed flag set
/// </summary>
/// <param name="tableName"></param>
/// <param name="keyName"></param>
/// <returns></returns>
private string FormatDeleteStatement(string tableName, string keyName)
{
//This query works with sql ce and sql server:
//DELETE FROM umbracoUser2NodeNotify WHERE umbracoUser2NodeNotify.nodeId IN
//(SELECT nodeId FROM umbracoUser2NodeNotify as TB1 INNER JOIN umbracoNode as TB2 ON TB1.nodeId = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = 'C66BA18E-EAF3-4CFF-8A22-41B16D66A972')
return
string.Format(
"DELETE FROM {0} WHERE {0}.{1} IN (SELECT TB1.{1} FROM {0} as TB1 INNER JOIN umbracoNode as TB2 ON TB1.{1} = TB2.id WHERE TB2.trashed = '1' AND TB2.nodeObjectType = @NodeObjectType)",
"DELETE FROM {0} WHERE {0}.{1} IN (SELECT id FROM umbracoNode WHERE trashed = '1' AND nodeObjectType = @NodeObjectType)",
tableName, keyName);
}
@@ -1,10 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Xml.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
@@ -25,13 +28,16 @@ using Umbraco.Core.IO;
namespace Umbraco.Core.Persistence.Repositories
{
using SqlSyntax;
internal abstract class VersionableRepositoryBase<TId, TEntity> : PetaPocoRepositoryBase<TId, TEntity>
where TEntity : class, IAggregateRoot
{
private readonly IContentSection _contentSection;
/// <summary>
/// This is used for unit tests ONLY
/// </summary>
internal static bool ThrowOnWarning = false;
protected VersionableRepositoryBase(IDatabaseUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IContentSection contentSection)
: base(work, cache, logger, sqlSyntax)
{
@@ -137,6 +143,40 @@ namespace Umbraco.Core.Persistence.Repositories
#endregion
/// <summary>
/// Gets paged document descendants as XML by path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="orderBy"></param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
public virtual IEnumerable<XElement> GetPagedXmlEntriesByPath(string path, long pageIndex, int pageSize, string[] orderBy, out long totalRecords)
{
var query = new Sql().Select(string.Format("umbracoNode.id, cmsContentXml.{0}", SqlSyntax.GetQuotedColumnName("xml")))
.From("umbracoNode")
.InnerJoin("cmsContentXml").On("cmsContentXml.nodeId = umbracoNode.id");
if (path == "-1")
{
query.Where("umbracoNode.nodeObjectType = @type", new { type = NodeObjectTypeId });
}
else
{
query.Where(string.Format("umbracoNode.{0} LIKE (@0)", SqlSyntax.GetQuotedColumnName("path")), path.EnsureEndsWith(",%"));
}
//each order by param needs to be in a bracket! see: https://github.com/toptensoftware/PetaPoco/issues/177
query.OrderBy(orderBy == null
? "(umbracoNode.id)"
: string.Join(",", orderBy.Select(x => string.Format("({0})", SqlSyntax.GetQuotedColumnName(x)))));
var pagedResult = Database.Page<ContentXmlDto>(pageIndex + 1, pageSize, query);
totalRecords = pagedResult.TotalItems;
return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml));
}
public int CountDescendants(int parentId, string contentTypeAlias = null)
{
var pathMatch = parentId == -1
@@ -348,12 +388,8 @@ namespace Umbraco.Core.Persistence.Repositories
ON CustomPropData.CustomPropValContentId = umbracoNode.id
", sortedInt, sortedDecimal, sortedDate, sortedString, nodeIdSelect.Item2, nodeIdSelect.Item1, versionQuery, sortedSql.Arguments.Length, newestQuery);
//insert this just above the first LEFT OUTER JOIN (for cmsDocument) or the last WHERE (everything else)
string newSql;
if (nodeIdSelect.Item1 == "cmsDocument")
newSql = sortedSql.SQL.Insert(sortedSql.SQL.IndexOf("LEFT OUTER JOIN"), outerJoinTempTable);
else
newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable);
//insert this just above the last WHERE
string newSql = sortedSql.SQL.Insert(sortedSql.SQL.LastIndexOf("WHERE"), outerJoinTempTable);
var newArgs = sortedSql.Arguments.ToList();
newArgs.Add(orderBy);
@@ -370,11 +406,14 @@ namespace Umbraco.Core.Persistence.Repositories
}
}
//no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique.
// if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column
// is empty for many nodes)
// see: http://issues.umbraco.org/issue/U4-8831
sortedSql.OrderBy("umbracoNode.id");
if (orderBySystemField && orderBy != "umbracoNode.id")
{
//no matter what we always MUST order the result also by umbracoNode.id to ensure that all records being ordered by are unique.
// if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column
// is empty for many nodes)
// see: http://issues.umbraco.org/issue/U4-8831
sortedSql.OrderBy("umbracoNode.id");
}
return sortedSql;
@@ -384,7 +423,6 @@ namespace Umbraco.Core.Persistence.Repositories
/// A helper method for inheritors to get the paged results by query in a way that minimizes queries
/// </summary>
/// <typeparam name="TDto">The type of the d.</typeparam>
/// <typeparam name="TContentBase">The 'true' entity type (i.e. Content, Member, etc...)</typeparam>
/// <param name="query">The query.</param>
/// <param name="pageIndex">Index of the page.</param>
/// <param name="pageSize">Size of the page.</param>
@@ -397,34 +435,30 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="orderBySystemField">Flag to indicate when ordering by system field</param>
/// <returns></returns>
/// <exception cref="System.ArgumentNullException">orderBy</exception>
protected IEnumerable<TEntity> GetPagedResultsByQuery<TDto, TContentBase>(IQuery<TEntity> query, long pageIndex, int pageSize, out long totalRecords,
protected IEnumerable<TEntity> GetPagedResultsByQuery<TDto>(IQuery<TEntity> query, long pageIndex, int pageSize, out long totalRecords,
Tuple<string, string> nodeIdSelect,
Func<Sql, IEnumerable<TEntity>> processQuery,
Func<Sql, PagingSqlQuery<TDto>, IEnumerable<TEntity>> processQuery,
string orderBy,
Direction orderDirection,
bool orderBySystemField,
Func<Tuple<string, object[]>> defaultFilter = null)
where TContentBase : class, IAggregateRoot, TEntity
{
if (orderBy == null) throw new ArgumentNullException("orderBy");
// Get base query
var sqlBase = GetBaseQuery(false);
// Get base query for returning IDs
var sqlBaseIds = GetBaseQuery(BaseQueryType.Ids);
// Get base query for returning all data
var sqlBaseFull = GetBaseQuery(BaseQueryType.FullMultiple);
if (query == null) query = new Query<TEntity>();
var translator = new SqlTranslator<TEntity>(sqlBase, query);
var sqlQuery = translator.Translate();
// Note we can't do multi-page for several DTOs like we can multi-fetch and are doing in PerformGetByQuery,
// but actually given we are doing a Get on each one (again as in PerformGetByQuery), we only need the node Id.
// So we'll modify the SQL.
var sqlNodeIds = new Sql(
sqlQuery.SQL.Replace("SELECT *", string.Format("SELECT {0}.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2)),
sqlQuery.Arguments);
var translatorIds = new SqlTranslator<TEntity>(sqlBaseIds, query);
var sqlQueryIds = translatorIds.Translate();
var translatorFull = new SqlTranslator<TEntity>(sqlBaseFull, query);
var sqlQueryFull = translatorFull.Translate();
//get sorted and filtered sql
var sqlNodeIdsWithSort = GetSortedSqlForPagedResults(
GetFilteredSqlForPagedResults(sqlNodeIds, defaultFilter),
GetFilteredSqlForPagedResults(sqlQueryIds, defaultFilter),
orderDirection, orderBy, orderBySystemField, nodeIdSelect);
// Get page of results and total count
@@ -433,38 +467,32 @@ namespace Umbraco.Core.Persistence.Repositories
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
//NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number
// that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in
// that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in
// the pageResult, then the GetAll will actually return ALL records in the db.
if (pagedResult.Items.Any())
{
//Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
//Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
var args = sqlNodeIdsWithSort.Arguments;
string sqlStringCount, sqlStringPage;
Database.BuildPageQueries<TDto>(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage);
//if this is for sql server, the sqlPage will start with a SELECT * but we don't want that, we only want to return the nodeId
sqlStringPage = sqlStringPage
.Replace("SELECT *",
//This ensures we only take the field name of the node id select and not the table name - since the resulting select
// will ony work with the field name.
"SELECT " + nodeIdSelect.Item2);
//We need to make this an inner join on the paged query
var splitQuery = sqlQuery.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None);
var withInnerJoinSql = new Sql(splitQuery[0])
//We need to make this FULL query an inner join on the paged ID query
var splitQuery = sqlQueryFull.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None);
var fullQueryWithPagedInnerJoin = new Sql(splitQuery[0])
.Append("INNER JOIN (")
//join the paged query with the paged query arguments
.Append(sqlStringPage, args)
.Append(") temp ")
.Append(string.Format("ON {0}.{1} = temp.{1}", nodeIdSelect.Item1, nodeIdSelect.Item2))
//add the original where clause back with the original arguments
.Where(splitQuery[1], sqlQuery.Arguments);
.Where(splitQuery[1], sqlQueryIds.Arguments);
//get sorted and filtered sql
var fullQuery = GetSortedSqlForPagedResults(
GetFilteredSqlForPagedResults(withInnerJoinSql, defaultFilter),
GetFilteredSqlForPagedResults(fullQueryWithPagedInnerJoin, defaultFilter),
orderDirection, orderBy, orderBySystemField, nodeIdSelect);
return processQuery(fullQuery);
return processQuery(fullQuery, new PagingSqlQuery<TDto>(Database, sqlNodeIdsWithSort, pageIndex, pageSize));
}
else
{
@@ -474,38 +502,56 @@ namespace Umbraco.Core.Persistence.Repositories
return result;
}
protected IDictionary<int, PropertyCollection> GetPropertyCollection(
Sql docSql,
IEnumerable<DocumentDefinition> documentDefs)
/// <summary>
/// Gets the property collection for a non-paged query
/// </summary>
/// <param name="sql"></param>
/// <param name="documentDefs"></param>
/// <returns></returns>
protected IDictionary<Guid, PropertyCollection> GetPropertyCollection(
Sql sql,
IReadOnlyCollection<DocumentDefinition> documentDefs)
{
if (documentDefs.Any() == false) return new Dictionary<int, PropertyCollection>();
return GetPropertyCollection(new PagingSqlQuery(sql), documentDefs);
}
//we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use
/// <summary>
/// Gets the property collection for a query
/// </summary>
/// <param name="pagingSqlQuery"></param>
/// <param name="documentDefs"></param>
/// <returns></returns>
protected IDictionary<Guid, PropertyCollection> GetPropertyCollection(
PagingSqlQuery pagingSqlQuery,
IReadOnlyCollection<DocumentDefinition> documentDefs)
{
if (documentDefs.Count == 0) return new Dictionary<Guid, PropertyCollection>();
//initialize to the query passed in
var docSql = pagingSqlQuery.PrePagedSql;
//we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use
// the statement to go get the property data for all of the items by using an inner join
var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal));
//now remove everything from an Orderby clause and beyond
if (parsedOriginalSql.InvariantContains("ORDER BY "))
if (pagingSqlQuery.HasPaging)
{
//if this is a paged query, build the paged query with the custom column substitution, then re-assign
docSql = pagingSqlQuery.BuildPagedQuery("{0}");
parsedOriginalSql = docSql.SQL;
}
else if (parsedOriginalSql.InvariantContains("ORDER BY "))
{
//now remove everything from an Orderby clause and beyond if this is unpaged data
parsedOriginalSql = parsedOriginalSql.Substring(0, parsedOriginalSql.LastIndexOf("ORDER BY ", StringComparison.Ordinal));
}
var propSql = new Sql(@"SELECT cmsPropertyData.*
FROM cmsPropertyData
INNER JOIN cmsPropertyType
ON cmsPropertyData.propertytypeid = cmsPropertyType.id
INNER JOIN
(" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData
ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId
LEFT OUTER JOIN cmsDataTypePreValues
ON cmsPropertyType.dataTypeId = cmsDataTypePreValues.datatypeNodeId", docSql.Arguments);
var allPropertyData = Database.Fetch<PropertyDataDto>(propSql);
//This is a lazy access call to get all prevalue data for the data types that make up all of these properties which we use
// below if any property requires tag support
var allPreValues = new Lazy<IEnumerable<DataTypePreValueDto>>(() =>
{
var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId
//This retrieves all pre-values for all data types that are referenced for all property types
// that exist in the data set.
//Benchmarks show that eagerly loading these so that we can lazily read the property data
// below (with the use of Query intead of Fetch) go about 30% faster, so we'll eagerly load
// this now since we cannot execute another reader inside of reading the property data.
var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId
FROM cmsDataTypePreValues a
WHERE EXISTS(
SELECT DISTINCT b.id as preValIdInner
@@ -513,29 +559,86 @@ WHERE EXISTS(
INNER JOIN cmsPropertyType
ON b.datatypeNodeId = cmsPropertyType.dataTypeId
INNER JOIN
(" + string.Format(parsedOriginalSql, "DISTINCT cmsContent.contentType") + @") as docData
(" + string.Format(parsedOriginalSql, "cmsContent.contentType") + @") as docData
ON cmsPropertyType.contentTypeId = docData.contentType
WHERE a.id = b.id)", docSql.Arguments);
return Database.Fetch<DataTypePreValueDto>(preValsSql);
});
var allPreValues = Database.Fetch<DataTypePreValueDto>(preValsSql);
var result = new Dictionary<int, PropertyCollection>();
//It's Important with the sort order here! We require this to be sorted by node id,
// this is required because this data set can be huge depending on the page size. Due
// to it's size we need to be smart about iterating over the property values to build
// the document. Before we used to use Linq to get the property data for a given content node
// and perform a Distinct() call. This kills performance because that would mean if we had 7000 nodes
// and on each iteration we will perform a lookup on potentially 100,000 property rows against the node
// id which turns out to be a crazy amount of iterations. Instead we know it's sorted by this value we'll
// keep an index stored of the rows being read so we never have to re-iterate the entire data set
// on each document iteration.
var propSql = new Sql(@"SELECT cmsPropertyData.*
FROM cmsPropertyData
INNER JOIN cmsPropertyType
ON cmsPropertyData.propertytypeid = cmsPropertyType.id
INNER JOIN
(" + string.Format(parsedOriginalSql, "cmsContent.nodeId, cmsContentVersion.VersionId") + @") as docData
ON cmsPropertyData.versionId = docData.VersionId AND cmsPropertyData.contentNodeId = docData.nodeId
ORDER BY contentNodeId, versionId, propertytypeid
", docSql.Arguments);
//This does NOT fetch all data into memory in a list, this will read
// over the records as a data reader, this is much better for performance and memory,
// but it means that during the reading of this data set, nothing else can be read
// from SQL server otherwise we'll get an exception.
var allPropertyData = Database.Query<PropertyDataDto>(propSql);
var result = new Dictionary<Guid, PropertyCollection>();
var propertiesWithTagSupport = new Dictionary<string, SupportTagsAttribute>();
//used to track the resolved composition property types per content type so we don't have to re-resolve (ToArray) the list every time
var resolvedCompositionProperties = new Dictionary<int, PropertyType[]>();
//iterate each definition grouped by it's content type - this will mean less property type iterations while building
// up the property collections
foreach (var compositionGroup in documentDefs.GroupBy(x => x.Composition))
//keep track of the current property data item being enumerated
var propertyDataSetEnumerator = allPropertyData.GetEnumerator();
var hasCurrent = false; // initially there is no enumerator.Current
var comparer = new DocumentDefinitionComparer(SqlSyntax);
try
{
var compositionProperties = compositionGroup.Key.CompositionPropertyTypes.ToArray();
foreach (var def in compositionGroup)
//This must be sorted by node id because this is how we are sorting the query to lookup property types above,
// which allows us to more efficiently iterate over the large data set of property values
foreach (var def in documentDefs.OrderBy(x => x.Id).ThenBy(x => x.Version, comparer))
{
var propertyDataDtos = allPropertyData.Where(x => x.NodeId == def.Id).Distinct();
// get the resolved properties from our local cache, or resolve them and put them in cache
PropertyType[] compositionProperties;
if (resolvedCompositionProperties.ContainsKey(def.Composition.Id))
{
compositionProperties = resolvedCompositionProperties[def.Composition.Id];
}
else
{
compositionProperties = def.Composition.CompositionPropertyTypes.ToArray();
resolvedCompositionProperties[def.Composition.Id] = compositionProperties;
}
var propertyFactory = new PropertyFactory(compositionProperties, def.Version, def.Id, def.CreateDate, def.VersionDate);
var properties = propertyFactory.BuildEntity(propertyDataDtos.ToArray()).ToArray();
// assemble the dtos for this def
// use the available enumerator.Current if any else move to next
var propertyDataDtos = new List<PropertyDataDto>();
while (hasCurrent || propertyDataSetEnumerator.MoveNext())
{
//Not checking null on VersionId because it can never be null - no idea why it's set to nullable
// ReSharper disable once PossibleInvalidOperationException
if (propertyDataSetEnumerator.Current.VersionId.Value == def.Version)
{
hasCurrent = false; // enumerator.Current is not available
propertyDataDtos.Add(propertyDataSetEnumerator.Current);
}
else
{
hasCurrent = true; // enumerator.Current is available for another def
break; // no more propertyDataDto for this def
}
}
var properties = PropertyFactory.BuildEntity(propertyDataDtos, compositionProperties, def.CreateDate, def.VersionDate).ToArray();
foreach (var property in properties)
{
@@ -552,7 +655,7 @@ WHERE EXISTS(
propertiesWithTagSupport[property.PropertyType.PropertyEditorAlias] = tagSupport;
//this property has tags, so we need to extract them and for that we need the prevals which we've already looked up
var preValData = allPreValues.Value.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId)
var preValData = allPreValues.Where(x => x.DataTypeNodeId == property.PropertyType.DataTypeDefinitionId)
.Distinct()
.ToArray();
@@ -568,38 +671,30 @@ WHERE EXISTS(
}
}
if (result.ContainsKey(def.Id))
if (result.ContainsKey(def.Version))
{
Logger.Warn<VersionableRepositoryBase<TId, TEntity>>("The query returned multiple property sets for document definition " + def.Id + ", " + def.Composition.Name);
var msg = string.Format("The query returned multiple property sets for document definition {0}, {1}, {2}", def.Id, def.Version, def.Composition.Name);
if (ThrowOnWarning)
{
throw new InvalidOperationException(msg);
}
else
{
Logger.Warn<VersionableRepositoryBase<TId, TEntity>>(msg);
}
}
result[def.Id] = new PropertyCollection(properties);
result[def.Version] = new PropertyCollection(properties);
}
}
finally
{
propertyDataSetEnumerator.Dispose();
}
return result;
}
public class DocumentDefinition
{
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public DocumentDefinition(int id, Guid version, DateTime versionDate, DateTime createDate, IContentTypeComposition composition)
{
Id = id;
Version = version;
VersionDate = versionDate;
CreateDate = createDate;
Composition = composition;
}
public int Id { get; set; }
public Guid Version { get; set; }
public DateTime VersionDate { get; set; }
public DateTime CreateDate { get; set; }
public IContentTypeComposition Composition { get; set; }
}
protected virtual string GetDatabaseFieldNameForOrderBy(string orderBy)
{
// Translate the passed order by field (which were originally defined for in-memory object sorting
@@ -611,8 +706,8 @@ WHERE EXISTS(
case "NAME":
return "umbracoNode.text";
case "PUBLISHED":
return "cmsDocument.published";
case "OWNER":
return "cmsDocument.published";
//TODO: This isn't going to work very nicely because it's going to order by ID, not by letter
return "umbracoNode.nodeUser";
// Members only
@@ -688,5 +783,233 @@ WHERE EXISTS(
return allsuccess;
}
/// <summary>
/// For Paging, repositories must support returning different query for the query type specified
/// </summary>
/// <param name="queryType"></param>
/// <returns></returns>
protected abstract Sql GetBaseQuery(BaseQueryType queryType);
internal class DocumentDefinitionCollection : KeyedCollection<ValueType, DocumentDefinition>
{
private readonly bool _includeAllVersions;
/// <summary>
/// Constructor specifying if all versions should be allowed, in that case the key for the collection becomes the versionId (GUID)
/// </summary>
/// <param name="includeAllVersions"></param>
public DocumentDefinitionCollection(bool includeAllVersions = false)
{
_includeAllVersions = includeAllVersions;
}
protected override ValueType GetKeyForItem(DocumentDefinition item)
{
return _includeAllVersions ? (ValueType)item.Version : item.Id;
}
/// <summary>
/// if this key already exists if it does then we need to check
/// if the existing item is 'older' than the new item and if that is the case we'll replace the older one
/// </summary>
/// <param name="item"></param>
/// <returns></returns>
public bool AddOrUpdate(DocumentDefinition item)
{
//if we are including all versions then just add, we aren't checking for latest
if (_includeAllVersions)
{
base.Add(item);
return true;
}
if (Dictionary == null)
{
base.Add(item);
return true;
}
var key = GetKeyForItem(item);
DocumentDefinition found;
if (TryGetValue(key, out found))
{
//it already exists and it's older so we need to replace it
if (item.VersionId > found.VersionId)
{
var currIndex = Items.IndexOf(found);
if (currIndex == -1)
throw new IndexOutOfRangeException("Could not find the item in the list: " + found.Version);
//replace the current one with the newer one
SetItem(currIndex, item);
return true;
}
//could not add or update
return false;
}
base.Add(item);
return true;
}
public bool TryGetValue(ValueType key, out DocumentDefinition val)
{
if (Dictionary == null)
{
val = null;
return false;
}
return Dictionary.TryGetValue(key, out val);
}
}
/// <summary>
/// A custom comparer required for sorting entities by GUIDs to match how the sorting of GUIDs works on SQL server
/// </summary>
/// <remarks>
/// MySql sorts GUIDs as a string, MSSQL sorts based on byte sections, this comparer will allow sorting GUIDs to be the same as how SQL server does
/// </remarks>
private class DocumentDefinitionComparer : IComparer<Guid>
{
private readonly ISqlSyntaxProvider _sqlSyntax;
public DocumentDefinitionComparer(ISqlSyntaxProvider sqlSyntax)
{
_sqlSyntax = sqlSyntax;
}
public int Compare(Guid x, Guid y)
{
//MySql sorts on GUIDs as strings (i.e. normal)
if (_sqlSyntax is MySqlSyntaxProvider)
{
return x.CompareTo(y);
}
//MSSQL doesn't it sorts them on byte sections!
return new SqlGuid(x).CompareTo(new SqlGuid(y));
}
}
internal class DocumentDefinition
{
/// <summary>
/// Initializes a new instance of the <see cref="T:System.Object"/> class.
/// </summary>
public DocumentDefinition(DocumentDto dto, IContentTypeComposition composition)
{
DocumentDto = dto;
ContentVersionDto = dto.ContentVersionDto;
Composition = composition;
}
public DocumentDefinition(ContentVersionDto dto, IContentTypeComposition composition)
{
ContentVersionDto = dto;
Composition = composition;
}
public DocumentDto DocumentDto { get; private set; }
public ContentVersionDto ContentVersionDto { get; private set; }
public int Id
{
get { return ContentVersionDto.NodeId; }
}
public Guid Version
{
get { return DocumentDto != null ? DocumentDto.VersionId : ContentVersionDto.VersionId; }
}
/// <summary>
/// This is used to determien which version is the most recent
/// </summary>
public int VersionId
{
get { return ContentVersionDto.Id; }
}
public DateTime VersionDate
{
get { return ContentVersionDto.VersionDate; }
}
public DateTime CreateDate
{
get { return ContentVersionDto.ContentDto.NodeDto.CreateDate; }
}
public IContentTypeComposition Composition { get; set; }
}
/// <summary>
/// An object representing a query that may contain paging information
/// </summary>
internal class PagingSqlQuery
{
public Sql PrePagedSql { get; private set; }
public PagingSqlQuery(Sql prePagedSql)
{
PrePagedSql = prePagedSql;
}
public virtual bool HasPaging
{
get { return false; }
}
public virtual Sql BuildPagedQuery(string selectColumns)
{
throw new InvalidOperationException("This query has no paging information");
}
}
/// <summary>
/// An object representing a query that contains paging information
/// </summary>
/// <typeparam name="T"></typeparam>
internal class PagingSqlQuery<T> : PagingSqlQuery
{
private readonly Database _db;
private readonly long _pageIndex;
private readonly int _pageSize;
public PagingSqlQuery(Database db, Sql prePagedSql, long pageIndex, int pageSize) : base(prePagedSql)
{
_db = db;
_pageIndex = pageIndex;
_pageSize = pageSize;
}
public override bool HasPaging
{
get { return _pageSize > 0; }
}
/// <summary>
/// Creates a paged query based on the original query and subtitutes the selectColumns specified
/// </summary>
/// <param name="selectColumns"></param>
/// <returns></returns>
public override Sql BuildPagedQuery(string selectColumns)
{
if (HasPaging == false) throw new InvalidOperationException("This query has no paging information");
var resultSql = string.Format("SELECT {0} {1}", selectColumns, PrePagedSql.SQL.Substring(PrePagedSql.SQL.IndexOf("FROM", StringComparison.Ordinal)));
//this query is meant to be paged so we need to generate the paging syntax
//Create the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
var args = PrePagedSql.Arguments;
string sqlStringCount, sqlStringPage;
_db.BuildPageQueries<T>(_pageIndex * _pageSize, _pageSize, resultSql, ref args, out sqlStringCount, out sqlStringPage);
return new Sql(sqlStringPage, args);
}
}
}
}
@@ -48,7 +48,12 @@ namespace Umbraco.Core.Persistence
_cacheHelper.IsolatedRuntimeCache.CacheFactory = type =>
{
var cache = origFactory(type);
return new DeepCloneRuntimeCacheProvider(cache);
//if the result is already a DeepCloneRuntimeCacheProvider then return it, otherwise
//wrap the result with a DeepCloneRuntimeCacheProvider
return cache is DeepCloneRuntimeCacheProvider
? cache
: new DeepCloneRuntimeCacheProvider(cache);
};
}
+4 -3
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core.Events;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
@@ -12,7 +13,8 @@ namespace Umbraco.Core.Publishing
{
public PublishStatus(IContent content, PublishStatusType statusType, EventMessages eventMessages)
: base(content, statusType, eventMessages)
{
{
InvalidProperties = Enumerable.Empty<Property>();
}
/// <summary>
@@ -20,8 +22,7 @@ namespace Umbraco.Core.Publishing
/// </summary>
public PublishStatus(IContent content, EventMessages eventMessages)
: this(content, PublishStatusType.Success, eventMessages)
{
}
{ }
public IContent ContentItem
{
@@ -30,7 +30,7 @@ namespace Umbraco.Core.Publishing
/// Publishes a single piece of Content
/// </summary>
/// <param name="content"><see cref="IContent"/> to publish</param>
/// <param name="userId">Id of the User issueing the publish operation</param>
/// <param name="userId">Id of the User issueing the publish operation</param>
internal Attempt<PublishStatus> PublishInternal(IContent content, int userId)
{
var evtMsgs = _eventMessagesFactory.Get();
@@ -99,26 +99,26 @@ namespace Umbraco.Core.Publishing
/// By default this is set to true which means that it will publish any content item in the list that is completely unpublished and
/// not visible on the front-end. If set to false, this will only publish content that is live on the front-end but has new versions
/// that have yet to be published.
/// </param>
/// </param>
/// <returns></returns>
/// <remarks>
///
///
/// This method becomes complex once we start to be able to cancel events or stop publishing a content item in any way because if a
/// content item is not published then it's children shouldn't be published either. This rule will apply for the following conditions:
/// * If a document fails to be published, do not proceed to publish it's children if:
/// ** The document does not have a publish version
/// ** The document does have a published version but the includeUnpublishedDocuments = false
///
///
/// In order to do this, we will order the content by level and begin by publishing each item at that level, then proceed to the next
/// level and so on. If we detect that the above rule applies when the document publishing is cancelled we'll add it to the list of
/// level and so on. If we detect that the above rule applies when the document publishing is cancelled we'll add it to the list of
/// parentsIdsCancelled so that it's children don't get published.
///
///
/// Its important to note that all 'root' documents included in the list *will* be published regardless of the rules mentioned
/// above (unless it is invalid)!! By 'root' documents we are referring to documents in the list with the minimum value for their 'level'.
/// In most cases the 'root' documents will only be one document since under normal circumstance we only publish one document and
/// above (unless it is invalid)!! By 'root' documents we are referring to documents in the list with the minimum value for their 'level'.
/// In most cases the 'root' documents will only be one document since under normal circumstance we only publish one document and
/// its children. The reason we have to do this is because if a user is publishing a document and it's children, it is implied that
/// the user definitely wants to publish it even if it has never been published before.
///
///
/// </remarks>
internal IEnumerable<Attempt<PublishStatus>> PublishWithChildrenInternal(
IEnumerable<IContent> content, int userId, bool includeUnpublishedDocuments = true)
@@ -132,7 +132,7 @@ namespace Umbraco.Core.Publishing
//group by levels and iterate over the sorted ascending level.
//TODO: This will cause all queries to execute, they will not be lazy but I'm not really sure being lazy actually made
// much difference because we iterate over them all anyways?? Morten?
// much difference because we iterate over them all anyways?? Morten?
// Because we're grouping I think this will execute all the queries anyways so need to fetch it all first.
var fetchedContent = content.ToArray();
@@ -149,7 +149,7 @@ namespace Umbraco.Core.Publishing
var levelGroups = fetchedContent.GroupBy(x => x.Level);
foreach (var level in levelGroups.OrderBy(x => x.Key))
{
//set the first level flag, used to ensure that all documents at the first level will
//set the first level flag, used to ensure that all documents at the first level will
//be published regardless of the rules mentioned in the remarks.
if (!firstLevel.HasValue)
{
@@ -200,7 +200,10 @@ namespace Umbraco.Core.Publishing
_logger.Info<PublishingStrategy>(
string.Format("Content '{0}' with Id '{1}' will not be published because some of it's content is not passing validation rules.",
item.Name, item.Id));
statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedContentInvalid, evtMsgs)));
statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedContentInvalid, evtMsgs)
{
InvalidProperties = ((ContentBase)item).LastInvalidProperties
}));
//Does this document apply to our rule to cancel it's children being published?
CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments);
@@ -272,11 +275,11 @@ namespace Umbraco.Core.Publishing
/// <param name="includeUnpublishedDocuments"></param>
/// <remarks>
/// See remarks on method: PublishWithChildrenInternal
/// </remarks>
/// </remarks>
private void CheckCancellingOfChildPublishing(IContent content, List<int> parentsIdsCancelled, bool includeUnpublishedDocuments)
{
//Does this document apply to our rule to cancel it's children being published?
//TODO: We're going back to the service layer here... not sure how to avoid this? And this will add extra overhead to
//TODO: We're going back to the service layer here... not sure how to avoid this? And this will add extra overhead to
// any document that fails to publish...
var hasPublishedVersion = ApplicationContext.Current.Services.ContentService.HasPublishedVersion(content.Id);
@@ -306,8 +309,8 @@ namespace Umbraco.Core.Publishing
//NOTE: This previously always returned true so I've left it that way. It returned true because (from Morten)...
// ... if one item couldn't be published it wouldn't be correct to return false.
// in retrospect it should have returned a list of with Ids and Publish Status
// come to think of it ... the cache would still be updated for a failed item or at least tried updated.
// It would call the Published event for the entire list, but if the Published property isn't set to True it
// come to think of it ... the cache would still be updated for a failed item or at least tried updated.
// It would call the Published event for the entire list, but if the Published property isn't set to True it
// wouldn't actually update the cache for that item. But not really ideal nevertheless...
return true;
}
@@ -345,7 +348,7 @@ namespace Umbraco.Core.Publishing
var evtMsgs = _eventMessagesFactory.Get();
//Fire UnPublishing event
if (UnPublishing.IsRaisedEventCancelled(
if (UnPublishing.IsRaisedEventCancelled(
new PublishEventArgs<IContent>(content, evtMsgs), this))
{
_logger.Info<PublishingStrategy>(
@@ -388,8 +391,8 @@ namespace Umbraco.Core.Publishing
//NOTE: This previously always returned true so I've left it that way. It returned true because (from Morten)...
// ... if one item couldn't be published it wouldn't be correct to return false.
// in retrospect it should have returned a list of with Ids and Publish Status
// come to think of it ... the cache would still be updated for a failed item or at least tried updated.
// It would call the Published event for the entire list, but if the Published property isn't set to True it
// come to think of it ... the cache would still be updated for a failed item or at least tried updated.
// It would call the Published event for the entire list, but if the Published property isn't set to True it
// wouldn't actually update the cache for that item. But not really ideal nevertheless...
return true;
}
@@ -405,7 +408,7 @@ namespace Umbraco.Core.Publishing
public override void PublishingFinalized(IContent content)
{
var evtMsgs = _eventMessagesFactory.Get();
Published.RaiseEvent(
Published.RaiseEvent(
new PublishEventArgs<IContent>(content, false, false, evtMsgs), this);
}
@@ -114,7 +114,7 @@ namespace Umbraco.Core.Security
AllowRefresh = true,
IssuedUtc = nowUtc,
ExpiresUtc = nowUtc.AddMinutes(GlobalSettings.TimeOutInMinutes)
}, userIdentity, rememberBrowserIdentity);
}, userIdentity, rememberBrowserIdentity);
}
else
{
@@ -127,6 +127,10 @@ namespace Umbraco.Core.Security
}, userIdentity);
}
//track the last login date
user.LastLoginDateUtc = DateTime.UtcNow;
await UserManager.UpdateAsync(user);
_logger.WriteCore(TraceEventType.Information, 0,
string.Format(
"Login attempt succeeded for username {0} from IP address {1}",
@@ -625,6 +625,12 @@ namespace Umbraco.Core.Security
var anythingChanged = false;
//don't assign anything if nothing has changed as this will trigger
//the track changes of the model
if ((user.LastLoginDate != default(DateTime) && identityUser.LastLoginDateUtc.HasValue == false)
|| identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)
{
anythingChanged = true;
user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime();
}
if (user.Name != identityUser.Name && identityUser.Name.IsNullOrWhiteSpace() == false)
{
anythingChanged = true;
+33 -2
View File
@@ -585,7 +585,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public IEnumerable<IContent> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
public IEnumerable<IContent> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
long total;
var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter);
@@ -604,7 +604,7 @@ namespace Umbraco.Core.Services
/// <param name="orderDirection">Direction to order by</param>
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IContent> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
public IEnumerable<IContent> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter);
}
@@ -1764,6 +1764,31 @@ namespace Umbraco.Core.Services
return true;
}
/// <summary>
/// Gets paged content descendants as XML by path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
public IEnumerable<XElement> GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateContentRepository(uow))
{
var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize,
//This order by is VERY important! This allows us to figure out what is implicitly not published, see ContentRepository.BuildXmlCache and
// UmbracoContentIndexer.PerformIndexAll which uses the logic based on this sort order
new[] {"level", "parentID", "sortOrder"},
out totalRecords);
return contents;
}
}
/// <summary>
/// This builds the Xml document used for the XML cache
/// </summary>
@@ -2160,6 +2185,7 @@ namespace Umbraco.Core.Services
//We need to check if children and their publish state to ensure that we 'republish' content that was previously published
if (published && previouslyPublished == false && HasChildren(content.Id))
{
//TODO: Horrible for performance if there are lots of descendents! We should page if anything but this is crazy
var descendants = GetPublishedDescendants(content);
_publishingStrategy.PublishingFinalized(descendants, false);
@@ -2192,6 +2218,11 @@ namespace Umbraco.Core.Services
}
}
if (string.IsNullOrWhiteSpace(content.Name))
{
throw new ArgumentException("Cannot save content with empty name.");
}
using (new WriteLock(Locker))
{
var uow = UowProvider.GetUnitOfWork();
+67 -60
View File
@@ -1,22 +1,15 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Threading;
using AutoMapper;
using Umbraco.Core.Auditing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Events;
using Umbraco.Core.Exceptions;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
@@ -26,14 +19,15 @@ namespace Umbraco.Core.Services
/// </summary>
public class ContentTypeService : ContentTypeServiceBase, IContentTypeService
{
private readonly IContentService _contentService;
private readonly IContentService _contentService;
private readonly IMediaService _mediaService;
//Support recursive locks because some of the methods that require locking call other methods that require locking.
//for example, the Move method needs to be locked but this calls the Save method which also needs to be locked.
private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
public ContentTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService, IMediaService mediaService)
public ContentTypeService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IContentService contentService,
IMediaService mediaService)
: base(provider, repositoryFactory, logger, eventMessagesFactory)
{
if (contentService == null) throw new ArgumentNullException("contentService");
@@ -154,8 +148,8 @@ namespace Umbraco.Core.Services
}
if (savingEvent.IsRaisedEventCancelled(
new SaveEventArgs<EntityContainer>(container, evtMsgs),
this))
new SaveEventArgs<EntityContainer>(container, evtMsgs),
this))
{
return OperationStatus.Cancelled(evtMsgs);
}
@@ -214,7 +208,7 @@ namespace Umbraco.Core.Services
public IEnumerable<EntityContainer> GetMediaTypeContainers(IMediaType mediaType)
{
var ancestorIds = mediaType.Path.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
var ancestorIds = mediaType.Path.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries)
.Select(x =>
{
var asInt = x.TryConvertTo<int>();
@@ -290,8 +284,8 @@ namespace Umbraco.Core.Services
if (container == null) return OperationStatus.NoOperation(evtMsgs);
if (DeletingContentTypeContainer.IsRaisedEventCancelled(
new DeleteEventArgs<EntityContainer>(container, evtMsgs),
this))
new DeleteEventArgs<EntityContainer>(container, evtMsgs),
this))
{
return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs));
}
@@ -316,8 +310,8 @@ namespace Umbraco.Core.Services
if (container == null) return OperationStatus.NoOperation(evtMsgs);
if (DeletingMediaTypeContainer.IsRaisedEventCancelled(
new DeleteEventArgs<EntityContainer>(container, evtMsgs),
this))
new DeleteEventArgs<EntityContainer>(container, evtMsgs),
this))
{
return Attempt.Fail(new OperationStatus(OperationStatusType.FailedCancelledByEvent, evtMsgs));
}
@@ -421,7 +415,7 @@ namespace Umbraco.Core.Services
clone.Name = name;
var compositionAliases = clone.CompositionAliases().Except(new[] { alias }).ToList();
var compositionAliases = clone.CompositionAliases().Except(new[] {alias}).ToList();
//remove all composition that is not it's current alias
foreach (var a in compositionAliases)
{
@@ -680,7 +674,7 @@ namespace Umbraco.Core.Services
var comparer = new DelegateEqualityComparer<IContentTypeComposition>((x, y) => x.Id == y.Id, x => x.Id);
var dependencies = new HashSet<IContentTypeComposition>(compositions, comparer);
var stack = new Stack<IContentTypeComposition>();
indirectReferences.ForEach(stack.Push);//Push indirect references to a stack, so we can add recursively
indirectReferences.ForEach(stack.Push); //Push indirect references to a stack, so we can add recursively
while (stack.Count > 0)
{
var indirectReference = stack.Pop();
@@ -719,8 +713,13 @@ namespace Umbraco.Core.Services
/// <param name="userId">Optional id of the user saving the ContentType</param>
public void Save(IContentType contentType, int userId = 0)
{
if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs<IContentType>(contentType), this))
return;
if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs<IContentType>(contentType), this))
return;
if (string.IsNullOrWhiteSpace(contentType.Name))
{
throw new ArgumentException("Cannot save content type with empty name.");
}
using (new WriteLock(Locker))
{
@@ -729,6 +728,8 @@ namespace Umbraco.Core.Services
{
ValidateLocked(contentType); // throws if invalid
contentType.CreatorId = userId;
if (contentType.Description == string.Empty)
contentType.Description = null;
repository.AddOrUpdate(contentType);
uow.Commit();
@@ -737,7 +738,7 @@ namespace Umbraco.Core.Services
UpdateContentXmlStructure(contentType);
}
SavedContentType.RaiseEvent(new SaveEventArgs<IContentType>(contentType, false), this);
Audit(AuditType.Save, string.Format("Save ContentType performed by user"), userId, contentType.Id);
Audit(AuditType.Save, string.Format("Save ContentType performed by user"), userId, contentType.Id);
}
/// <summary>
@@ -750,7 +751,7 @@ namespace Umbraco.Core.Services
var asArray = contentTypes.ToArray();
if (SavingContentType.IsRaisedEventCancelled(new SaveEventArgs<IContentType>(asArray), this))
return;
return;
using (new WriteLock(Locker))
{
@@ -765,6 +766,8 @@ namespace Umbraco.Core.Services
foreach (var contentType in asArray)
{
contentType.CreatorId = userId;
if (contentType.Description == string.Empty)
contentType.Description = null;
repository.AddOrUpdate(contentType);
}
@@ -775,7 +778,7 @@ namespace Umbraco.Core.Services
UpdateContentXmlStructure(asArray.Cast<IContentTypeBase>().ToArray());
}
SavedContentType.RaiseEvent(new SaveEventArgs<IContentType>(asArray, false), this);
Audit(AuditType.Save, string.Format("Save ContentTypes performed by user"), userId, -1);
Audit(AuditType.Save, string.Format("Save ContentTypes performed by user"), userId, -1);
}
/// <summary>
@@ -786,8 +789,8 @@ namespace Umbraco.Core.Services
/// <remarks>Deleting a <see cref="IContentType"/> will delete all the <see cref="IContent"/> objects based on this <see cref="IContentType"/></remarks>
public void Delete(IContentType contentType, int userId = 0)
{
if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs<IContentType>(contentType), this))
return;
if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs<IContentType>(contentType), this))
return;
using (new WriteLock(Locker))
{
@@ -831,7 +834,7 @@ namespace Umbraco.Core.Services
var asArray = contentTypes.ToArray();
if (DeletingContentType.IsRaisedEventCancelled(new DeleteEventArgs<IContentType>(asArray), this))
return;
return;
using (new WriteLock(Locker))
{
@@ -999,8 +1002,8 @@ namespace Umbraco.Core.Services
var evtMsgs = EventMessagesFactory.Get();
if (MovingMediaType.IsRaisedEventCancelled(
new MoveEventArgs<IMediaType>(evtMsgs, new MoveEventInfo<IMediaType>(toMove, toMove.Path, containerId)),
this))
new MoveEventArgs<IMediaType>(evtMsgs, new MoveEventInfo<IMediaType>(toMove, toMove.Path, containerId)),
this))
{
return Attempt.Fail(
new OperationStatus<MoveOperationStatusType>(
@@ -1042,8 +1045,8 @@ namespace Umbraco.Core.Services
var evtMsgs = EventMessagesFactory.Get();
if (MovingContentType.IsRaisedEventCancelled(
new MoveEventArgs<IContentType>(evtMsgs, new MoveEventInfo<IContentType>(toMove, toMove.Path, containerId)),
this))
new MoveEventArgs<IContentType>(evtMsgs, new MoveEventInfo<IContentType>(toMove, toMove.Path, containerId)),
this))
{
return Attempt.Fail(
new OperationStatus<MoveOperationStatusType>(
@@ -1173,8 +1176,8 @@ namespace Umbraco.Core.Services
/// <param name="userId">Optional Id of the user saving the MediaType</param>
public void Save(IMediaType mediaType, int userId = 0)
{
if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs<IMediaType>(mediaType), this))
return;
if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs<IMediaType>(mediaType), this))
return;
using (new WriteLock(Locker))
{
@@ -1183,6 +1186,8 @@ namespace Umbraco.Core.Services
{
ValidateLocked(mediaType); // throws if invalid
mediaType.CreatorId = userId;
if (mediaType.Description == string.Empty)
mediaType.Description = null;
repository.AddOrUpdate(mediaType);
uow.Commit();
@@ -1192,7 +1197,7 @@ namespace Umbraco.Core.Services
}
SavedMediaType.RaiseEvent(new SaveEventArgs<IMediaType>(mediaType, false), this);
Audit(AuditType.Save, string.Format("Save MediaType performed by user"), userId, mediaType.Id);
Audit(AuditType.Save, string.Format("Save MediaType performed by user"), userId, mediaType.Id);
}
/// <summary>
@@ -1205,7 +1210,7 @@ namespace Umbraco.Core.Services
var asArray = mediaTypes.ToArray();
if (SavingMediaType.IsRaisedEventCancelled(new SaveEventArgs<IMediaType>(asArray), this))
return;
return;
using (new WriteLock(Locker))
{
@@ -1220,6 +1225,8 @@ namespace Umbraco.Core.Services
foreach (var mediaType in asArray)
{
mediaType.CreatorId = userId;
if (mediaType.Description == string.Empty)
mediaType.Description = null;
repository.AddOrUpdate(mediaType);
}
@@ -1231,7 +1238,7 @@ namespace Umbraco.Core.Services
}
SavedMediaType.RaiseEvent(new SaveEventArgs<IMediaType>(asArray, false), this);
Audit(AuditType.Save, string.Format("Save MediaTypes performed by user"), userId, -1);
Audit(AuditType.Save, string.Format("Save MediaTypes performed by user"), userId, -1);
}
/// <summary>
@@ -1242,8 +1249,8 @@ namespace Umbraco.Core.Services
/// <remarks>Deleting a <see cref="IMediaType"/> will delete all the <see cref="IMedia"/> objects based on this <see cref="IMediaType"/></remarks>
public void Delete(IMediaType mediaType, int userId = 0)
{
if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs<IMediaType>(mediaType), this))
return;
if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs<IMediaType>(mediaType), this))
return;
using (new WriteLock(Locker))
{
_mediaService.DeleteMediaOfType(mediaType.Id, userId);
@@ -1275,7 +1282,7 @@ namespace Umbraco.Core.Services
var asArray = mediaTypes.ToArray();
if (DeletingMediaType.IsRaisedEventCancelled(new DeleteEventArgs<IMediaType>(asArray), this))
return;
return;
using (new WriteLock(Locker))
{
foreach (var mediaType in asArray)
@@ -1385,40 +1392,40 @@ namespace Umbraco.Core.Services
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IContentType>> DeletingContentType;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IContentType>> DeletedContentType;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IContentType>> DeletedContentType;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IMediaType>> DeletingMediaType;
/// <summary>
/// Occurs before Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IMediaType>> DeletingMediaType;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IMediaType>> DeletedMediaType;
/// <summary>
/// Occurs after Delete
/// </summary>
public static event TypedEventHandler<IContentTypeService, DeleteEventArgs<IMediaType>> DeletedMediaType;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IContentType>> SavingContentType;
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IContentType>> SavingContentType;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IContentType>> SavedContentType;
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IContentType>> SavedContentType;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IMediaType>> SavingMediaType;
/// <summary>
/// Occurs before Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IMediaType>> SavingMediaType;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IMediaType>> SavedMediaType;
/// <summary>
/// Occurs after Save
/// </summary>
public static event TypedEventHandler<IContentTypeService, SaveEventArgs<IMediaType>> SavedMediaType;
/// <summary>
/// Occurs before Move
@@ -5,6 +5,7 @@ using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
@@ -70,5 +71,20 @@ namespace Umbraco.Core.Services
return toUpdate;
}
/// <summary>
/// Given the path of a content item, this will return true if the content item exists underneath a list view content item
/// </summary>
/// <param name="contentPath"></param>
/// <returns></returns>
public bool HasContainerInPath(string contentPath)
{
using (var uow = UowProvider.GetUnitOfWork())
{
// can use same repo for both content and media
var repository = RepositoryFactory.CreateContentTypeRepository(uow);
return repository.HasContainerInPath(contentPath);
}
}
}
}
+7 -2
View File
@@ -361,8 +361,13 @@ namespace Umbraco.Core.Services
/// <param name="userId">Id of the user issueing the save</param>
public void Save(IDataTypeDefinition dataTypeDefinition, int userId = 0)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return;
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IDataTypeDefinition>(dataTypeDefinition), this))
return;
if (string.IsNullOrWhiteSpace(dataTypeDefinition.Name))
{
throw new ArgumentException("Cannot save datatype with empty name.");
}
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateDataTypeDefinitionRepository(uow))
+16 -2
View File
@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml;
using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -95,6 +96,19 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IContentService : IService
{
/// <summary>
/// Gets all XML entries found in the cmsContentXml table based on the given path
/// </summary>
/// <param name="path">Path starts with</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
/// <remarks>
/// If -1 is passed, then this will return all content xml entries, otherwise will return all descendents from the path
/// </remarks>
IEnumerable<XElement> GetPagedXmlEntries(string path, long pageIndex, int pageSize, out long totalRecords);
/// <summary>
/// This builds the Xml document used for the XML cache
/// </summary>
@@ -245,7 +259,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerable<IContent> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords,
string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
@@ -259,7 +273,7 @@ namespace Umbraco.Core.Services
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IContent> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
/// <summary>
/// Gets a collection of <see cref="IContent"/> objects by Parent Id
@@ -11,6 +11,13 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IContentTypeService : IService
{
/// <summary>
/// Given the path of a content item, this will return true if the content item exists underneath a list view content item
/// </summary>
/// <param name="contentPath"></param>
/// <returns></returns>
bool HasContainerInPath(string contentPath);
int CountContentTypes();
int CountMediaTypes();
+19 -2
View File
@@ -80,6 +80,7 @@ namespace Umbraco.Core.Services
/// </param>
void RebuildXmlStructures(params int[] contentTypeIds);
int CountNotTrashed(string contentTypeAlias = null);
int Count(string contentTypeAlias = null);
int CountChildren(int parentId, string contentTypeAlias = null);
int CountDescendants(int parentId, string contentTypeAlias = null);
@@ -166,10 +167,26 @@ namespace Umbraco.Core.Services
IEnumerable<IMedia> GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, string filter);
/// <summary>
/// Gets a collection of <see cref="IMedia"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Children from</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records query would return without paging</param>
/// <param name="orderBy">Field to order by</param>
/// <param name="orderDirection">Direction to order by</param>
/// <param name="orderBySystemField">Flag to indicate when ordering by system field</param>
/// <param name="filter">Search text filter</param>
/// <param name="contentTypeFilter">A list of content type Ids to filter the list by</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IMedia> GetPagedChildren(int id, long pageIndex, int pageSize, out long totalRecords,
string orderBy, Direction orderDirection, bool orderBySystemField, string filter, int[] contentTypeFilter);
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
IEnumerable<IMedia> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalRecords,
string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
/// <summary>
/// Gets a collection of <see cref="IMedia"/> objects by Parent Id
@@ -183,7 +200,7 @@ namespace Umbraco.Core.Services
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
IEnumerable<IMedia> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalRecords,
string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "");
string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "");
/// <summary>
/// Gets a collection of <see cref="IMedia"/> objects by Parent Id
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Xml.Linq;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
@@ -13,6 +14,15 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IMemberService : IMembershipMemberService
{
/// <summary>
/// Gets all XML entries found in the cmsContentXml table
/// </summary>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of content items</returns>
IEnumerable<XElement> GetPagedXmlEntries(long pageIndex, int pageSize, out long totalRecords);
/// <summary>
/// Rebuilds all xml content in the cmsContentXml table for all documents
/// </summary>
+16 -11
View File
@@ -142,19 +142,24 @@ namespace Umbraco.Core.Services
/// <param name="userId">Optional Id of the user deleting the macro</param>
public void Save(IMacro macro, int userId = 0)
{
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMacro>(macro), this))
return;
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMacroRepository(uow))
{
repository.AddOrUpdate(macro);
uow.Commit();
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMacro>(macro), this))
return;
Saved.RaiseEvent(new SaveEventArgs<IMacro>(macro, false), this);
}
if (string.IsNullOrWhiteSpace(macro.Name))
{
throw new ArgumentException("Cannot save macro with empty name.");
}
Audit(AuditType.Save, "Save Macro performed by user", userId, -1);
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMacroRepository(uow))
{
repository.AddOrUpdate(macro);
uow.Commit();
Saved.RaiseEvent(new SaveEventArgs<IMacro>(macro, false), this);
}
Audit(AuditType.Save, "Save Macro performed by user", userId, -1);
}
///// <summary>
+54 -6
View File
@@ -7,7 +7,6 @@ using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml.Linq;
using Umbraco.Core.Auditing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
@@ -17,9 +16,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.SqlSyntax;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Core.Publishing;
namespace Umbraco.Core.Services
{
@@ -248,6 +245,29 @@ namespace Umbraco.Core.Services
}
}
public int CountNotTrashed(string contentTypeAlias = null)
{
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMediaRepository(uow))
using (var mediaTypeRepository = RepositoryFactory.CreateMediaTypeRepository(uow))
{
var mediaTypeId = 0;
if (contentTypeAlias.IsNullOrWhiteSpace() == false)
{
var mediaType = mediaTypeRepository.Get(contentTypeAlias);
if (mediaType == null) return 0;
mediaTypeId = mediaType.Id;
}
var query = Query<IMedia>.Builder.Where(media => media.Trashed == false);
if (mediaTypeId > 0)
{
query.Where(media => media.ContentTypeId == mediaTypeId);
}
return repository.Count(query);
}
}
public int Count(string contentTypeAlias = null)
{
var uow = UowProvider.GetUnitOfWork();
@@ -435,6 +455,25 @@ namespace Umbraco.Core.Services
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IMedia> GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren,
string orderBy, Direction orderDirection, bool orderBySystemField, string filter)
{
return GetPagedChildren(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter, null);
}
/// <summary>
/// Gets a collection of <see cref="IMedia"/> objects by Parent Id
/// </summary>
/// <param name="id">Id of the Parent to retrieve Children from</param>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalChildren">Total records query would return without paging</param>
/// <param name="orderBy">Field to order by</param>
/// <param name="orderDirection">Direction to order by</param>
/// <param name="orderBySystemField">Flag to indicate when ordering by system field</param>
/// <param name="filter">Search text filter</param>
/// <param name="contentTypeFilter">A list of content type Ids to filter the list by</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IMedia> GetPagedChildren(int id, long pageIndex, int pageSize, out long totalChildren,
string orderBy, Direction orderDirection, bool orderBySystemField, string filter, int[] contentTypeFilter)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
@@ -443,6 +482,11 @@ namespace Umbraco.Core.Services
var query = Query<IMedia>.Builder;
query.Where(x => x.ParentId == id);
if (contentTypeFilter != null && contentTypeFilter.Length > 0)
{
query.Where(x => contentTypeFilter.Contains(x.ContentTypeId));
}
var medias = repository.GetPagedResultsByQuery(query, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, orderBySystemField, filter);
return medias;
@@ -451,7 +495,7 @@ namespace Umbraco.Core.Services
[Obsolete("Use the overload with 'long' parameter types instead")]
[EditorBrowsable(EditorBrowsableState.Never)]
public IEnumerable<IMedia> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
public IEnumerable<IMedia> GetPagedDescendants(int id, int pageIndex, int pageSize, out int totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
long total;
var result = GetPagedDescendants(id, Convert.ToInt64(pageIndex), pageSize, out total, orderBy, orderDirection, true, filter);
@@ -470,7 +514,7 @@ namespace Umbraco.Core.Services
/// <param name="orderDirection">Direction to order by</param>
/// <param name="filter">Search text filter</param>
/// <returns>An Enumerable list of <see cref="IContent"/> objects</returns>
public IEnumerable<IMedia> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "Path", Direction orderDirection = Direction.Ascending, string filter = "")
public IEnumerable<IMedia> GetPagedDescendants(int id, long pageIndex, int pageSize, out long totalChildren, string orderBy = "path", Direction orderDirection = Direction.Ascending, string filter = "")
{
return GetPagedDescendants(id, pageIndex, pageSize, out totalChildren, orderBy, orderDirection, true, filter);
}
@@ -837,7 +881,11 @@ namespace Umbraco.Core.Services
{
return OperationStatus.Cancelled(evtMsgs);
}
}
if (string.IsNullOrWhiteSpace(media.Name))
{
throw new ArgumentException("Cannot save media with empty name.");
}
var uow = UowProvider.GetUnitOfWork();
@@ -1224,7 +1272,7 @@ namespace Umbraco.Core.Services
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMediaRepository(uow))
{
var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, out totalRecords);
var contents = repository.GetPagedXmlEntriesByPath(path, pageIndex, pageSize, null, out totalRecords);
return contents;
}
}
@@ -612,6 +612,26 @@ namespace Umbraco.Core.Services
Audit(AuditType.Publish, "MemberService.RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1);
}
/// <summary>
/// Gets paged member descendants as XML by path
/// </summary>
/// <param name="pageIndex">Page number</param>
/// <param name="pageSize">Page size</param>
/// <param name="totalRecords">Total records the query would return without paging</param>
/// <returns>A paged enumerable of XML entries of member items</returns>
public IEnumerable<XElement> GetPagedXmlEntries(long pageIndex, int pageSize, out long totalRecords)
{
Mandate.ParameterCondition(pageIndex >= 0, "pageIndex");
Mandate.ParameterCondition(pageSize > 0, "pageSize");
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMemberRepository(uow))
{
var contents = repository.GetPagedXmlEntriesByPath("-1", pageIndex, pageSize, null, out totalRecords);
return contents;
}
}
#endregion
#region IMembershipMemberService Implementation
@@ -978,6 +998,11 @@ namespace Umbraco.Core.Services
}
}
if (string.IsNullOrWhiteSpace(entity.Name))
{
throw new ArgumentException("Cannot save member with empty name.");
}
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMemberRepository(uow))
{
@@ -2,12 +2,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Umbraco.Core.Auditing;
using Umbraco.Core.Events;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.UnitOfWork;
namespace Umbraco.Core.Services
@@ -78,12 +76,19 @@ namespace Umbraco.Core.Services
if (Saving.IsRaisedEventCancelled(new SaveEventArgs<IMemberType>(memberType), this))
return;
if (string.IsNullOrWhiteSpace(memberType.Name))
{
throw new ArgumentException("Cannot save MemberType with empty name.");
}
using (new WriteLock(Locker))
{
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateMemberTypeRepository(uow))
{
memberType.CreatorId = userId;
if (memberType.Description == string.Empty)
memberType.Description = null;
repository.AddOrUpdate(memberType);
uow.Commit();
@@ -109,6 +114,8 @@ namespace Umbraco.Core.Services
foreach (var memberType in asArray)
{
memberType.CreatorId = userId;
if (memberType.Description == string.Empty)
memberType.Description = null;
repository.AddOrUpdate(memberType);
}
+22
View File
@@ -125,6 +125,11 @@ namespace Umbraco.Core.Services
{
if (userType == null) throw new ArgumentNullException("userType");
if (string.IsNullOrWhiteSpace(username))
{
throw new ArgumentException("Cannot create user with empty username.");
}
//TODO: PUT lock here!!
var uow = UowProvider.GetUnitOfWork();
@@ -312,6 +317,15 @@ namespace Umbraco.Core.Services
return;
}
if (string.IsNullOrWhiteSpace(entity.Username))
{
throw new ArgumentException("Cannot save user with empty username.");
}
if (string.IsNullOrWhiteSpace(entity.Name))
{
throw new ArgumentException("Cannot save user with empty name.");
}
var uow = UowProvider.GetUnitOfWork();
using (var repository = RepositoryFactory.CreateUserRepository(uow))
{
@@ -353,6 +367,14 @@ namespace Umbraco.Core.Services
{
foreach (var member in entities)
{
if (string.IsNullOrWhiteSpace(member.Username))
{
throw new ArgumentException("Cannot save user with empty username.");
}
if (string.IsNullOrWhiteSpace(member.Name))
{
throw new ArgumentException("Cannot save user with empty name.");
}
repository.AddOrUpdate(member);
}
//commit the whole lot in one go
-24
View File
@@ -1,24 +0,0 @@
namespace Umbraco.Core.Sync
{
/// <summary>
/// The type of Cold Boot taking place
/// </summary>
public enum ColdBootType
{
/// <summary>
/// Indicates there was no cold boot
/// </summary>
NoColdBoot,
/// <summary>
/// This indicates that there is no lasysynced file for the current machine/appid
/// </summary>
NeverSynced,
/// <summary>
/// This indicates that the number of instructions needing to be processed exceeds the value stored
/// for the <see cref="DatabaseServerMessengerOptions.MaxProcessingInstructionCount"/>
/// </summary>
ExceedsMaxProcessingInstructionCount
}
}
@@ -43,11 +43,6 @@ namespace Umbraco.Core.Sync
protected DatabaseServerMessengerOptions Options { get; private set; }
protected ApplicationContext ApplicationContext { get { return _appContext; } }
/// <summary>
/// Returns the cold boot type detected for the current app domain
/// </summary>
public ColdBootType ColdBootType { get; private set; }
public DatabaseServerMessenger(ApplicationContext appContext, bool distributedEnabled, DatabaseServerMessengerOptions options)
: base(distributedEnabled)
{
@@ -151,12 +146,11 @@ namespace Umbraco.Core.Sync
/// </remarks>
private void Initialize()
{
ColdBootType = ColdBootType.NoColdBoot;
lock (_locko)
{
if (_released) return;
var coldboot = false;
if (_lastId < 0) // never synced before
{
// we haven't synced - in this case we aren't going to sync the whole thing, we will assume this is a new
@@ -165,7 +159,7 @@ namespace Umbraco.Core.Sync
+ " The server will build its caches and indexes, and then adjust its last synced Id to the latest found in"
+ " the database and maintain cache updates based on that Id.");
ColdBootType = ColdBootType.NeverSynced;
coldboot = true;
}
else
{
@@ -181,11 +175,11 @@ namespace Umbraco.Core.Sync
+ " to the latest found in the database and maintain cache updates based on that Id.",
() => count, () => Options.MaxProcessingInstructionCount);
ColdBootType = ColdBootType.ExceedsMaxProcessingInstructionCount;
coldboot = true;
}
}
if (ColdBootType != ColdBootType.NoColdBoot)
if (coldboot)
{
// go get the last id in the db and store it
// note: do it BEFORE initializing otherwise some instructions might get lost
+8 -4
View File
@@ -49,8 +49,9 @@
<Reference Include="ICSharpCode.SharpZipLib, Version=0.86.0.518, Culture=neutral, PublicKeyToken=1b03e6acf1164f73, processorArchitecture=MSIL">
<HintPath>..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll</HintPath>
</Reference>
<Reference Include="ImageProcessor, Version=2.5.1.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ImageProcessor.2.5.1\lib\net45\ImageProcessor.dll</HintPath>
<Reference Include="ImageProcessor, Version=2.5.2.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\ImageProcessor.2.5.2\lib\net45\ImageProcessor.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="log4net, Version=1.2.11.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\packages\log4net-mediumtrust.2.0.0\lib\log4net.dll</HintPath>
@@ -178,6 +179,8 @@
<Compile Include="Configuration\BaseRest\ExtensionElement.cs" />
<Compile Include="Configuration\BaseRest\ExtensionElementCollection.cs" />
<Compile Include="Configuration\BaseRest\MethodElement.cs" />
<Compile Include="Configuration\ContentXmlStorage.cs" />
<Compile Include="Configuration\CoreDebug.cs" />
<Compile Include="Configuration\Dashboard\AccessElement.cs" />
<Compile Include="Configuration\Dashboard\AccessItem.cs" />
<Compile Include="Configuration\Dashboard\AccessType.cs" />
@@ -298,6 +301,7 @@
<Compile Include="DateTimeExtensions.cs" />
<Compile Include="DecimalExtensions.cs" />
<Compile Include="DelegateExtensions.cs" />
<Compile Include="Diagnostics\MiniDump.cs" />
<Compile Include="DictionaryExtensions.cs" />
<Compile Include="Dictionary\CultureDictionaryFactoryResolver.cs" />
<Compile Include="Dictionary\ICultureDictionaryFactory.cs" />
@@ -463,9 +467,11 @@
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSevenThreeOne\UpdateUserLanguagesToIsoCode.cs" />
<Compile Include="Persistence\PocoDataDataReader.cs" />
<Compile Include="Persistence\Querying\QueryExtensions.cs" />
<Compile Include="Persistence\Querying\SqlExpressionExtensions.cs" />
<Compile Include="Persistence\RecordPersistenceType.cs" />
<Compile Include="Persistence\Relators\AccessRulesRelator.cs" />
<Compile Include="Persistence\Repositories\AuditRepository.cs" />
<Compile Include="Persistence\Repositories\BaseQueryType.cs" />
<Compile Include="Persistence\Repositories\EntityContainerRepository.cs" />
<Compile Include="Persistence\Repositories\DomainRepository.cs" />
<Compile Include="Persistence\Repositories\ExternalLoginRepository.cs" />
@@ -647,7 +653,6 @@
<Compile Include="PropertyEditors\DefaultPropertyValueConverterAttribute.cs" />
<Compile Include="Persistence\Migrations\Upgrades\TargetVersionSeven\UpdateRelatedLinksData.cs" />
<Compile Include="PropertyEditors\IValueEditor.cs" />
<Compile Include="Persistence\Querying\SqlStringExtensions.cs" />
<Compile Include="Persistence\Querying\StringPropertyMatchType.cs" />
<Compile Include="Persistence\Querying\TextColumnType.cs" />
<Compile Include="Persistence\Querying\ValuePropertyMatchType.cs" />
@@ -1311,7 +1316,6 @@
<Compile Include="Strings\ContentBaseExtensions.cs" />
<Compile Include="Strings\Diff.cs" />
<Compile Include="Sync\BatchedWebServiceServerMessenger.cs" />
<Compile Include="Sync\ColdBootType.cs" />
<Compile Include="Sync\IServerRegistrar2.cs" />
<Compile Include="Sync\ServerRole.cs" />
<Compile Include="Sync\DatabaseServerMessenger.cs" />
-26
View File
@@ -16,32 +16,6 @@ namespace Umbraco.Core
/// </summary>
internal static class XmlExtensions
{
/// <summary>
/// Saves the xml document async
/// </summary>
/// <param name="xdoc"></param>
/// <param name="filename"></param>
/// <returns></returns>
public static async Task SaveAsync(this XmlDocument xdoc, string filename)
{
if (xdoc.DocumentElement == null)
throw new XmlException("Cannot save xml document, there is no root element");
using (var fs = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.Read, bufferSize: 4096, useAsync: true))
using (var xmlWriter = XmlWriter.Create(fs, new XmlWriterSettings
{
Async = true,
Encoding = Encoding.UTF8,
Indent = true
}))
{
//NOTE: There are no nice methods to write it async, only flushing it async. We
// could implement this ourselves but it'd be a very manual process.
xdoc.WriteTo(xmlWriter);
await xmlWriter.FlushAsync().ConfigureAwait(false);
}
}
public static bool HasAttribute(this XmlAttributeCollection attributes, string attributeName)
{
return attributes.Cast<XmlAttribute>().Any(x => x.Name == attributeName);
+1 -1
View File
@@ -2,7 +2,7 @@
<packages>
<package id="AutoMapper" version="3.3.1" targetFramework="net45" />
<package id="HtmlAgilityPack" version="1.4.9" targetFramework="net45" />
<package id="ImageProcessor" version="2.5.1" targetFramework="net45" />
<package id="ImageProcessor" version="2.5.2" targetFramework="net45" />
<package id="log4net-mediumtrust" version="2.0.0" targetFramework="net45" />
<package id="Microsoft.AspNet.Identity.Core" version="2.2.1" targetFramework="net45" />
<package id="Microsoft.AspNet.Identity.Owin" version="2.2.1" targetFramework="net45" />
@@ -16,6 +16,7 @@ using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Reports;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Validators;
using Moq;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
@@ -43,15 +44,18 @@ namespace Umbraco.Tests.Benchmarks
public ModelToSqlExpressionHelperBenchmarks()
{
_contentMapper = new ContentMapper(_syntaxProvider);
_contentMapper.BuildMap();
var contentMapper = new ContentMapper(_syntaxProvider);
contentMapper.BuildMap();
_cachedExpression = new CachedExpression();
var mappingResolver = new Mock<MappingResolver>();
mappingResolver.Setup(resolver => resolver.ResolveMapperByType(It.IsAny<Type>())).Returns(contentMapper);
_mappingResolver = mappingResolver.Object;
}
private readonly ISqlSyntaxProvider _syntaxProvider = new SqlCeSyntaxProvider();
private readonly BaseMapper _contentMapper;
private readonly CachedExpression _cachedExpression;
private readonly MappingResolver _mappingResolver;
[Benchmark(Baseline = true)]
public void WithNonCached()
{
@@ -62,7 +66,7 @@ namespace Umbraco.Tests.Benchmarks
Expression<Func<IContent, bool>> predicate = content =>
content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b);
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>(_syntaxProvider, _contentMapper);
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>(_syntaxProvider, _mappingResolver);
var result = modelToSqlExpressionHelper.Visit(predicate);
}
@@ -80,7 +84,7 @@ namespace Umbraco.Tests.Benchmarks
Expression<Func<IContent, bool>> predicate = content =>
content.Path.StartsWith("-1") && content.Published && (content.ContentTypeId == a || content.ContentTypeId == b);
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>(_syntaxProvider, _contentMapper);
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>(_syntaxProvider, _mappingResolver);
//wrap it!
_cachedExpression.Wrap(predicate);
+4 -1
View File
@@ -13,7 +13,10 @@ namespace Umbraco.Tests.Benchmarks
typeof(BulkInsertBenchmarks),
typeof(ModelToSqlExpressionHelperBenchmarks),
typeof(XmlBenchmarks),
typeof(LinqCastBenchmarks)
typeof(LinqCastBenchmarks),
//typeof(DeepCloneBenchmarks),
typeof(XmlPublishedContentInitBenchmarks),
});
switcher.Run(args);
}
@@ -49,6 +49,9 @@
<Reference Include="BenchmarkDotNet.Toolchains.Roslyn, Version=0.9.9.0, Culture=neutral, PublicKeyToken=aa0ca2f9092cefc4, processorArchitecture=MSIL">
<HintPath>..\packages\BenchmarkDotNet.Toolchains.Roslyn.0.9.9\lib\net45\BenchmarkDotNet.Toolchains.Roslyn.dll</HintPath>
</Reference>
<Reference Include="Castle.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
<HintPath>..\packages\Castle.Core.4.0.0\lib\net45\Castle.Core.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CodeAnalysis, Version=1.3.1.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.CodeAnalysis.Common.1.3.2\lib\net45\Microsoft.CodeAnalysis.dll</HintPath>
</Reference>
@@ -58,6 +61,9 @@
<Reference Include="Microsoft.Diagnostics.Tracing.TraceEvent, Version=1.0.41.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll</HintPath>
</Reference>
<Reference Include="Moq, Version=4.7.0.0, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL">
<HintPath>..\packages\Moq.4.7.0\lib\net45\Moq.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=1.1.37.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll</HintPath>
@@ -91,6 +97,7 @@
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="XmlBenchmarks.cs" />
<Compile Include="XmlPublishedContentInitBenchmarks.cs" />
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
@@ -105,6 +112,10 @@
<Project>{5d3b8245-ada6-453f-a008-50ed04bfe770}</Project>
<Name>Umbraco.Tests</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
<Project>{651e1350-91b6-44b7-bd60-7207006d7003}</Project>
<Name>Umbraco.Web</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Analyzer Include="..\packages\Microsoft.CodeAnalysis.Analyzers.1.1.0\analyzers\dotnet\cs\Microsoft.CodeAnalysis.Analyzers.dll" />
@@ -0,0 +1,315 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnostics.Windows;
using BenchmarkDotNet.Jobs;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web.PublishedCache.XmlPublishedCache;
namespace Umbraco.Tests.Benchmarks
{
[Config(typeof(Config))]
public class XmlPublishedContentInitBenchmarks
{
private class Config : ManualConfig
{
public Config()
{
Add(new MemoryDiagnoser());
//The 'quick and dirty' settings, so it runs a little quicker
// see benchmarkdotnet FAQ
Add(Job.Default
.WithLaunchCount(1) // benchmark process will be launched only once
.WithIterationTime(100) // 100ms per iteration
.WithWarmupCount(3) // 3 warmup iteration
.WithTargetCount(3)); // 3 target iteration
}
}
public XmlPublishedContentInitBenchmarks()
{
_xml10 = Build(10);
_xml100 = Build(100);
_xml1000 = Build(1000);
_xml10000 = Build(10000);
}
private readonly string[] _intAttributes = { "id", "parentID", "nodeType", "level", "writerID", "creatorID", "template", "sortOrder", "isDoc", "isDraft" };
private readonly string[] _strAttributes = { "nodeName", "urlName", "writerName", "creatorName", "path" };
private readonly string[] _dateAttributes = { "createDate", "updateDate" };
private readonly string[] _guidAttributes = { "key", "version" };
private XmlDocument Build(int children)
{
var xml = new XmlDocument();
var root = Build(xml, "Home", 10);
for (int i = 0; i < children; i++)
{
var child = Build(xml, "child" + i, 10);
root.AppendChild(child);
}
xml.AppendChild(root);
return xml;
}
private XmlElement Build(XmlDocument xml, string name, int propertyCount)
{
var random = new Random();
var content = xml.CreateElement(name);
foreach (var p in _intAttributes)
{
var a = xml.CreateAttribute(p);
a.Value = random.Next(1, 9).ToInvariantString();
content.Attributes.Append(a);
}
foreach (var p in _strAttributes)
{
var a = xml.CreateAttribute(p);
a.Value = Guid.NewGuid().ToString();
content.Attributes.Append(a);
}
foreach (var p in _guidAttributes)
{
var a = xml.CreateAttribute(p);
a.Value = Guid.NewGuid().ToString();
content.Attributes.Append(a);
}
foreach (var p in _dateAttributes)
{
var a = xml.CreateAttribute(p);
a.Value = DateTime.Now.ToString("o");
content.Attributes.Append(a);
}
for (int i = 0; i < propertyCount; i++)
{
var prop = xml.CreateElement("prop" + i);
var cdata = xml.CreateCDataSection(string.Join("", Enumerable.Range(0, 10).Select(x => Guid.NewGuid().ToString())));
prop.AppendChild(cdata);
content.AppendChild(prop);
}
return content;
}
private readonly XmlDocument _xml10;
private readonly XmlDocument _xml100;
private readonly XmlDocument _xml1000;
private readonly XmlDocument _xml10000;
//out props
int id, nodeType, level, writerId, creatorId, template, sortOrder;
Guid key, version;
string name, urlName, writerName, creatorName, docTypeAlias, path;
bool isDraft;
DateTime createDate, updateDate;
PublishedContentType publishedContentType;
Dictionary<string, IPublishedProperty> properties;
[Benchmark(Baseline = true, OperationsPerInvoke = 10)]
public void Original_10_Children()
{
OriginalInitializeNode(_xml10.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Original_100_Children()
{
OriginalInitializeNode(_xml100.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Original_1000_Children()
{
OriginalInitializeNode(_xml1000.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Original_10000_Children()
{
OriginalInitializeNode(_xml10000.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Enhanced_10_Children()
{
XmlPublishedContent.InitializeNode(_xml10.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties, GetPublishedContentType);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Enhanced_100_Children()
{
XmlPublishedContent.InitializeNode(_xml100.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties, GetPublishedContentType);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Enhanced_1000_Children()
{
XmlPublishedContent.InitializeNode(_xml1000.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties, GetPublishedContentType);
}
[Benchmark(OperationsPerInvoke = 10)]
public void Enhanced_10000_Children()
{
XmlPublishedContent.InitializeNode(_xml10000.DocumentElement, false, false,
out id, out key, out template, out sortOrder, out name, out writerName, out urlName,
out creatorName, out creatorId, out writerId, out docTypeAlias, out nodeType, out path,
out version, out createDate, out updateDate, out level, out isDraft, out publishedContentType,
out properties, GetPublishedContentType);
}
internal static void OriginalInitializeNode(XmlNode xmlNode, bool legacy, bool isPreviewing,
out int id, out Guid key, out int template, out int sortOrder, out string name, out string writerName, out string urlName,
out string creatorName, out int creatorId, out int writerId, out string docTypeAlias, out int docTypeId, out string path,
out Guid version, out DateTime createDate, out DateTime updateDate, out int level, out bool isDraft,
out PublishedContentType contentType, out Dictionary<string, IPublishedProperty> properties)
{
//initialize the out params with defaults:
writerName = null;
docTypeAlias = null;
id = template = sortOrder = template = creatorId = writerId = docTypeId = level = default(int);
key = version = default(Guid);
name = writerName = urlName = creatorName = docTypeAlias = path = null;
createDate = updateDate = default(DateTime);
isDraft = false;
contentType = null;
properties = null;
//return if this is null
if (xmlNode == null)
{
return;
}
if (xmlNode.Attributes != null)
{
id = int.Parse(xmlNode.Attributes.GetNamedItem("id").Value);
if (xmlNode.Attributes.GetNamedItem("key") != null) // because, migration
key = Guid.Parse(xmlNode.Attributes.GetNamedItem("key").Value);
if (xmlNode.Attributes.GetNamedItem("template") != null)
template = int.Parse(xmlNode.Attributes.GetNamedItem("template").Value);
if (xmlNode.Attributes.GetNamedItem("sortOrder") != null)
sortOrder = int.Parse(xmlNode.Attributes.GetNamedItem("sortOrder").Value);
if (xmlNode.Attributes.GetNamedItem("nodeName") != null)
name = xmlNode.Attributes.GetNamedItem("nodeName").Value;
if (xmlNode.Attributes.GetNamedItem("writerName") != null)
writerName = xmlNode.Attributes.GetNamedItem("writerName").Value;
if (xmlNode.Attributes.GetNamedItem("urlName") != null)
urlName = xmlNode.Attributes.GetNamedItem("urlName").Value;
// Creatorname is new in 2.1, so published xml might not have it!
try
{
creatorName = xmlNode.Attributes.GetNamedItem("creatorName").Value;
}
catch
{
creatorName = writerName;
}
//Added the actual userID, as a user cannot be looked up via full name only...
if (xmlNode.Attributes.GetNamedItem("creatorID") != null)
creatorId = int.Parse(xmlNode.Attributes.GetNamedItem("creatorID").Value);
if (xmlNode.Attributes.GetNamedItem("writerID") != null)
writerId = int.Parse(xmlNode.Attributes.GetNamedItem("writerID").Value);
if (legacy)
{
if (xmlNode.Attributes.GetNamedItem("nodeTypeAlias") != null)
docTypeAlias = xmlNode.Attributes.GetNamedItem("nodeTypeAlias").Value;
}
else
{
docTypeAlias = xmlNode.Name;
}
if (xmlNode.Attributes.GetNamedItem("nodeType") != null)
docTypeId = int.Parse(xmlNode.Attributes.GetNamedItem("nodeType").Value);
if (xmlNode.Attributes.GetNamedItem("path") != null)
path = xmlNode.Attributes.GetNamedItem("path").Value;
if (xmlNode.Attributes.GetNamedItem("version") != null)
version = new Guid(xmlNode.Attributes.GetNamedItem("version").Value);
if (xmlNode.Attributes.GetNamedItem("createDate") != null)
createDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("createDate").Value);
if (xmlNode.Attributes.GetNamedItem("updateDate") != null)
updateDate = DateTime.Parse(xmlNode.Attributes.GetNamedItem("updateDate").Value);
if (xmlNode.Attributes.GetNamedItem("level") != null)
level = int.Parse(xmlNode.Attributes.GetNamedItem("level").Value);
isDraft = (xmlNode.Attributes.GetNamedItem("isDraft") != null);
}
// load data
var dataXPath = legacy ? "data" : "* [not(@isDoc)]";
var nodes = xmlNode.SelectNodes(dataXPath);
contentType = GetPublishedContentType(PublishedItemType.Content, docTypeAlias);
var propertyNodes = new Dictionary<string, XmlNode>();
if (nodes != null)
foreach (XmlNode n in nodes)
{
var attrs = n.Attributes;
if (attrs == null) continue;
var alias = legacy
? attrs.GetNamedItem("alias").Value
: n.Name;
propertyNodes[alias.ToLowerInvariant()] = n;
}
properties = contentType.PropertyTypes.Select(p =>
{
XmlNode n;
return propertyNodes.TryGetValue(p.PropertyTypeAlias.ToLowerInvariant(), out n)
? new XmlPublishedProperty(p, isPreviewing, n)
: new XmlPublishedProperty(p, isPreviewing);
}).Cast<IPublishedProperty>().ToDictionary(
x => x.PropertyTypeAlias,
x => x,
StringComparer.OrdinalIgnoreCase);
}
private static PublishedContentType GetPublishedContentType(PublishedItemType type, string alias)
{
return new PublishedContentType(alias, new string[] {},
new List<PublishedPropertyType>(Enumerable.Range(0, 10).Select(x => new PublishedPropertyType("prop" + x, 0, "test", initConverters:false))));
}
}
}
@@ -4,11 +4,13 @@
<package id="BenchmarkDotNet.Core" version="0.9.9" targetFramework="net45" />
<package id="BenchmarkDotNet.Diagnostics.Windows" version="0.9.9" targetFramework="net45" />
<package id="BenchmarkDotNet.Toolchains.Roslyn" version="0.9.9" targetFramework="net45" />
<package id="Castle.Core" version="4.0.0" targetFramework="net45" />
<package id="Microsoft.CodeAnalysis.Analyzers" version="1.1.0" targetFramework="net45" />
<package id="Microsoft.CodeAnalysis.Common" version="1.3.2" targetFramework="net45" />
<package id="Microsoft.CodeAnalysis.CSharp" version="1.3.2" targetFramework="net45" />
<package id="Microsoft.Diagnostics.Tracing.TraceEvent" version="1.0.41" targetFramework="net45" />
<package id="Microsoft.SqlServer.Compact" version="4.0.8854.1" targetFramework="net45" />
<package id="Moq" version="4.7.0" targetFramework="net45" />
<package id="SqlServerCE" version="4.0.0.1" targetFramework="net45" />
<package id="System.Collections" version="4.0.0" targetFramework="net45" />
<package id="System.Collections.Immutable" version="1.1.37" targetFramework="net45" />
+190
View File
@@ -0,0 +1,190 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
using NUnit.Framework;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Dependencies
{
[TestFixture]
public class NuGet
{
[Test]
public void NuGet_Package_Versions_Are_The_Same_In_All_Package_Config_Files()
{
var packagesAndVersions = GetNuGetPackagesInSolution();
Assert.IsTrue(packagesAndVersions.Any());
var failTest = false;
foreach (var package in packagesAndVersions.OrderBy(x => x.ConfigFilePath))
{
var matchingPackages = packagesAndVersions.Where(x => string.Equals(x.PackageName, package.PackageName, StringComparison.InvariantCultureIgnoreCase));
foreach (var matchingPackage in matchingPackages)
{
if (package.PackageVersion == matchingPackage.PackageVersion)
continue;
Debug.WriteLine("Package '{0}' with version {1} in {2} doesn't match with version {3} in {4}",
package.PackageName, package.PackageVersion, package.ConfigFilePath,
matchingPackage.PackageVersion, matchingPackage.ConfigFilePath);
failTest = true;
}
}
Assert.IsFalse(failTest);
}
[Test]
public void NuSpec_File_Matches_Installed_Dependencies()
{
var solutionDirectory = GetSolutionDirectory();
Assert.NotNull(solutionDirectory);
Assert.NotNull(solutionDirectory.Parent);
var nuSpecPath = solutionDirectory.Parent.FullName + "\\build\\NuSpecs\\";
Assert.IsTrue(Directory.Exists(nuSpecPath));
var packagesAndVersions = GetNuGetPackagesInSolution();
var failTest = false;
var nuSpecFiles = Directory.GetFiles(nuSpecPath);
foreach (var fileName in nuSpecFiles.Where(x => x.EndsWith("nuspec")))
{
var serializer = new XmlSerializer(typeof(NuSpec));
using (var reader = new StreamReader(fileName))
{
var nuspec = (NuSpec)serializer.Deserialize(reader);
var dependencies = nuspec.MetaData.dependencies;
//UmbracoCms.Core has version "[$version$]" - ignore
foreach (var dependency in dependencies.Where(x => x.Id != "UmbracoCms.Core"))
{
var dependencyVersionRange = dependency.Version.Replace("[", string.Empty).Replace("(", string.Empty).Split(',');
var dependencyMinimumVersion = dependencyVersionRange.First().Trim();
var matchingPackages = packagesAndVersions.Where(x => string.Equals(x.PackageName, dependency.Id,
StringComparison.InvariantCultureIgnoreCase)).ToList();
if (matchingPackages.Any() == false)
continue;
// NuGet_Package_Versions_Are_The_Same_In_All_Package_Config_Files test
// guarantees that all packages have one version, solutionwide, so it's okay to take First() here
if (dependencyMinimumVersion != matchingPackages.First().PackageVersion)
{
Debug.WriteLine("NuSpec dependency '{0}' with minimum version {1} in doesn't match with version {2} in the solution",
dependency.Id, dependencyMinimumVersion, matchingPackages.First().PackageVersion);
failTest = true;
}
}
}
}
Assert.IsFalse(failTest);
}
private List<PackageVersionMatcher> GetNuGetPackagesInSolution()
{
var packagesAndVersions = new List<PackageVersionMatcher>();
var solutionDirectory = GetSolutionDirectory();
if (solutionDirectory == null)
return packagesAndVersions;
var packageConfigFiles = new List<FileInfo>();
var packageDirectories =
solutionDirectory.GetDirectories().Where(x =>
x.Name.StartsWith("Umbraco.Tests") == false &&
x.Name.StartsWith("Umbraco.MSBuild.Tasks") == false).ToArray();
foreach (var directory in packageDirectories)
{
var packagesFiles = directory.EnumerateFiles("packages.config");
packageConfigFiles.AddRange(packagesFiles);
}
foreach (var file in packageConfigFiles)
{
//read all and de-duplicate packages
var serializer = new XmlSerializer(typeof(PackageConfigEntries));
using (var reader = new StreamReader(file.FullName))
{
var packages = (PackageConfigEntries)serializer.Deserialize(reader);
packagesAndVersions.AddRange(packages.Package.Select(package =>
new PackageVersionMatcher
{
PackageName = package.Id,
PackageVersion = package.Version,
ConfigFilePath = file.FullName
}));
}
}
return packagesAndVersions;
}
private DirectoryInfo GetSolutionDirectory()
{
var testsDirectory = new FileInfo(TestHelper.MapPathForTest("~/"));
if (testsDirectory.Directory == null)
return null;
if (testsDirectory.Directory.Parent == null || testsDirectory.Directory.Parent.Parent == null)
return null;
var solutionDirectory = testsDirectory.Directory.Parent.Parent.Parent;
return solutionDirectory;
}
}
public class PackageVersionMatcher
{
public string ConfigFilePath { get; set; }
public string PackageName { get; set; }
public string PackageVersion { get; set; }
}
[XmlType(AnonymousType = true)]
[XmlRoot(Namespace = "", IsNullable = false, ElementName = "packages")]
public class PackageConfigEntries
{
[XmlElement("package")]
public PackagesPackage[] Package { get; set; }
}
[XmlType(AnonymousType = true, TypeName = "package")]
public class PackagesPackage
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd")]
[XmlRoot(Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", IsNullable = false, ElementName = "package")]
public class NuSpec
{
[XmlElement("metadata")]
public Metadata MetaData { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "metadata")]
public class Metadata
{
[XmlArrayItem("dependency", IsNullable = false)]
//TODO: breaks when renamed to capital D
public Dependency[] dependencies { get; set; }
}
[XmlType(AnonymousType = true, Namespace = "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd", TypeName = "dependencies")]
public class Dependency
{
[XmlAttribute(AttributeName = "id")]
public string Id { get; set; }
[XmlAttribute(AttributeName = "version")]
public string Version { get; set; }
}
}
+92
View File
@@ -0,0 +1,92 @@
using System;
using NUnit.Framework;
using Umbraco.Core.Models;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.Issues
{
[TestFixture]
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
public class U9560 : BaseDatabaseFactoryTest
{
[Test]
public void Test()
{
// create a content type and some properties
var contentType = new ContentType(-1);
contentType.Alias = "test";
contentType.Name = "test";
var propertyType = new PropertyType("test", DataTypeDatabaseType.Ntext, "prop") { Name = "Prop", Description = "", Mandatory = false, SortOrder = 1, DataTypeDefinitionId = -88 };
contentType.PropertyTypeCollection.Add(propertyType);
ServiceContext.ContentTypeService.Save(contentType);
var aliasName = string.Empty;
// read fields, same as what we do with PetaPoco Fetch<dynamic>
var db = DatabaseContext.Database;
db.OpenSharedConnection();
try
{
var conn = db.Connection;
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT mandatory, dataTypeId, propertyTypeGroupId, contentTypeId, sortOrder, alias, name, validationRegExp, description from cmsPropertyType where id=" + propertyType.Id;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
for (var i = 0; i < reader.FieldCount; i++)
Console.WriteLine(reader.GetName(i));
aliasName = reader.GetName(5);
}
}
}
finally
{
db.CloseSharedConnection();
}
// note that although the query is for 'alias' the field is named 'Alias'
Assert.AreEqual("Alias", aliasName);
// try differently
db.OpenSharedConnection();
try
{
var conn = db.Connection;
var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT mandatory, dataTypeId, propertyTypeGroupId, contentTypeId, sortOrder, alias as alias, name, validationRegExp, description from cmsPropertyType where id=" + propertyType.Id;
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
for (var i = 0; i < reader.FieldCount; i++)
Console.WriteLine(reader.GetName(i));
aliasName = reader.GetName(5);
}
}
}
finally
{
db.CloseSharedConnection();
}
// and now it is OK
Assert.AreEqual("alias", aliasName);
// get the legacy content type
var legacyContentType = new umbraco.cms.businesslogic.ContentType(contentType.Id);
Assert.AreEqual("test", legacyContentType.Alias);
// get the legacy properties
var legacyProperties = legacyContentType.PropertyTypes;
// without the fix, due to some (swallowed) inner exception, we have no properties
//Assert.IsNull(legacyProperties);
// thanks to the fix, it works
Assert.IsNotNull(legacyProperties);
Assert.AreEqual(1, legacyProperties.Count);
Assert.AreEqual("prop", legacyProperties[0].Alias);
}
}
}
@@ -34,7 +34,7 @@ namespace Umbraco.Tests.Persistence
{
DatabaseContext = _dbContext,
IsReady = true
};
};
}
[TearDown]
@@ -44,6 +44,13 @@ namespace Umbraco.Tests.Persistence
ApplicationContext.Current = null;
}
[Test]
public void Database_Connection()
{
var db = _dbContext.Database;
Assert.IsNull(db.Connection);
}
[Test]
public void Can_Verify_Single_Database_Instance()
{
@@ -99,7 +106,7 @@ namespace Umbraco.Tests.Persistence
var appCtx = new ApplicationContext(
new DatabaseContext(Mock.Of<IDatabaseFactory>(), Mock.Of<ILogger>(), Mock.Of<ISqlSyntaxProvider>(), "test"),
new ServiceContext(migrationEntryService: Mock.Of<IMigrationEntryService>()),
new ServiceContext(migrationEntryService: Mock.Of<IMigrationEntryService>()),
CacheHelper.CreateDisabledCacheHelper(),
new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
@@ -1,5 +1,6 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Moq;
using NUnit.Framework;
@@ -16,19 +17,52 @@ namespace Umbraco.Tests.Persistence.Querying
[TestFixture]
public class ExpressionTests : BaseUsingSqlCeSyntax
{
// [Test]
// public void Can_Query_With_Content_Type_Alias()
// {
// //Arrange
// Expression<Func<IMedia, bool>> predicate = content => content.ContentType.Alias == "Test";
// var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>();
// var result = modelToSqlExpressionHelper.Visit(predicate);
[Test]
public void Equals_Claus_With_Two_Entity_Values()
{
var dataType = new DataTypeDefinition(-1, "Test")
{
Id = 12345
};
Expression<Func<PropertyType, bool>> predicate = p => p.DataTypeDefinitionId == dataType.Id;
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<PropertyType>();
var result = modelToSqlExpressionHelper.Visit(predicate);
// Debug.Print("Model to Sql ExpressionHelper: \n" + result);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
// Assert.AreEqual("[cmsContentType].[alias] = @0", result);
// Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]);
// }
Assert.AreEqual("([cmsPropertyType].[dataTypeId] = @0)", result);
Assert.AreEqual(12345, modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
public void Can_Query_With_Content_Type_Alias()
{
//Arrange
Expression<Func<IMedia, bool>> predicate = content => content.ContentType.Alias == "Test";
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("([cmsContentType].[alias] = @0)", result);
Assert.AreEqual("Test", modelToSqlExpressionHelper.GetSqlParameters()[0]);
}
[Test]
public void Can_Query_With_Content_Type_Aliases()
{
//Arrange
var aliases = new[] {"Test1", "Test2"};
Expression<Func<IMedia, bool>> predicate = content => aliases.Contains(content.ContentType.Alias);
var modelToSqlExpressionHelper = new ModelToSqlExpressionVisitor<IContent>();
var result = modelToSqlExpressionHelper.Visit(predicate);
Debug.Print("Model to Sql ExpressionHelper: \n" + result);
Assert.AreEqual("[cmsContentType].[alias] IN (@1,@2)", result);
Assert.AreEqual("Test1", modelToSqlExpressionHelper.GetSqlParameters()[1]);
Assert.AreEqual("Test2", modelToSqlExpressionHelper.GetSqlParameters()[2]);
}
[Test]
public void CachedExpression_Can_Verify_Path_StartsWith_Predicate_In_Same_Result()
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
@@ -21,7 +22,6 @@ using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
using umbraco.editorControls.tinyMCE3;
using umbraco.interfaces;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Tests.Persistence.Repositories
@@ -36,11 +36,15 @@ namespace Umbraco.Tests.Persistence.Repositories
base.Initialize();
CreateTestData();
VersionableRepositoryBase<int, IContent>.ThrowOnWarning = true;
}
[TearDown]
public override void TearDown()
{
VersionableRepositoryBase<int, IContent>.ThrowOnWarning = false;
base.TearDown();
}
@@ -67,6 +71,191 @@ namespace Umbraco.Tests.Persistence.Repositories
return repository;
}
[Test]
public void Get_Always_Returns_Latest_Version()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
IContent content1;
var versions = new List<Guid>();
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage");
content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
//save version
contentTypeRepository.AddOrUpdate(hasPropertiesContentType);
repository.AddOrUpdate(content1);
unitOfWork.Commit();
versions.Add(content1.Version);
//publish version
content1.ChangePublishedState(PublishedState.Published);
repository.AddOrUpdate(content1);
unitOfWork.Commit();
versions.Add(content1.Version);
//change something and make a pending version
content1.Name = "new name";
content1.ChangePublishedState(PublishedState.Saved);
repository.AddOrUpdate(content1);
unitOfWork.Commit();
versions.Add(content1.Version);
}
// Assert
Assert.AreEqual(3, versions.Distinct().Count());
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
var content = repository.GetByQuery(new Query<IContent>().Where(c => c.Id == content1.Id)).ToArray()[0];
Assert.AreEqual(versions[2], content.Version);
content = repository.Get(content1.Id);
Assert.AreEqual(versions[2], content.Version);
foreach (var version in versions)
{
content = repository.GetByVersion(version);
Assert.IsNotNull(content);
Assert.AreEqual(version, content.Version);
}
}
}
[Test]
public void Deal_With_Corrupt_Duplicate_Newest_Published_Flags()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
IContent content1;
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage");
content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
contentTypeRepository.AddOrUpdate(hasPropertiesContentType);
repository.AddOrUpdate(content1);
unitOfWork.Commit();
}
var versionDtos = new List<ContentVersionDto>();
//Now manually corrupt the data
var versions = new[] { Guid.NewGuid(), Guid.NewGuid() };
for (var index = 0; index < versions.Length; index++)
{
var version = versions[index];
var versionDate = DateTime.Now.AddMinutes(index);
var versionDto = new ContentVersionDto
{
NodeId = content1.Id,
VersionDate = versionDate,
VersionId = version
};
this.DatabaseContext.Database.Insert(versionDto);
versionDtos.Add(versionDto);
this.DatabaseContext.Database.Insert(new DocumentDto
{
Newest = true,
NodeId = content1.Id,
Published = true,
Text = content1.Name,
VersionId = version,
WriterUserId = 0,
UpdateDate = versionDate,
TemplateId = content1.Template == null || content1.Template.Id <= 0 ? null : (int?) content1.Template.Id
});
}
// Assert
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
var content = repository.GetByQuery(new Query<IContent>().Where(c => c.Id == content1.Id)).ToArray();
Assert.AreEqual(1, content.Length);
Assert.AreEqual(content[0].Version, versionDtos.Single(x => x.Id == versionDtos.Max(y => y.Id)).VersionId);
Assert.AreEqual(content[0].UpdateDate.ToString(CultureInfo.InvariantCulture), versionDtos.Single(x => x.Id == versionDtos.Max(y => y.Id)).VersionDate.ToString(CultureInfo.InvariantCulture));
var contentItem = repository.GetByVersion(content1.Version);
Assert.IsNotNull(contentItem);
contentItem = repository.Get(content1.Id);
Assert.IsNotNull(contentItem);
Assert.AreEqual(contentItem.UpdateDate.ToString(CultureInfo.InvariantCulture), versionDtos.Single(x => x.Id == versionDtos.Max(y => y.Id)).VersionDate.ToString(CultureInfo.InvariantCulture));
Assert.AreEqual(contentItem.Version, versionDtos.Single(x => x.Id == versionDtos.Max(y => y.Id)).VersionId);
var allVersions = repository.GetAllVersions(content[0].Id);
var allKnownVersions = versionDtos.Select(x => x.VersionId).Union(new[]{ content1.Version }).ToArray();
Assert.IsTrue(allKnownVersions.ContainsAll(allVersions.Select(x => x.Version)));
Assert.IsTrue(allVersions.Select(x => x.Version).ContainsAll(allKnownVersions));
}
}
/// <summary>
/// This tests the regression issue of U4-9438
/// </summary>
/// <remarks>
/// The problem was the iteration of the property data in VersionableRepositoryBase when a content item
/// in the list actually doesn't have any property types, it would still skip over a property row.
/// To test, we have 3 content items, the first has properties, the second doesn't and the third does.
/// </remarks>
[Test]
public void Property_Data_Assigned_Correctly()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
var allContent = new List<IContent>();
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
var emptyContentType = MockedContentTypes.CreateBasicContentType();
var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage");
var content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
var content2 = MockedContent.CreateBasicContent(emptyContentType);
var content3 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
// Act
contentTypeRepository.AddOrUpdate(emptyContentType);
contentTypeRepository.AddOrUpdate(hasPropertiesContentType);
repository.AddOrUpdate(content1);
repository.AddOrUpdate(content2);
repository.AddOrUpdate(content3);
unitOfWork.Commit();
allContent.Add(content1);
allContent.Add(content2);
allContent.Add(content3);
}
// Assert
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
//this will cause the GetPropertyCollection to execute and we need to ensure that
// all of the properties and property types are all correct
var result = repository.GetAll(allContent.Select(x => x.Id).ToArray()).ToArray();
foreach (var content in result)
{
foreach (var contentProperty in content.Properties)
{
//prior to the fix, the 2nd document iteration in the GetPropertyCollection would have caused
//the enumerator to move forward past the first property of the 3rd document which would have
//ended up not assiging a property to the 3rd document. This would have ended up with the 3rd
//document still having 3 properties but the last one would not have been assigned an identity
//because the property data would not have been assigned.
Assert.IsTrue(contentProperty.HasIdentity);
}
}
}
}
[Test]
public void Rebuild_Xml_Structures_With_Non_Latest_Version()
{
@@ -166,6 +355,62 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Rebuild_All_Xml_Structures_Ensure_Orphaned_Are_Removed()
{
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
//delete all xml
unitOfWork.Database.Execute("DELETE FROM cmsContentXml");
var contentType1 = MockedContentTypes.CreateSimpleContentType("Textpage1", "Textpage1");
contentTypeRepository.AddOrUpdate(contentType1);
var allCreated = new List<IContent>();
for (var i = 0; i < 100; i++)
{
//These will be non-published so shouldn't show up
var c1 = MockedContent.CreateSimpleContent(contentType1);
repository.AddOrUpdate(c1);
allCreated.Add(c1);
}
for (var i = 0; i < 100; i++)
{
var c1 = MockedContent.CreateSimpleContent(contentType1);
c1.ChangePublishedState(PublishedState.Published);
repository.AddOrUpdate(c1);
allCreated.Add(c1);
}
unitOfWork.Commit();
//now create some versions of this content - this shouldn't affect the xml structures saved
for (int i = 0; i < allCreated.Count; i++)
{
allCreated[i].Name = "blah" + i;
repository.AddOrUpdate(allCreated[i]);
}
unitOfWork.Commit();
//Add some extra orphaned rows that shouldn't be there
var notPublished = MockedContent.CreateSimpleContent(contentType1);
repository.AddOrUpdate(notPublished);
unitOfWork.Commit();
//Force add it
unitOfWork.Database.Insert(new ContentXmlDto
{
NodeId = notPublished.Id,
Xml = "<test></test>"
});
repository.RebuildXmlStructures(media => new XElement("test"), 10);
Assert.AreEqual(100, unitOfWork.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM cmsContentXml"));
}
}
[Test]
public void Rebuild_All_Xml_Structures_For_Content_Type()
{
@@ -172,6 +172,38 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Can_Get_All_Containers()
{
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
EntityContainer container1, container2, container3;
using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid))
{
container1 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container1" };
containerRepository.AddOrUpdate(container1);
container2 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container2" };
containerRepository.AddOrUpdate(container2);
container3 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid) { Name = "container3" };
containerRepository.AddOrUpdate(container3);
unitOfWork.Commit();
Assert.That(container1.Id, Is.GreaterThan(0));
Assert.That(container2.Id, Is.GreaterThan(0));
Assert.That(container3.Id, Is.GreaterThan(0));
}
using (var containerRepository = CreateContainerRepository(unitOfWork, Constants.ObjectTypes.DocumentTypeContainerGuid))
{
var found1 = containerRepository.Get(container1.Id);
Assert.IsNotNull(found1);
var found2 = containerRepository.Get(container2.Id);
Assert.IsNotNull(found2);
var found3 = containerRepository.Get(container3.Id);
Assert.IsNotNull(found3);
var allContainers = containerRepository.GetAll();
Assert.AreEqual(3, allContainers.Count());
}
}
[Test]
public void Can_Delete_Container()
{
@@ -46,23 +46,6 @@ namespace Umbraco.Tests.Persistence.Repositories
return new EntityContainerRepository(unitOfWork, CacheHelper.CreateDisabledCacheHelper(), Mock.Of<ILogger>(), SqlSyntax, Constants.ObjectTypes.DataTypeContainerGuid);
}
[TestCase("UmbracoPreVal87-21,3,48", 3, true)]
[TestCase("UmbracoPreVal87-21,33,48", 3, false)]
[TestCase("UmbracoPreVal87-21,33,48", 33, true)]
[TestCase("UmbracoPreVal87-21,3,48", 33, false)]
[TestCase("UmbracoPreVal87-21,3,48", 21, true)]
[TestCase("UmbracoPreVal87-21,3,48", 48, true)]
[TestCase("UmbracoPreVal87-22,33,48", 2, false)]
[TestCase("UmbracoPreVal87-22,33,48", 22, true)]
[TestCase("UmbracoPreVal87-22,33,44", 4, false)]
[TestCase("UmbracoPreVal87-22,33,44", 44, true)]
[TestCase("UmbracoPreVal87-22,333,44", 33, false)]
[TestCase("UmbracoPreVal87-22,333,44", 333, true)]
public void Pre_Value_Cache_Key_Tests(string cacheKey, int preValueId, bool outcome)
{
Assert.AreEqual(outcome, Regex.IsMatch(cacheKey, DataTypeDefinitionRepository.GetCacheKeyRegex(preValueId)));
}
[Test]
public void Can_Move()
{
@@ -537,7 +520,7 @@ namespace Umbraco.Tests.Persistence.Repositories
}
var cached = cache.IsolatedRuntimeCache.GetCache<IDataTypeDefinition>().Result
.GetCacheItemsByKeySearch<PreValueCollection>(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-");
.GetCacheItemsByKeySearch<PreValueCollection>(CacheKeys.DataTypePreValuesCacheKey + "_" + dtd.Id);
Assert.IsNotNull(cached);
Assert.AreEqual(1, cached.Count());
@@ -581,7 +564,7 @@ namespace Umbraco.Tests.Persistence.Repositories
}
var cached = cache.IsolatedRuntimeCache.GetCache<IDataTypeDefinition>().Result
.GetCacheItemsByKeySearch<PreValueCollection>(CacheKeys.DataTypePreValuesCacheKey + dtd.Id + "-");
.GetCacheItemsByKeySearch<PreValueCollection>(CacheKeys.DataTypePreValuesCacheKey + "_" + dtd.Id);
Assert.IsNotNull(cached);
Assert.AreEqual(1, cached.Count());
@@ -0,0 +1,183 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Models.EntityBase;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence.Querying;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
namespace Umbraco.Tests.Persistence.Repositories
{
[DatabaseTestBehavior(DatabaseBehavior.NewDbFileAndSchemaPerTest)]
[TestFixture]
public class EntityRepositoryTest : BaseDatabaseFactoryTest
{
[SetUp]
public override void Initialize()
{
base.Initialize();
}
[TearDown]
public override void TearDown()
{
base.TearDown();
}
private ContentRepository CreateContentRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository)
{
TemplateRepository tr;
return CreateContentRepository(unitOfWork, out contentTypeRepository, out tr);
}
private ContentRepository CreateContentRepository(IDatabaseUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository)
{
templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of<IFileSystem>(), Mock.Of<IFileSystem>(), Mock.Of<ITemplatesSection>());
var tagRepository = new TagRepository(unitOfWork, CacheHelper, Logger, SqlSyntax);
contentTypeRepository = new ContentTypeRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, templateRepository);
var repository = new ContentRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository, templateRepository, tagRepository, Mock.Of<IContentSection>());
return repository;
}
[Test]
public void Deal_With_Corrupt_Duplicate_Newest_Published_Flags_Full_Model()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
IContent content1;
using (var repository = CreateContentRepository(unitOfWork, out contentTypeRepository))
{
var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage");
content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
contentTypeRepository.AddOrUpdate(hasPropertiesContentType);
repository.AddOrUpdate(content1);
unitOfWork.Commit();
}
var versionDtos = new List<ContentVersionDto>();
//Now manually corrupt the data
var versions = new[] {Guid.NewGuid(), Guid.NewGuid()};
for (var index = 0; index < versions.Length; index++)
{
var version = versions[index];
var versionDate = DateTime.Now.AddMinutes(index);
var versionDto = new ContentVersionDto
{
NodeId = content1.Id,
VersionDate = versionDate,
VersionId = version
};
this.DatabaseContext.Database.Insert(versionDto);
versionDtos.Add(versionDto);
this.DatabaseContext.Database.Insert(new DocumentDto
{
Newest = true,
NodeId = content1.Id,
Published = true,
Text = content1.Name,
VersionId = version,
WriterUserId = 0,
UpdateDate = versionDate,
TemplateId = content1.Template == null || content1.Template.Id <= 0 ? null : (int?)content1.Template.Id
});
}
// Assert
using (var repository = new EntityRepository(unitOfWork))
{
var content = repository.GetByQuery(new Query<IUmbracoEntity>().Where(c => c.Id == content1.Id), Constants.ObjectTypes.DocumentGuid).ToArray();
Assert.AreEqual(1, content.Length);
Assert.AreEqual(versionDtos.Max(x => x.Id), content[0].AdditionalData["VersionId"]);
content = repository.GetAll(Constants.ObjectTypes.DocumentGuid, content[0].Key).ToArray();
Assert.AreEqual(1, content.Length);
Assert.AreEqual(versionDtos.Max(x => x.Id), content[0].AdditionalData["VersionId"]);
content = repository.GetAll(Constants.ObjectTypes.DocumentGuid, content[0].Id).ToArray();
Assert.AreEqual(1, content.Length);
Assert.AreEqual(versionDtos.Max(x => x.Id), content[0].AdditionalData["VersionId"]);
var contentItem = repository.Get(content[0].Id, Constants.ObjectTypes.DocumentGuid);
Assert.AreEqual(versionDtos.Max(x => x.Id), contentItem.AdditionalData["VersionId"]);
contentItem = repository.GetByKey(content[0].Key, Constants.ObjectTypes.DocumentGuid);
Assert.AreEqual(versionDtos.Max(x => x.Id), contentItem.AdditionalData["VersionId"]);
}
}
/// <summary>
/// The Slim model will test the EntityRepository when it doesn't know the object type so it will
/// make the simplest (slim) query
/// </summary>
[Test]
public void Deal_With_Corrupt_Duplicate_Newest_Published_Flags_Slim_Model()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
IContent content1;
using (var repository = CreateContentRepository(unitOfWork, out contentTypeRepository))
{
var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage");
content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
contentTypeRepository.AddOrUpdate(hasPropertiesContentType);
repository.AddOrUpdate(content1);
unitOfWork.Commit();
}
//Now manually corrupt the data
var versions = new[] { Guid.NewGuid(), Guid.NewGuid() };
for (var index = 0; index < versions.Length; index++)
{
var version = versions[index];
var versionDate = DateTime.Now.AddMinutes(index);
var versionDto = new ContentVersionDto
{
NodeId = content1.Id,
VersionDate = versionDate,
VersionId = version
};
this.DatabaseContext.Database.Insert(versionDto);
this.DatabaseContext.Database.Insert(new DocumentDto
{
Newest = true,
NodeId = content1.Id,
Published = true,
Text = content1.Name,
VersionId = version,
WriterUserId = 0,
UpdateDate = versionDate,
TemplateId = content1.Template == null || content1.Template.Id <= 0 ? null : (int?)content1.Template.Id
});
}
// Assert
using (var repository = new EntityRepository(unitOfWork))
{
var content = repository.GetByQuery(new Query<IUmbracoEntity>().Where(c => c.Id == content1.Id)).ToArray();
Assert.AreEqual(1, content.Length);
var contentItem = repository.Get(content[0].Id);
Assert.IsNotNull(contentItem);
contentItem = repository.GetByKey(content[0].Key);
Assert.IsNotNull(contentItem);
}
}
}
}
@@ -4,6 +4,7 @@ using System.Linq;
using System.Xml.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
@@ -70,6 +71,44 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Rebuild_All_Xml_Structures_Ensure_Orphaned_Are_Removed()
{
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
MediaTypeRepository mediaTypeRepository;
using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository))
{
//delete all xml
unitOfWork.Database.Execute("DELETE FROM cmsContentXml");
var mediaType = mediaTypeRepository.Get(1032);
for (var i = 0; i < 100; i++)
{
var image = MockedMedia.CreateMediaImage(mediaType, -1);
repository.AddOrUpdate(image);
}
unitOfWork.Commit();
//Add some extra orphaned rows that shouldn't be there
var trashed = MockedMedia.CreateMediaImage(mediaType, -1);
trashed.ChangeTrashedState(true, Constants.System.RecycleBinMedia);
repository.AddOrUpdate(trashed);
unitOfWork.Commit();
//Force add it
unitOfWork.Database.Insert(new ContentXmlDto
{
NodeId = trashed.Id,
Xml = "<test></test>"
});
repository.RebuildXmlStructures(media => new XElement("test"), 10);
Assert.AreEqual(103, unitOfWork.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM cmsContentXml"));
}
}
[Test]
public void Rebuild_Some_Xml_Structures()
{
@@ -345,6 +384,61 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Can_Perform_GetByQuery_On_MediaRepository_With_ContentType_Id_Filter()
{
// Arrange
var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031);
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
MediaTypeRepository mediaTypeRepository;
using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository))
{
// Act
for (int i = 0; i < 10; i++)
{
var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1);
repository.AddOrUpdate(folder);
}
unitOfWork.Commit();
var types = new[] { 1031 };
var query = Query<IMedia>.Builder.Where(x => types.Contains(x.ContentTypeId));
var result = repository.GetByQuery(query);
// Assert
Assert.That(result.Count(), Is.GreaterThanOrEqualTo(11));
}
}
[Ignore("We could allow this to work but it requires an extra join on the query used which currently we don't absolutely need so leaving this out for now")]
[Test]
public void Can_Perform_GetByQuery_On_MediaRepository_With_ContentType_Alias_Filter()
{
// Arrange
var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031);
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
MediaTypeRepository mediaTypeRepository;
using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository))
{
// Act
for (int i = 0; i < 10; i++)
{
var folder = MockedMedia.CreateMediaFolder(folderMediaType, -1);
repository.AddOrUpdate(folder);
}
unitOfWork.Commit();
var types = new[] { "Folder" };
var query = Query<IMedia>.Builder.Where(x => types.Contains(x.ContentType.Alias));
var result = repository.GetByQuery(query);
// Assert
Assert.That(result.Count(), Is.GreaterThanOrEqualTo(11));
}
}
[Test]
public void Can_Perform_GetPagedResultsByQuery_ForFirstPage_On_MediaRepository()
{
@@ -470,7 +564,7 @@ namespace Umbraco.Tests.Persistence.Repositories
Assert.That(result.First().Name, Is.EqualTo("Test File"));
}
}
[Test]
public void Can_Perform_GetPagedResultsByQuery_WithFilterMatchingAll_On_MediaRepository()
{
@@ -154,36 +154,61 @@ namespace Umbraco.Tests.Persistence.Repositories
[Test]
public void Get_All()
{
var content = CreateTestData(3).ToArray();
var content = CreateTestData(30).ToArray();
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
using (var repo = new PublicAccessRepository(unitOfWork, CacheHelper, Logger, SqlSyntax))
{
var entry1 = new PublicAccessEntry(content[0], content[1], content[2], new[]
{
var allEntries = new List<PublicAccessEntry>();
for (int i = 0; i < 10; i++)
{
new PublicAccessRule
var rules = new List<PublicAccessRule>();
for (int j = 0; j < 50; j++)
{
RuleValue = "test",
RuleType = "RoleName"
},
});
repo.AddOrUpdate(entry1);
rules.Add(new PublicAccessRule
{
RuleValue = "test" + j,
RuleType = "RoleName" + j
});
}
var entry1 = new PublicAccessEntry(content[i], content[i+1], content[i+2], rules);
repo.AddOrUpdate(entry1);
unitOfWork.Commit();
allEntries.Add(entry1);
}
var entry2 = new PublicAccessEntry(content[1], content[0], content[2], new[]
//now remove a few rules from a few of the items and then add some more, this will put things 'out of order' which
//we need to verify our sort order is working for the relator
for (int i = 0; i < allEntries.Count; i++)
{
new PublicAccessRule
//all the even ones
if (i % 2 == 0)
{
RuleValue = "test",
RuleType = "RoleName"
},
});
repo.AddOrUpdate(entry2);
unitOfWork.Commit();
var rules = allEntries[i].Rules.ToArray();
for (int j = 0; j < rules.Length; j++)
{
//all the even ones
if (j % 2 == 0)
{
allEntries[i].RemoveRule(rules[j]);
}
}
allEntries[i].AddRule("newrule" + i, "newrule" + i);
repo.AddOrUpdate(allEntries[i]);
unitOfWork.Commit();
}
}
var found = repo.GetAll().ToArray();
Assert.AreEqual(2, found.Count());
Assert.AreEqual(10, found.Length);
foreach (var publicAccessEntry in found)
{
var matched = allEntries.First(x => x.Key == publicAccessEntry.Key);
Assert.AreEqual(matched.Rules.Count(), publicAccessEntry.Rules.Count());
}
}
}
@@ -1,6 +1,8 @@
using System;
using System.Linq;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.Store;
using Moq;
using NUnit.Framework;
@@ -31,12 +33,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ensure_Children_Are_Sorted()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile());
Assert.IsNotNull(result);
Assert.AreEqual(1, result.TotalItemCount);
@@ -60,12 +65,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ensure_Result_Has_All_Values()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var result = searcher.Search(searcher.CreateSearchCriteria().Id(1111).Compile());
Assert.IsNotNull(result);
Assert.AreEqual(1, result.TotalItemCount);
@@ -27,6 +27,7 @@ using UmbracoExamine;
using UmbracoExamine.DataServices;
using umbraco.BusinessLogic;
using System.Linq;
using Lucene.Net.Index;
namespace Umbraco.Tests.PublishedContent
{
@@ -106,11 +107,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ensure_Children_Sorted_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -135,11 +139,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Do_Not_Find_In_Recycle_Bin()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -175,11 +182,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Children_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -197,11 +207,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Descendants_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -219,11 +232,14 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void DescendantsOrSelf_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var ctx = GetUmbracoContext("/test", 1234);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
@@ -241,12 +257,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void Ancestors_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var ctx = GetUmbracoContext("/test", 1234);
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
//we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace
@@ -260,12 +279,15 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void AncestorsOrSelf_With_Examine()
{
using (var luceneDir = new RAMDirectory())
using (var luceneDir = new RandomIdRAMDirectory())
using (var writer = new IndexWriter(luceneDir, new StandardAnalyzer(Lucene.Net.Util.Version.LUCENE_29), IndexWriter.MaxFieldLength.LIMITED))
using (var indexer = IndexInitializer.GetUmbracoIndexer(writer))
using (var searcher = IndexInitializer.GetUmbracoSearcher(writer))
{
var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
//var indexer = IndexInitializer.GetUmbracoIndexer(luceneDir);
indexer.RebuildIndex();
var ctx = GetUmbracoContext("/test", 1234);
var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
//var searcher = IndexInitializer.GetUmbracoSearcher(luceneDir);
var cache = new ContextualPublishedMediaCache(new PublishedMediaCache(ctx.Application, searcher, indexer), ctx);
//we are using the media.xml media to test the examine results implementation, see the media.xml file in the ExamineHelpers namespace
@@ -36,11 +36,15 @@ namespace Umbraco.Tests.Services
public override void Initialize()
{
base.Initialize();
VersionableRepositoryBase<int, IContent>.ThrowOnWarning = true;
}
[TearDown]
public override void TearDown()
{
VersionableRepositoryBase<int, IContent>.ThrowOnWarning = false;
base.TearDown();
}
@@ -668,6 +672,17 @@ namespace Umbraco.Tests.Services
Assert.Throws<Exception>(() => contentService.CreateContent("Test", -1, "umbAliasDoesntExist"));
}
[Test]
public void Cannot_Save_Content_With_Empty_Name()
{
// Arrange
var contentService = ServiceContext.ContentService;
var content = new Content(string.Empty, -1, ServiceContext.ContentTypeService.GetContentType("umbTextpage"));
// Act & Assert
Assert.Throws<ArgumentException>(() => contentService.Save(content));
}
[Test]
public void Can_Get_Content_By_Id()
{
@@ -751,16 +766,26 @@ namespace Umbraco.Tests.Services
var parent = ServiceContext.ContentService.GetById(NodeDto.NodeIdSeed + 1);
ServiceContext.ContentService.Publish(parent);//Publishing root, so Text Page 2 can be updated.
var subpage2 = contentService.GetById(NodeDto.NodeIdSeed + 3);
subpage2.Name = "Text Page 2 Updated";
subpage2.SetValue("author", "Jane Doe");
contentService.SaveAndPublishWithStatus(subpage2, 0);//NOTE New versions are only added between publish-state-changed, so publishing to ensure addition version.
subpage2.Name = "Text Page 2 Updated again";
subpage2.SetValue("author", "Bob Hope");
contentService.SaveAndPublishWithStatus(subpage2, 0);//NOTE New versions are only added between publish-state-changed, so publishing to ensure addition version.
// Act
var versions = contentService.GetVersions(NodeDto.NodeIdSeed + 3).ToList();
// Assert
Assert.That(versions.Any(), Is.True);
Assert.That(versions.Count(), Is.GreaterThanOrEqualTo(2));
//ensure each version contains the correct property values
Assert.AreEqual("John Doe", versions[2].GetValue("author"));
Assert.AreEqual("Jane Doe", versions[1].GetValue("author"));
Assert.AreEqual("Bob Hope", versions[0].GetValue("author"));
}
[Test]
@@ -1328,6 +1353,69 @@ namespace Umbraco.Tests.Services
Assert.That(contents.Any(), Is.False);
}
[Test]
public void Can_Empty_RecycleBin_With_Content_That_Has_All_Related_Data()
{
// Arrange
//need to:
// * add relations
// * add permissions
// * add notifications
// * public access
// * tags
// * domain
// * published & preview data
// * multiple versions
var contentType = MockedContentTypes.CreateAllTypesContentType("test", "test");
ServiceContext.ContentTypeService.Save(contentType, 0);
object obj =
new
{
tags = "Hello,World"
};
var content1 = MockedContent.CreateBasicContent(contentType);
content1.PropertyValues(obj);
content1.ResetDirtyProperties(false);
ServiceContext.ContentService.Save(content1, 0);
Assert.IsTrue(ServiceContext.ContentService.PublishWithStatus(content1, 0).Success);
var content2 = MockedContent.CreateBasicContent(contentType);
content2.PropertyValues(obj);
content2.ResetDirtyProperties(false);
ServiceContext.ContentService.Save(content2, 0);
Assert.IsTrue(ServiceContext.ContentService.PublishWithStatus(content2, 0).Success);
ServiceContext.RelationService.Save(new RelationType(Constants.ObjectTypes.DocumentGuid, Constants.ObjectTypes.DocumentGuid, "test"));
Assert.IsNotNull(ServiceContext.RelationService.Relate(content1, content2, "test"));
ServiceContext.PublicAccessService.Save(new PublicAccessEntry(content1, content2, content2, new List<PublicAccessRule>
{
new PublicAccessRule
{
RuleType = "test",
RuleValue = "test"
}
}));
Assert.IsTrue(ServiceContext.PublicAccessService.AddRule(content1, "test2", "test2").Success);
Assert.IsNotNull(ServiceContext.NotificationService.CreateNotification(ServiceContext.UserService.GetUserById(0), content1, "test"));
ServiceContext.ContentService.AssignContentPermission(content1, 'A', new[] {0});
Assert.IsTrue(ServiceContext.DomainService.Save(new UmbracoDomain("www.test.com", "en-AU")
{
RootContentId = content1.Id
}).Success);
// Act
ServiceContext.ContentService.EmptyRecycleBin();
var contents = ServiceContext.ContentService.GetContentInRecycleBin();
// Assert
Assert.That(contents.Any(), Is.False);
}
[Test]
public void Can_Move_Content()
{
@@ -1,4 +1,3 @@
using System.Runtime.Remoting;
using NUnit.Framework;
using System;
using System.Collections.Generic;
@@ -8,7 +7,6 @@ using Umbraco.Core.Exceptions;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Services;
using Umbraco.Tests.CodeFirst.TestModels.Composition;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Entities;
@@ -304,6 +302,42 @@ namespace Umbraco.Tests.Services
Assert.IsNull(deletedContentType);
}
[Test]
public void Can_Create_Container()
{
// Arrange
var cts = ServiceContext.ContentTypeService;
// Act
var container = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid);
container.Name = "container1";
cts.SaveContentTypeContainer(container);
// Assert
var createdContainer = cts.GetContentTypeContainer(container.Id);
Assert.IsNotNull(createdContainer);
}
[Test]
public void Can_Get_All_Containers()
{
// Arrange
var cts = ServiceContext.ContentTypeService;
// Act
var container1 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid);
container1.Name = "container1";
cts.SaveContentTypeContainer(container1);
var container2 = new EntityContainer(Constants.ObjectTypes.DocumentTypeGuid);
container2.Name = "container2";
cts.SaveContentTypeContainer(container2);
// Assert
var containers = cts.GetContentTypeContainers(new int[0]);
Assert.AreEqual(2, containers.Count());
}
[Test]
public void Deleting_ContentType_Sends_Correct_Number_Of_DeletedEntities_In_Events()
{
@@ -717,6 +751,16 @@ namespace Umbraco.Tests.Services
Assert.DoesNotThrow(() => service.GetContentType("advancedPage"));
}
[Test]
public void Cannot_Save_ContentType_With_Empty_Name()
{
// Arrange
var contentType = MockedContentTypes.CreateSimpleContentType("contentType", string.Empty);
// Act & Assert
Assert.Throws<ArgumentException>(() => ServiceContext.ContentTypeService.Save(contentType));
}
[Test]
public void Cannot_Rename_PropertyType_Alias_On_Composition_Which_Would_Cause_Conflict_In_Other_Composition()
{
@@ -1374,6 +1418,38 @@ namespace Umbraco.Tests.Services
Assert.That(descriptionPropertyTypeReloaded.PropertyGroupId.IsValueCreated, Is.False);
}
[Test]
public void Empty_Description_Is_Always_Null_After_Saving_Content_Type()
{
var service = ServiceContext.ContentTypeService;
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Description = null;
service.Save(contentType);
var contentType2 = MockedContentTypes.CreateBasicContentType("basePage2", "Base Page 2");
contentType2.Description = string.Empty;
service.Save(contentType2);
Assert.IsNull(contentType.Description);
Assert.IsNull(contentType2.Description);
}
[Test]
public void Empty_Description_Is_Always_Null_After_Saving_Media_Type()
{
var service = ServiceContext.ContentTypeService;
var mediaType = MockedContentTypes.CreateSimpleMediaType("mediaType", "Media Type");
mediaType.Description = null;
service.Save(mediaType);
var mediaType2 = MockedContentTypes.CreateSimpleMediaType("mediaType2", "Media Type 2");
mediaType2.Description = string.Empty;
service.Save(mediaType2);
Assert.IsNull(mediaType.Description);
Assert.IsNull(mediaType2.Description);
}
private ContentType CreateComponent()
{
var component = new ContentType(-1)
@@ -221,5 +221,18 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("preVal1", preVals.PreValuesAsArray.First().Value);
Assert.AreEqual("preVal2", preVals.PreValuesAsArray.Last().Value);
}
[Test]
public void Cannot_Save_DataType_With_Empty_Name()
{
// Arrange
var dataTypeService = ServiceContext.DataTypeService;
// Act
var dataTypeDefinition = new DataTypeDefinition(-1, "Test.TestEditor") { Name = string.Empty, DatabaseType = DataTypeDatabaseType.Ntext };
// Act & Assert
Assert.Throws<ArgumentException>(() => dataTypeService.Save(dataTypeDefinition));
}
}
}
@@ -1,4 +1,5 @@
using System.Linq;
using System;
using System.Linq;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
@@ -217,6 +218,17 @@ namespace Umbraco.Tests.Services
}
[Test]
public void Cannot_Save_Macro_With_Empty_Name()
{
// Arrange
var macroService = ServiceContext.MacroService;
var macro = new Macro("test", string.Empty, scriptPath: "~/Views/MacroPartials/Test.cshtml", cacheDuration: 1234);
// Act & Assert
Assert.Throws<ArgumentException>(() => macroService.Save(macro));
}
//[Test]
//public void Can_Get_Many_By_Alias()
//{
@@ -7,6 +7,7 @@ using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Rdbms;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.Persistence.UnitOfWork;
using Umbraco.Tests.TestHelpers;
@@ -30,6 +31,33 @@ namespace Umbraco.Tests.Services
base.TearDown();
}
[Test]
public void Get_Paged_Children_With_Media_Type_Filter()
{
var mediaService = ServiceContext.MediaService;
var mediaType1 = MockedContentTypes.CreateImageMediaType("Image2");
ServiceContext.ContentTypeService.Save(mediaType1);
var mediaType2 = MockedContentTypes.CreateImageMediaType("Image3");
ServiceContext.ContentTypeService.Save(mediaType2);
for (int i = 0; i < 10; i++)
{
var m1 = MockedMedia.CreateMediaImage(mediaType1, -1);
mediaService.Save(m1);
var m2 = MockedMedia.CreateMediaImage(mediaType2, -1);
mediaService.Save(m2);
}
long total;
var result = ServiceContext.MediaService.GetPagedChildren(-1, 0, 11, out total, "SortOrder", Direction.Ascending, true, null, new[] {mediaType1.Id, mediaType2.Id});
Assert.AreEqual(11, result.Count());
Assert.AreEqual(20, total);
result = ServiceContext.MediaService.GetPagedChildren(-1, 1, 11, out total, "SortOrder", Direction.Ascending, true, null, new[] { mediaType1.Id, mediaType2.Id });
Assert.AreEqual(9, result.Count());
Assert.AreEqual(20, total);
}
[Test]
public void Can_Move_Media()
{
@@ -81,6 +109,19 @@ namespace Umbraco.Tests.Services
Assert.That(mediaChild.Trashed, Is.False);
}
[Test]
public void Cannot_Save_Media_With_Empty_Name()
{
// Arrange
var mediaService = ServiceContext.MediaService;
var mediaType = MockedContentTypes.CreateVideoMediaType();
ServiceContext.ContentTypeService.Save(mediaType);
var media = mediaService.CreateMedia(string.Empty, -1, "video");
// Act & Assert
Assert.Throws<ArgumentException>(() => mediaService.Save(media));
}
[Test]
public void Ensure_Content_Xml_Created()
{
@@ -185,6 +185,18 @@ namespace Umbraco.Tests.Services
Assert.AreEqual(2, membersInRole.Count());
}
[Test]
public void Cannot_Save_Member_With_Empty_Name()
{
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
ServiceContext.MemberTypeService.Save(memberType);
IMember member = MockedMember.CreateSimpleMember(memberType, string.Empty, "test@test.com", "pass", "test");
// Act & Assert
Assert.Throws<ArgumentException>(() => ServiceContext.MemberService.Save(member));
}
[TestCase("MyTestRole1", "test1", StringPropertyMatchType.StartsWith, 1)]
[TestCase("MyTestRole1", "test", StringPropertyMatchType.StartsWith, 3)]
[TestCase("MyTestRole1", "test1", StringPropertyMatchType.Exact, 1)]

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