private PropertyType MapPropertyType(IContentTypeComposition contentType, PropertyTypeCommonDto dto, IDictionary <string, PropertyType> builtinProperties)
        {
            var groupId = dto.PropertyTypeGroupId;

            var readonlyStorageType = builtinProperties.TryGetValue(dto.Alias, out var propertyType);
            var storageType         = readonlyStorageType ? propertyType.ValueStorageType : Enum <ValueStorageType> .Parse(dto.DataTypeDto.DbType);

            if (contentType is MemberType memberType)
            {
                var access = new MemberTypePropertyProfileAccess(dto.ViewOnProfile, dto.CanEdit, dto.IsSensitive);
                memberType.MemberTypePropertyTypes[dto.Alias] = access;
            }

            return(new PropertyType(dto.DataTypeDto.EditorAlias, storageType, readonlyStorageType, dto.Alias)
            {
                Description = dto.Description,
                DataTypeId = dto.DataTypeId,
                DataTypeKey = dto.DataTypeDto.NodeDto.UniqueId,
                Id = dto.Id,
                Key = dto.UniqueId,
                Mandatory = dto.Mandatory,
                Name = dto.Name,
                PropertyGroupId = groupId.HasValue ? new Lazy <int>(() => groupId.Value) : null,
                SortOrder = dto.SortOrder,
                ValidationRegExp = dto.ValidationRegExp,
                Variations = (ContentVariation)dto.Variations
            });
        }
        private void MapGroupsAndProperties(IDictionary <int, IContentTypeComposition> contentTypes)
        {
            var sql1 = Sql()
                       .Select <PropertyTypeGroupDto>()
                       .From <PropertyTypeGroupDto>()
                       .InnerJoin <ContentTypeDto>().On <PropertyTypeGroupDto, ContentTypeDto>((ptg, ct) => ptg.ContentTypeNodeId == ct.NodeId)
                       .OrderBy <ContentTypeDto>(x => x.NodeId)
                       .AndBy <PropertyTypeGroupDto>(x => x.SortOrder, x => x.Id);

            var groupDtos = Database.Fetch <PropertyTypeGroupDto>(sql1);

            var sql2 = Sql()
                       .Select <PropertyTypeDto>(r => r.Select(x => x.DataTypeDto, r1 => r1.Select(x => x.NodeDto)))
                       .AndSelect <MemberPropertyTypeDto>()
                       .From <PropertyTypeDto>()
                       .InnerJoin <DataTypeDto>().On <PropertyTypeDto, DataTypeDto>((pt, dt) => pt.DataTypeId == dt.NodeId)
                       .InnerJoin <NodeDto>().On <DataTypeDto, NodeDto>((dt, n) => dt.NodeId == n.NodeId)
                       .InnerJoin <ContentTypeDto>().On <PropertyTypeDto, ContentTypeDto>((pt, ct) => pt.ContentTypeId == ct.NodeId)
                       .LeftJoin <PropertyTypeGroupDto>().On <PropertyTypeDto, PropertyTypeGroupDto>((pt, ptg) => pt.PropertyTypeGroupId == ptg.Id)
                       .LeftJoin <MemberPropertyTypeDto>().On <PropertyTypeDto, MemberPropertyTypeDto>((pt, mpt) => pt.Id == mpt.PropertyTypeId)
                       .OrderBy <ContentTypeDto>(x => x.NodeId)
                       .AndBy <PropertyTypeGroupDto>(x => x.SortOrder, x => x.Id) // NULLs will come first or last, never mind, we deal with it below
                       .AndBy <PropertyTypeDto>(x => x.SortOrder, x => x.Id);

            var propertyDtos      = Database.Fetch <PropertyTypeCommonDto>(sql2);
            var builtinProperties = Constants.Conventions.Member.GetStandardPropertyTypeStubs();

            var groupIx    = 0;
            var propertyIx = 0;

            foreach (var contentType in contentTypes.Values)
            {
                // only IContentType is publishing
                var isPublishing = contentType is IContentType;

                // get group-less properties (in case NULL is ordered first)
                var noGroupPropertyTypes = new PropertyTypeCollection(isPublishing);
                while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == null)
                {
                    noGroupPropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties));
                    propertyIx++;
                }

                // get groups and their properties
                var groupCollection = new PropertyGroupCollection();
                while (groupIx < groupDtos.Count && groupDtos[groupIx].ContentTypeNodeId == contentType.Id)
                {
                    var group = MapPropertyGroup(groupDtos[groupIx], isPublishing);
                    groupCollection.Add(group);
                    groupIx++;

                    while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == group.Id)
                    {
                        group.PropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties));
                        propertyIx++;
                    }
                }
                contentType.PropertyGroups = groupCollection;

                // get group-less properties (in case NULL is ordered last)
                while (propertyIx < propertyDtos.Count && propertyDtos[propertyIx].ContentTypeId == contentType.Id && propertyDtos[propertyIx].PropertyTypeGroupId == null)
                {
                    noGroupPropertyTypes.Add(MapPropertyType(contentType, propertyDtos[propertyIx], builtinProperties));
                    propertyIx++;
                }
                contentType.NoGroupPropertyTypes = noGroupPropertyTypes;

                // ensure builtin properties
                if (contentType is MemberType memberType)
                {
                    // ensure that the group exists (ok if it already exists)
                    memberType.AddPropertyGroup(Constants.Conventions.Member.StandardPropertiesGroupName);

                    // ensure that property types exist (ok if they already exist)
                    foreach (var(alias, propertyType) in builtinProperties)
                    {
                        var added = memberType.AddPropertyType(propertyType, Constants.Conventions.Member.StandardPropertiesGroupName);

                        if (added)
                        {
                            var access = new MemberTypePropertyProfileAccess(false, false, false);
                            memberType.MemberTypePropertyTypes[alias] = access;
                        }
                    }
                }
            }
        }