diff --git a/.gitignore b/.gitignore index f644044430..bd7a0ffa9d 100644 --- a/.gitignore +++ b/.gitignore @@ -141,5 +141,5 @@ build/docs.zip build/ui-docs.zip build/csharp-docs.zip build/msbuild.log - +.vs/ src/packages/ \ No newline at end of file diff --git a/src/Umbraco.Core/CoreBootManager.cs b/src/Umbraco.Core/CoreBootManager.cs index 4da740f458..4ef08bd02f 100644 --- a/src/Umbraco.Core/CoreBootManager.cs +++ b/src/Umbraco.Core/CoreBootManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Configuration; using System.IO; using System.Linq; using System.Threading; @@ -30,12 +31,13 @@ using Umbraco.Core.Manifest; using Umbraco.Core.Services; using Umbraco.Core.Sync; using Umbraco.Core.Strings; +using IntegerValidator = Umbraco.Core.PropertyEditors.IntegerValidator; using MigrationsVersionFourNineZero = Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionFourNineZero; namespace Umbraco.Core { /// - /// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application + /// A bootstrapper for the Umbraco application which initializes all objects for the Core of the application /// /// /// This does not provide any startup functionality relating to web objects @@ -191,14 +193,14 @@ namespace Umbraco.Core protected virtual CacheHelper CreateApplicationCache() { var cacheHelper = new CacheHelper( - //we need to have the dep clone runtime cache provider to ensure + //we need to have the dep clone runtime cache provider to ensure //all entities are cached properly (cloned in and cloned out) new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()), new StaticCacheProvider(), //we have no request based cache when not running in web-based context new NullCacheProvider(), new IsolatedRuntimeCache(type => - //we need to have the dep clone runtime cache provider to ensure + //we need to have the dep clone runtime cache provider to ensure //all entities are cached properly (cloned in and cloned out) new DeepCloneRuntimeCacheProvider(new ObjectCacheRuntimeCacheProvider()))); @@ -251,18 +253,18 @@ namespace Umbraco.Core } /// - /// Special method to initialize the ApplicationEventsResolver and any modifications required for it such + /// Special method to initialize the ApplicationEventsResolver and any modifications required for it such /// as adding custom types to the resolver. /// protected virtual void InitializeApplicationEventsResolver() { //find and initialize the application startup handlers, we need to initialize this resolver here because - //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to + //it is a special resolver where they need to be instantiated first before any other resolvers in order to bind to //events and to call their events during bootup. //ApplicationStartupHandler.RegisterHandlers(); //... and set the special flag to let us resolve before frozen resolution ApplicationEventsResolver.Current = new ApplicationEventsResolver( - ServiceProvider, + ServiceProvider, ProfilingLogger.Logger, PluginManager.ResolveApplicationStartupHandlers()) { @@ -282,7 +284,7 @@ namespace Umbraco.Core } /// - /// Fires after initialization and calls the callback to allow for customizations to occur & + /// Fires after initialization and calls the callback to allow for customizations to occur & /// Ensure that the OnApplicationStarting methods of the IApplicationEvents are called /// /// @@ -334,7 +336,7 @@ namespace Umbraco.Core { if (_isComplete) throw new InvalidOperationException("The boot manager has already been completed"); - + FreezeResolution(); //Here we need to make sure the db can be connected to @@ -366,7 +368,7 @@ namespace Umbraco.Core ProfilingLogger.Logger.Error("An error occurred running OnApplicationStarted for handler " + x.GetType(), ex); throw; } - }); + }); } //Now, startup all of our legacy startup handler @@ -455,6 +457,10 @@ namespace Umbraco.Core { ServerRegistrarResolver.Current = new ServerRegistrarResolver(new ConfigServerRegistrar()); } + else if ("true".InvariantEquals(ConfigurationManager.AppSettings["umbracoDisableElectionForSingleServer"])) + { + ServerRegistrarResolver.Current = new ServerRegistrarResolver(new SingleServerRegistrar()); + } else { ServerRegistrarResolver.Current = new ServerRegistrarResolver( @@ -462,7 +468,6 @@ namespace Umbraco.Core new Lazy(() => ApplicationContext.Services.ServerRegistrationService), new DatabaseServerRegistrarOptions())); } - //by default we'll use the database server messenger with default options (no callbacks), // this will be overridden in the web startup @@ -473,7 +478,7 @@ namespace Umbraco.Core ServiceProvider, ProfilingLogger.Logger, () => PluginManager.ResolveAssignedMapperTypes()); - + //RepositoryResolver.Current = new RepositoryResolver( // new RepositoryFactory(ApplicationCache)); diff --git a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs index c4cd28f6e0..e9c1685bb3 100644 --- a/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs +++ b/src/Umbraco.Core/Models/Rdbms/PropertyDataDto.cs @@ -11,7 +11,6 @@ namespace Umbraco.Core.Models.Rdbms { [Column("id")] [PrimaryKeyColumn] - [Index(IndexTypes.NonClustered, Name = "IX_cmsPropertyData")] public int Id { get; set; } [Column("contentNodeId")] diff --git a/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs new file mode 100644 index 0000000000..b50c8e5f94 --- /dev/null +++ b/src/Umbraco.Core/Persistence/Migrations/Upgrades/TargetVersionSevenSixZero/RemovePropertyDataIdIndex.cs @@ -0,0 +1,38 @@ +using System.Linq; +using Umbraco.Core.Configuration; +using Umbraco.Core.Logging; +using Umbraco.Core.Persistence.SqlSyntax; + +namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenSixZero +{ + /// + /// See: http://issues.umbraco.org/issue/U4-9188 + /// + [Migration("7.6.0", 0, GlobalSettings.UmbracoMigrationName)] + public class UpdateUniqueIndexOnCmsPropertyData : MigrationBase + { + public UpdateUniqueIndexOnCmsPropertyData(ISqlSyntaxProvider sqlSyntax, ILogger logger) + : base(sqlSyntax, logger) + { + } + + public override void Up() + { + //tuple = tablename, indexname, columnname, unique + var indexes = SqlSyntax.GetDefinedIndexes(Context.Database).ToArray(); + var found = indexes.FirstOrDefault( + x => x.Item1.InvariantEquals("cmsPropertyData") + && x.Item2.InvariantEquals("IX_cmsPropertyData")); + + if (found != null) + { + //drop the index + Delete.Index("IX_cmsPropertyData").OnTable("cmsPropertyData"); + } + } + + public override void Down() + { + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/PetaPoco.cs b/src/Umbraco.Core/Persistence/PetaPoco.cs index 63278889a2..88ff456ff6 100644 --- a/src/Umbraco.Core/Persistence/PetaPoco.cs +++ b/src/Umbraco.Core/Persistence/PetaPoco.cs @@ -739,6 +739,11 @@ namespace Umbraco.Core.Persistence /// internal virtual void BuildSqlDbSpecificPagingQuery(DBType databaseType, long skip, long take, string sql, string sqlSelectRemoved, string sqlOrderBy, ref object[] args, out string sqlPage) { + // this is overriden in UmbracoDatabase, and if running SqlServer >=2012, the database type + // is switched from SqlServer to SqlServerCE in order to use the better paging syntax that + // SqlCE supports, and SqlServer >=2012 too. + // so the first case is actually for SqlServer <2012, and second case is CE *and* SqlServer >=2012 + if (databaseType == DBType.SqlServer || databaseType == DBType.Oracle) { sqlSelectRemoved = rxOrderBy.Replace(sqlSelectRemoved, ""); @@ -746,8 +751,16 @@ namespace Umbraco.Core.Persistence { sqlSelectRemoved = "peta_inner.* FROM (SELECT " + sqlSelectRemoved + ") peta_inner"; } - sqlPage = string.Format("SELECT * FROM (SELECT ROW_NUMBER() OVER ({0}) peta_rn, {1}) peta_paged WHERE peta_rn>@{2} AND peta_rn<=@{3}", - sqlOrderBy == null ? "ORDER BY (SELECT NULL)" : sqlOrderBy, sqlSelectRemoved, args.Length, args.Length + 1); + + // split to ensure that peta_rn is the last field to be selected, else Page would fail + // the resulting sql is not perfect, NPoco has a much nicer way to do it, but it would require + // importing large parts of NPoco + var pos = sqlSelectRemoved.IndexOf("FROM"); + var sqlColumns = sqlSelectRemoved.Substring(0, pos); + var sqlFrom = sqlSelectRemoved.Substring(pos); + + sqlPage = string.Format("SELECT * FROM (SELECT {0}, ROW_NUMBER() OVER ({1}) peta_rn {2}) peta_paged WHERE peta_rn>@{3} AND peta_rn<=@{4}", + sqlColumns, sqlOrderBy ?? "ORDER BY (SELECT NULL)", sqlFrom, args.Length, args.Length + 1); args = args.Concat(new object[] { skip, skip + take }).ToArray(); } else if (databaseType == DBType.SqlServerCE) @@ -774,7 +787,7 @@ namespace Umbraco.Core.Persistence throw new Exception("Unable to parse SQL statement for paged query"); if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*")) throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id"); - + BuildSqlDbSpecificPagingQuery(_dbType, skip, take, sql, sqlSelectRemoved, sqlOrderBy, ref args, out sqlPage); } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index c4b1884f17..9f9180c98f 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -1,22 +1,14 @@ using System; using System.Collections.Generic; -using System.Data; using System.Globalization; using System.Linq; -using System.Linq.Expressions; -using System.Net.Http.Headers; -using System.Text; using System.Xml; using System.Xml.Linq; -using Umbraco.Core.Configuration; -using Umbraco.Core.Dynamics; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Membership; using Umbraco.Core.Models.Rdbms; - using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; @@ -81,7 +73,7 @@ namespace Umbraco.Core.Persistence.Repositories var sql = GetBaseQuery(false); if (ids.Any()) { - sql.Where("umbracoNode.id in (@ids)", new { ids = ids }); + sql.Where("umbracoNode.id in (@ids)", new { ids }); } //we only want the newest ones with this method @@ -224,6 +216,14 @@ namespace Umbraco.Core.Persistence.Repositories } } + public override IEnumerable GetAllVersions(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + return ProcessQuery(sql, true); + } + public override IContent GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); @@ -633,29 +633,9 @@ namespace Umbraco.Core.Persistence.Repositories .OrderBy(x => x.Level, SqlSyntax) .OrderBy(x => x.SortOrder, SqlSyntax); - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - foreach (var dto in dtos) - { - //Check in the cache first. If it exists there AND it is published - // then we can use that entity. Otherwise if it is not published (which can be the case - // because we only store the 'latest' entries in the cache which might not be the published - // version) - var fromCache = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); - //var fromCache = TryGetFromCache(dto.NodeId); - if (fromCache != null && fromCache.Published) - { - yield return fromCache; - } - else - { - yield return CreateContentFromDto(dto, dto.VersionId, sql); - } - } + return ProcessQuery(sql, true); } - /// /// This builds the Xml document used for the XML cache /// @@ -829,7 +809,7 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsDocument", "nodeId"), - ProcessQuery, orderBy, orderDirection, orderBySystemField, + sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -860,83 +840,79 @@ order by umbracoNode.level, umbracoNode.parentID, umbracoNode.sortOrder"; return base.GetDatabaseFieldNameForOrderBy(orderBy); } - private IEnumerable ProcessQuery(Sql sql) + private IEnumerable ProcessQuery(Sql sql, bool withCache = false) { - //NOTE: This doesn't allow properties to be part of the query + // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sql); + if (dtos.Count == 0) return Enumerable.Empty(); - //nothing found - if (dtos.Any() == false) return Enumerable.Empty(); + var content = new IContent[dtos.Count]; + var defs = new List(); + var templateIds = new List(); - //content types - //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var contentTypes = _contentTypeRepository.GetAll(dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray()) - .ToArray(); - - - var ids = dtos - .Where(dto => dto.TemplateId.HasValue && dto.TemplateId.Value > 0) - .Select(x => x.TemplateId.Value).ToArray(); - - //NOTE: This should be ok for an SQL 'IN' statement, there shouldn't be an insane amount of content types - var templates = ids.Length == 0 ? Enumerable.Empty() : _templateRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.VersionId, - d.dto.ContentVersionDto.VersionDate, - d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - d.contentType)); - - var propertyData = GetPropertyCollection(sql, docDefs); - - return dtosWithContentTypes.Select(d => CreateContentFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), - templates.FirstOrDefault(tem => tem.Id == (d.dto.TemplateId.HasValue ? d.dto.TemplateId.Value : -1)), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a content object from a DocumentDto, which is used by Get and GetByVersion. - /// - /// - /// - /// - /// - /// - private IContent CreateContentFromDto(DocumentDto dto, - IContentType contentType, - ITemplate template, - Models.PropertyCollection propCollection) - { - var factory = new ContentFactory(contentType, NodeObjectTypeId, dto.NodeId); - var content = factory.BuildEntity(dto); - - //Check if template id is set on DocumentDto, and get ITemplate if it is. - if (dto.TemplateId.HasValue && dto.TemplateId.Value > 0) + for (var i = 0; i < dtos.Count; i++) { - content.Template = template ?? _templateRepository.Get(dto.TemplateId.Value); - } - else - { - //ensure there isn't one set. - content.Template = null; + var dto = dtos[i]; + + // if the cache contains the published version, use it + if (withCache) + { + var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + if (cached != null && cached.Published) + { + content[i] = cached; + 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); + + // need properties + defs.Add(new DocumentDefinition( + dto.NodeId, + dto.VersionId, + dto.ContentVersionDto.VersionDate, + dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + contentType + )); } - content.Properties = propCollection; + // load all required templates in 1 query + var templates = _templateRepository.GetAll(templateIds.ToArray()) + .ToDictionary(x => x.Id, x => x); + + // load all properties for all documents from database in 1 query + var propertyData = GetPropertyCollection(sql, defs); + + // assign + var dtoIndex = 0; + foreach (var def in defs) + { + // move to corresponding item (which has to exist) + while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++; + + // 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 + cc.Template = template; + cc.Properties = propertyData[cc.Id]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity) cc).ResetDirtyProperties(false); + } - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)content).ResetDirtyProperties(false); return content; } diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 38e5e46cde..092b7df025 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -4,21 +4,17 @@ using System.Globalization; using System.Linq; using System.Text; using System.Xml.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.Dynamics; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models.Rdbms; - +using Umbraco.Core.Cache; using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Persistence.SqlSyntax; using Umbraco.Core.Persistence.UnitOfWork; -using Umbraco.Core.Services; namespace Umbraco.Core.Persistence.Repositories { @@ -137,6 +133,74 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of VersionableRepositoryBase + public override IEnumerable GetAllVersions(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + return ProcessQuery(sql, true); + } + + private IEnumerable ProcessQuery(Sql sql, bool withCache = false) + { + // fetch returns a list so it's ok to iterate it in this method + var dtos = Database.Fetch(sql); + var content = new IMedia[dtos.Count]; + var defs = new List(); + + for (var i = 0; i < dtos.Count; i++) + { + var dto = dtos[i]; + + // if the cache contains the item, use it + if (withCache) + { + var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + if (cached != null) + { + content[i] = cached; + 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 + )); + } + + // load all properties for all documents from database in 1 query + var propertyData = GetPropertyCollection(sql, defs); + + // assign + var dtoIndex = 0; + foreach (var def in defs) + { + // move to corresponding item (which has to exist) + while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++; + + // complete the item + var cc = content[dtoIndex]; + cc.Properties = propertyData[cc.Id]; + + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity) cc).ResetDirtyProperties(false); + } + + return content; + } + public override IMedia GetByVersion(Guid versionId) { var sql = GetBaseQuery(false); @@ -431,7 +495,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsContentVersion", "contentId"), - ProcessQuery, orderBy, orderDirection, orderBySystemField, + sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -460,62 +524,6 @@ namespace Umbraco.Core.Persistence.Repositories return pagedResult.Items.Select(dto => XElement.Parse(dto.Xml)); } - private IEnumerable ProcessQuery(Sql sql) - { - //NOTE: This doesn't allow properties to be part of the query - var dtos = Database.Fetch(sql); - - var ids = dtos.Select(x => x.ContentDto.ContentTypeId).ToArray(); - - //content types - var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _mediaTypeRepository.GetAll(ids).ToArray(); - - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); - - //Go get the property data for each document - var docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.VersionId, - d.dto.VersionDate, - d.dto.ContentDto.NodeDto.CreateDate, - d.contentType)) - .ToArray(); - - var propertyData = GetPropertyCollection(sql, docDefs); - - return dtosWithContentTypes.Select(d => CreateMediaFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); - } - - /// - /// Private method to create a media object from a ContentDto - /// - /// - /// - /// - /// - private IMedia CreateMediaFromDto(ContentVersionDto dto, - IMediaType contentType, - PropertyCollection propCollection) - { - var factory = new MediaFactory(contentType, NodeObjectTypeId, dto.NodeId); - var media = factory.BuildEntity(dto); - - media.Properties = propCollection; - - //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; - } - /// /// Private method to create a media object from a ContentDto /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs index 66f803f9cd..dcab898685 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MemberRepository.cs @@ -2,24 +2,19 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Linq.Expressions; using System.Text; using System.Xml.Linq; -using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; -using Umbraco.Core.IO; using Umbraco.Core.Logging; using Umbraco.Core.Models.EntityBase; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; - +using Umbraco.Core.Cache; using Umbraco.Core.Persistence.DatabaseModelDefinitions; 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.Dynamics; namespace Umbraco.Core.Persistence.Repositories { @@ -380,6 +375,14 @@ namespace Umbraco.Core.Persistence.Repositories #region Overrides of VersionableRepositoryBase + public override IEnumerable GetAllVersions(int id) + { + var sql = GetBaseQuery(false) + .Where(GetBaseWhereClause(), new { Id = id }) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + return ProcessQuery(sql, true); + } + public void RebuildXmlStructures(Func serializer, int groupSize = 200, IEnumerable contentTypeIds = null) { // the previous way of doing this was to run it all in one big transaction, @@ -616,7 +619,7 @@ namespace Umbraco.Core.Persistence.Repositories return GetPagedResultsByQuery(query, pageIndex, pageSize, out totalRecords, new Tuple("cmsMember", "nodeId"), - ProcessQuery, orderBy, orderDirection, orderBySystemField, + sql => ProcessQuery(sql), orderBy, orderDirection, orderBySystemField, filterCallback); } @@ -656,59 +659,65 @@ namespace Umbraco.Core.Persistence.Repositories return base.GetEntityPropertyNameForOrderBy(orderBy); } - private IEnumerable ProcessQuery(Sql sql) + private IEnumerable ProcessQuery(Sql sql, bool withCache = false) { - //NOTE: This doesn't allow properties to be part of the query + // fetch returns a list so it's ok to iterate it in this method var dtos = Database.Fetch(sql); - var ids = dtos.Select(x => x.ContentVersionDto.ContentDto.ContentTypeId).ToArray(); + var content = new IMember[dtos.Count]; + var defs = new List(); - //content types - var contentTypes = ids.Length == 0 ? Enumerable.Empty() : _memberTypeRepository.GetAll(ids).ToArray(); + for (var i = 0; i < dtos.Count; i++) + { + var dto = dtos[i]; - var dtosWithContentTypes = dtos - //This select into and null check are required because we don't have a foreign damn key on the contentType column - // http://issues.umbraco.org/issue/U4-5503 - .Select(x => new { dto = x, contentType = contentTypes.FirstOrDefault(ct => ct.Id == x.ContentVersionDto.ContentDto.ContentTypeId) }) - .Where(x => x.contentType != null) - .ToArray(); + // if the cache contains the item, use it + if (withCache) + { + var cached = RuntimeCache.GetCacheItem(GetCacheIdKey(dto.NodeId)); + if (cached != null) + { + content[i] = cached; + continue; + } + } - //Go get the property data for each document - IEnumerable docDefs = dtosWithContentTypes.Select(d => new DocumentDefinition( - d.dto.NodeId, - d.dto.ContentVersionDto.VersionId, - d.dto.ContentVersionDto.VersionDate, - d.dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, - d.contentType)); + // 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); - var propertyData = GetPropertyCollection(sql, docDefs); + // need properties + defs.Add(new DocumentDefinition( + dto.NodeId, + dto.ContentVersionDto.VersionId, + dto.ContentVersionDto.VersionDate, + dto.ContentVersionDto.ContentDto.NodeDto.CreateDate, + contentType + )); + } - return dtosWithContentTypes.Select(d => CreateMemberFromDto( - d.dto, - contentTypes.First(ct => ct.Id == d.dto.ContentVersionDto.ContentDto.ContentTypeId), - propertyData[d.dto.NodeId])); - } + // load all properties for all documents from database in 1 query + var propertyData = GetPropertyCollection(sql, defs); - /// - /// Private method to create a member object from a MemberDto - /// - /// - /// - /// - /// - private IMember CreateMemberFromDto(MemberDto dto, - IMemberType contentType, - PropertyCollection propCollection) - { - var factory = new MemberFactory(contentType, NodeObjectTypeId, dto.ContentVersionDto.NodeId); - var member = factory.BuildEntity(dto); + // assign + var dtoIndex = 0; + foreach (var def in defs) + { + // move to corresponding item (which has to exist) + while (dtos[dtoIndex].NodeId != def.Id) dtoIndex++; - member.Properties = propCollection; + // complete the item + var cc = content[dtoIndex]; + cc.Properties = propertyData[cc.Id]; - //on initial construction we don't want to have dirty properties tracked - // http://issues.umbraco.org/issue/U4-1946 - ((Entity)member).ResetDirtyProperties(false); - return member; + //on initial construction we don't want to have dirty properties tracked + // http://issues.umbraco.org/issue/U4-1946 + ((Entity)cc).ResetDirtyProperties(false); + } + + return content; } /// diff --git a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs index 4c35c90e1b..6cc97bdfb3 100644 --- a/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs +++ b/src/Umbraco.Core/Persistence/SqlSyntax/SqlServerSyntaxProvider.cs @@ -6,16 +6,11 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Core.Persistence.SqlSyntax { /// - /// Represents an SqlSyntaxProvider for Sql Server + /// Represents an SqlSyntaxProvider for Sql Server. /// - [SqlSyntaxProviderAttribute(Constants.DatabaseProviders.SqlServer)] + [SqlSyntaxProvider(Constants.DatabaseProviders.SqlServer)] public class SqlServerSyntaxProvider : MicrosoftSqlSyntaxProviderBase { - public SqlServerSyntaxProvider() - { - - } - /// /// Gets/sets the version of the current SQL server instance /// @@ -31,7 +26,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax switch (firstPart) { case "13": - _versionName = SqlServerVersionName.V2014; + _versionName = SqlServerVersionName.V2016; break; case "12": _versionName = SqlServerVersionName.V2014; @@ -75,7 +70,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax { var items = db.Fetch("SELECT TableName = t.Name,ColumnName = c.Name,dc.Name,dc.[Definition] FROM sys.tables t INNER JOIN sys.default_constraints dc ON t.object_id = dc.parent_object_id INNER JOIN sys.columns c ON dc.parent_object_id = c.object_id AND c.column_id = dc.parent_column_id"); return items.Select(x => new Tuple(x.TableName, x.ColumnName, x.Name, x.Definition)); - } + } public override IEnumerable GetTablesInSchema(Database db) { @@ -120,9 +115,9 @@ from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id inner join sys.all_columns as AC on IC.[object_id] = AC.[object_id] and IC.[column_id] = AC.[column_id] WHERE I.name NOT LIKE 'PK_%' order by T.name, I.name"); - return items.Select(item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, + return items.Select(item => new Tuple(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME, item.UNIQUE == 1)).ToList(); - + } public override bool DoesTableExist(Database db, string tableName) @@ -164,7 +159,7 @@ order by T.name, I.name"); switch (systemMethod) { case SystemMethods.NewGuid: - return "NEWID()"; + return "NEWID()"; case SystemMethods.CurrentDateTime: return "GETDATE()"; //case SystemMethods.NewSequentialId: @@ -181,11 +176,11 @@ order by T.name, I.name"); get { return "ALTER TABLE [{0}] DROP CONSTRAINT [DF_{0}_{1}]"; } } - + public override string DropIndex { get { return "DROP INDEX {0} ON {1}"; } } public override string RenameColumn { get { return "sp_rename '{0}.{1}', '{2}', 'COLUMN'"; } } - + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs index a417ec601a..7b713374aa 100644 --- a/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs +++ b/src/Umbraco.Core/Sync/ApplicationUrlHelper.cs @@ -114,7 +114,8 @@ namespace Umbraco.Core.Sync // - contain a scheme // - end or not with a slash, it will be taken care of // eg "http://www.mysite.com/umbraco" - var registrar = ServerRegistrarResolver.Current.Registrar as IServerRegistrar2; + var resolver = ServerRegistrarResolver.HasCurrent ? ServerRegistrarResolver.Current : null; + var registrar = resolver == null ? null : resolver.Registrar as IServerRegistrar2; url = registrar == null ? null : registrar.GetCurrentServerUmbracoApplicationUrl(); if (url.IsNullOrWhiteSpace() == false) { diff --git a/src/Umbraco.Core/Sync/SingleServerRegistrar.cs b/src/Umbraco.Core/Sync/SingleServerRegistrar.cs new file mode 100644 index 0000000000..117ce516be --- /dev/null +++ b/src/Umbraco.Core/Sync/SingleServerRegistrar.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; + +namespace Umbraco.Core.Sync +{ + public class SingleServerRegistrar : IServerRegistrar2 + { + private readonly string _umbracoApplicationUrl; + + public IEnumerable Registrations { get; private set; } + + public SingleServerRegistrar() + { + _umbracoApplicationUrl = ApplicationContext.Current.UmbracoApplicationUrl; + Registrations = new[] { new ServerAddressImpl(_umbracoApplicationUrl) }; + } + + public ServerRole GetCurrentServerRole() + { + return ServerRole.Single; + } + + public string GetCurrentServerUmbracoApplicationUrl() + { + return _umbracoApplicationUrl; + } + + private class ServerAddressImpl : IServerAddress + { + public ServerAddressImpl(string serverAddress) + { + ServerAddress = serverAddress; + } + + public string ServerAddress { get; private set; } + } + } +} diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 9f94796b92..6da7f4519b 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -454,6 +454,7 @@ + @@ -1348,6 +1349,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js index e5fba69a88..e34bc48ecd 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.controller.js @@ -12,6 +12,7 @@ status: "", progress:0 }; + vm.installCompleted = false; vm.zipFile = { uploadStatus: "idle", uploadProgress: 0, @@ -137,10 +138,10 @@ localStorageService.set("packageInstallUri", "installed"); } - //reload on next digest (after cookie) - $timeout(function () { - $window.location.reload(true); - }); + vm.installState.status = localizationService.localize("packager_installStateCompleted"); + vm.installCompleted = true; + + }, installError); @@ -150,6 +151,13 @@ //This will return a rejection meaning that the promise change above will stop return $q.reject(); } + + vm.reloadPage = function() { + //reload on next digest (after cookie) + $timeout(function () { + $window.location.reload(true); + }); + } } angular.module("umbraco").controller("Umbraco.Editors.Packages.InstallLocalController", PackagesInstallLocalController); diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html index 499e844588..6bb7c6fb14 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/install-local.html @@ -159,6 +159,19 @@

{{vm.installState.status}}

+ + +
+ + +
+ diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js index e4afb661e3..5ae1d4bf2c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.controller.js @@ -30,6 +30,7 @@ vm.openLightbox = openLightbox; vm.closeLightbox = closeLightbox; vm.search = search; + vm.installCompleted = false; var currSort = "Latest"; //used to cancel any request in progress if another one needs to take it's place @@ -215,10 +216,8 @@ localStorageService.set("packageInstallUri", result.postInstallationPath); } - //reload on next digest (after cookie) - $timeout(function() { - window.location.reload(true); - }); + vm.installState.status = localizationService.localize("packager_installStateCompleted"); + vm.installCompleted = true; }, error); @@ -277,6 +276,13 @@ searchDebounced(); } + vm.reloadPage = function () { + //reload on next digest (after cookie) + $timeout(function () { + window.location.reload(true); + }); + } + init(); } diff --git a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html index 3975dc96d9..e7b14182ca 100644 --- a/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html +++ b/src/Umbraco.Web.UI.Client/src/views/packager/views/repo.html @@ -340,6 +340,16 @@

{{vm.installState.status}}

+
+ + +
+ diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 4fc01686d9..7d1ee51a4b 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -2398,6 +2398,7 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml index bdc2647563..f7ac08d837 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en.xml @@ -817,7 +817,7 @@ To manage your website, simply open the Umbraco back office and start adding con Installing... Restarting, please wait... All done, your browser will now refresh, please wait... - + Please click finish to complete installation and reload page. Paste with full formatting (Not recommended) diff --git a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml index 7544cacc0c..173da6cc9b 100644 --- a/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml @@ -817,6 +817,7 @@ To manage your website, simply open the Umbraco back office and start adding con Installing... Restarting, please wait... All done, your browser will now refresh, please wait... + Please click finish to complete installation and reload page. Paste with full formatting (Not recommended) diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1578ff7555..a0b112c7c7 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -2258,6 +2258,7 @@ $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v11.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v12.0 $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v15.0 diff --git a/src/umbraco.presentation.targets b/src/umbraco.presentation.targets index c38a1da485..2a33705d6f 100644 --- a/src/umbraco.presentation.targets +++ b/src/umbraco.presentation.targets @@ -55,6 +55,10 @@ $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v14.0\Web\Microsoft.Web.Publishing.Tasks.dll + + + $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v15.0\Web\Microsoft.Web.Publishing.Tasks.dll +