diff --git a/build/UmbracoVersion.txt b/build/UmbracoVersion.txt index 88b444903c..e8bfb02cf8 100644 --- a/build/UmbracoVersion.txt +++ b/build/UmbracoVersion.txt @@ -1,3 +1,3 @@ # Usage: on line 2 put the release version, on line 3 put the version comment (example: beta) 7.7.0 -beta \ No newline at end of file +beta002 \ No newline at end of file diff --git a/src/SolutionInfo.cs b/src/SolutionInfo.cs index db3fa10631..d46dbdaf46 100644 --- a/src/SolutionInfo.cs +++ b/src/SolutionInfo.cs @@ -12,4 +12,4 @@ using System.Resources; [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyFileVersion("7.7.0")] -[assembly: AssemblyInformationalVersion("7.7.0-beta")] \ No newline at end of file +[assembly: AssemblyInformationalVersion("7.7.0-beta002")] \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoVersion.cs b/src/Umbraco.Core/Configuration/UmbracoVersion.cs index d4a18ddcf6..155cf3f124 100644 --- a/src/Umbraco.Core/Configuration/UmbracoVersion.cs +++ b/src/Umbraco.Core/Configuration/UmbracoVersion.cs @@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration /// Gets the version comment (like beta or RC). /// /// The version comment. - public static string CurrentComment { get { return "beta"; } } + public static string CurrentComment { get { return "beta002"; } } // Get the version of the umbraco.dll by looking at a class in that dll // Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index addd0bc925..236f0ed311 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -31,6 +31,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ContentPreviewRepository _contentPreviewRepository; private readonly ContentXmlRepository _contentXmlRepository; private readonly PermissionRepository _permissionRepository; + private readonly ContentByGuidReadRepository _contentByGuidReadRepository; public ContentRepository(IScopeUnitOfWork work, CacheHelper cacheHelper, ILogger logger, ISqlSyntaxProvider syntaxProvider, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository, ITagRepository tagRepository, IContentSection contentSection) : base(work, cacheHelper, logger, syntaxProvider, contentSection) @@ -44,7 +45,7 @@ namespace Umbraco.Core.Persistence.Repositories _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.NoCache, logger, syntaxProvider); _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.NoCache, logger, syntaxProvider); _permissionRepository = new PermissionRepository(UnitOfWork, cacheHelper, Logger, SqlSyntax); - + _contentByGuidReadRepository = new ContentByGuidReadRepository(this, work, cacheHelper, logger, syntaxProvider); EnsureUniqueNaming = true; } @@ -938,6 +939,113 @@ order by umbracoNode.{2}, umbracoNode.parentID, umbracoNode.sortOrder", #endregion + #region Read Repository implementation for GUID keys + public IContent Get(Guid id) + { + return _contentByGuidReadRepository.Get(id); + } + + IEnumerable IReadRepository.GetAll(params Guid[] ids) + { + return _contentByGuidReadRepository.GetAll(ids); + } + + public bool Exists(Guid id) + { + return _contentByGuidReadRepository.Exists(id); + } + + /// + /// A reading repository purely for looking up by GUID + /// + /// + /// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things! + /// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them + /// + private class ContentByGuidReadRepository : PetaPocoRepositoryBase + { + private readonly ContentRepository _outerRepo; + + public ContentByGuidReadRepository(ContentRepository outerRepo, + IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { + _outerRepo = outerRepo; + } + + protected override IContent PerformGet(Guid id) + { + var sql = _outerRepo.GetBaseQuery(BaseQueryType.FullSingle) + .Where(GetBaseWhereClause(), new { Id = id }) + .Where(x => x.Newest, SqlSyntax) + .OrderByDescending(x => x.VersionDate, SqlSyntax); + + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + + if (dto == null) + return null; + + var content = _outerRepo.CreateContentFromDto(dto, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + Func translate = s => + { + if (ids.Any()) + { + s.Where("umbracoNode.uniqueID in (@ids)", new { ids }); + } + //we only want the newest ones with this method + s.Where(x => x.Newest, SqlSyntax); + return s; + }; + + var sqlBaseFull = _outerRepo.GetBaseQuery(BaseQueryType.FullMultiple); + var sqlBaseIds = _outerRepo.GetBaseQuery(BaseQueryType.Ids); + + return _outerRepo.ProcessQuery(translate(sqlBaseFull), new PagingSqlQuery(translate(sqlBaseIds))); + } + + protected override Sql GetBaseQuery(bool isCount) + { + return _outerRepo.GetBaseQuery(isCount); + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.uniqueID = @Id"; + } + + protected override Guid NodeObjectTypeId + { + get { return _outerRepo.NodeObjectTypeId; } + } + + #region Not needed to implement + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + protected override IEnumerable GetDeleteClauses() + { + throw new NotImplementedException(); + } + protected override void PersistNewItem(IContent entity) + { + throw new NotImplementedException(); + } + protected override void PersistUpdatedItem(IContent entity) + { + throw new NotImplementedException(); + } + #endregion + } + #endregion + protected override string GetDatabaseFieldNameForOrderBy(string orderBy) { //Some custom ones diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs index c019b694d2..c98f073f40 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IContentRepository.cs @@ -11,7 +11,7 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository + public interface IContentRepository : IRepositoryVersionable, IRecycleBinRepository, IReadRepository, IDeleteMediaFilesRepository { /// /// This builds the Xml document used for the XML cache diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs index 4c9b1d3561..b6a22ba761 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IMediaRepository.cs @@ -7,9 +7,8 @@ using Umbraco.Core.Persistence.Querying; namespace Umbraco.Core.Persistence.Repositories { - public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IDeleteMediaFilesRepository + public interface IMediaRepository : IRepositoryVersionable, IRecycleBinRepository, IReadRepository, IDeleteMediaFilesRepository { - /// /// Used to add/update published xml for the media item /// diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index ee413c2a59..ad2dd26a43 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -27,6 +27,7 @@ namespace Umbraco.Core.Persistence.Repositories private readonly ITagRepository _tagRepository; private readonly ContentXmlRepository _contentXmlRepository; private readonly ContentPreviewRepository _contentPreviewRepository; + private readonly MediaByGuidReadRepository _mediaByGuidReadRepository; public MediaRepository(IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax, IMediaTypeRepository mediaTypeRepository, ITagRepository tagRepository, IContentSection contentSection) : base(work, cache, logger, sqlSyntax, contentSection) @@ -37,6 +38,7 @@ namespace Umbraco.Core.Persistence.Repositories _tagRepository = tagRepository; _contentXmlRepository = new ContentXmlRepository(work, CacheHelper.NoCache, logger, sqlSyntax); _contentPreviewRepository = new ContentPreviewRepository(work, CacheHelper.NoCache, logger, sqlSyntax); + _mediaByGuidReadRepository = new MediaByGuidReadRepository(this, work, cache, logger, sqlSyntax); EnsureUniqueNaming = contentSection.EnsureUniqueNaming; } @@ -522,6 +524,104 @@ namespace Umbraco.Core.Persistence.Repositories #endregion + #region Read Repository implementation for GUID keys + public IMedia Get(Guid id) + { + return _mediaByGuidReadRepository.Get(id); + } + + IEnumerable IReadRepository.GetAll(params Guid[] ids) + { + return _mediaByGuidReadRepository.GetAll(ids); + } + + public bool Exists(Guid id) + { + return _mediaByGuidReadRepository.Exists(id); + } + + /// + /// A reading repository purely for looking up by GUID + /// + /// + /// TODO: This is ugly and to fix we need to decouple the IRepositoryQueryable -> IRepository -> IReadRepository which should all be separate things! + /// Then we can do the same thing with repository instances and we wouldn't need to leave all these methods as not implemented because we wouldn't need to implement them + /// + private class MediaByGuidReadRepository : PetaPocoRepositoryBase + { + private readonly MediaRepository _outerRepo; + + public MediaByGuidReadRepository(MediaRepository outerRepo, + IScopeUnitOfWork work, CacheHelper cache, ILogger logger, ISqlSyntaxProvider sqlSyntax) + : base(work, cache, logger, sqlSyntax) + { + _outerRepo = outerRepo; + } + + protected override IMedia PerformGet(Guid id) + { + var sql = GetBaseQuery(false); + sql.Where(GetBaseWhereClause(), new { Id = id }); + sql.OrderByDescending(x => x.VersionDate, SqlSyntax); + + var dto = Database.Fetch(SqlSyntax.SelectTop(sql, 1)).FirstOrDefault(); + + if (dto == null) + return null; + + var content = _outerRepo.CreateMediaFromDto(dto, sql); + + return content; + } + + protected override IEnumerable PerformGetAll(params Guid[] ids) + { + var sql = GetBaseQuery(false); + if (ids.Any()) + { + sql.Where("umbracoNode.uniqueID in (@ids)", new { ids = ids }); + } + + return _outerRepo.ProcessQuery(sql, new PagingSqlQuery(sql)); + } + + protected override Sql GetBaseQuery(bool isCount) + { + return _outerRepo.GetBaseQuery(isCount); + } + + protected override string GetBaseWhereClause() + { + return "umbracoNode.uniqueID = @Id"; + } + + protected override Guid NodeObjectTypeId + { + get { return _outerRepo.NodeObjectTypeId; } + } + + #region Not needed to implement + + protected override IEnumerable PerformGetByQuery(IQuery query) + { + throw new NotImplementedException(); + } + protected override IEnumerable GetDeleteClauses() + { + throw new NotImplementedException(); + } + protected override void PersistNewItem(IMedia entity) + { + throw new NotImplementedException(); + } + protected override void PersistUpdatedItem(IMedia entity) + { + throw new NotImplementedException(); + } + #endregion + } + #endregion + /// /// Gets paged media results /// diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 1ba94105f5..42ea80a717 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -30,7 +30,6 @@ namespace Umbraco.Core.Services private readonly EntityXmlSerializer _entitySerializer = new EntityXmlSerializer(); private readonly IDataTypeService _dataTypeService; private readonly IUserService _userService; - private readonly IdkMap _idkMap; //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. @@ -42,8 +41,7 @@ namespace Umbraco.Core.Services ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, - IUserService userService, - IdkMap idkMap) + IUserService userService) : base(provider, repositoryFactory, logger, eventMessagesFactory) { if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); @@ -51,7 +49,6 @@ namespace Umbraco.Core.Services _publishingStrategy = new PublishingStrategy(UowProvider.ScopeProvider, eventMessagesFactory, logger); _dataTypeService = dataTypeService; _userService = userService; - _idkMap = idkMap; } #region Static Queries @@ -141,6 +138,26 @@ namespace Umbraco.Core.Services } } + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// Note that using this method will simply return a new IContent without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + public IContent CreateContent(string name, Guid parentId, string contentTypeAlias, int userId = 0) + { + var parent = GetById(parentId); + return CreateContent(name, parent, contentTypeAlias, userId); + } + /// /// Creates an object using the alias of the /// that this Content should based on. @@ -347,7 +364,7 @@ namespace Umbraco.Core.Services } /// - /// Gets an object by Id + /// Gets objects by Ids /// /// Ids of the Content to retrieve /// @@ -374,6 +391,34 @@ namespace Umbraco.Core.Services } } + /// + /// Gets objects by Ids + /// + /// Ids of the Content to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + var idsArray = ids.ToArray(); + if (idsArray.Length == 0) return Enumerable.Empty(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + + // ensure that the result has the order based on the ids passed in + var result = repository.GetAll(idsArray); + var content = result.ToDictionary(x => x.Key, x => x); + + var sortedResult = idsArray.Select(x => + { + IContent c; + return content.TryGetValue(x, out c) ? c : null; + }).WhereNotNull(); + + return sortedResult; + } + } + /// /// Gets an object by its 'UniqueId' /// @@ -381,13 +426,11 @@ namespace Umbraco.Core.Services /// public IContent GetById(Guid key) { - // the repository implements a cache policy on int identifiers, not guids, - // and we are not changing it now, but we still would like to rely on caching - // instead of running a full query against the database, so relying on the - // id-key map, which is fast. - - var a = _idkMap.GetIdForKey(key, UmbracoObjectTypes.Document); - return a.Success ? GetById(a.Result) : null; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentRepository(uow); + return repository.Get(key); + } } /// @@ -1168,13 +1211,14 @@ namespace Umbraco.Core.Services public IContent GetBlueprintById(Guid id) { - // the repository implements a cache policy on int identifiers, not guids, - // and we are not changing it now, but we still would like to rely on caching - // instead of running a full query against the database, so relying on the - // id-key map, which is fast. - - var a = _idkMap.GetIdForKey(id, UmbracoObjectTypes.DocumentBlueprint); - return a.Success ? GetBlueprintById(a.Result) : null; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateContentBlueprintRepository(uow); + var blueprint = repository.Get(id); + if (blueprint != null) + ((Content)blueprint).IsBlueprint = true; + return blueprint; + } } public void SaveBlueprint(IContent content, int userId = 0) diff --git a/src/Umbraco.Core/Services/ContentServiceExtensions.cs b/src/Umbraco.Core/Services/ContentServiceExtensions.cs index b3380cf1bf..6e3f75c42d 100644 --- a/src/Umbraco.Core/Services/ContentServiceExtensions.cs +++ b/src/Umbraco.Core/Services/ContentServiceExtensions.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using System.Linq; using Umbraco.Core.Models; @@ -8,8 +9,43 @@ using Umbraco.Core.Persistence.Repositories; namespace Umbraco.Core.Services { + /// + /// Content service extension methods + /// public static class ContentServiceExtensions - { + { + public static IEnumerable GetByIds(this IContentService contentService, IEnumerable ids) + { + var guids = new List(); + foreach (var udi in ids) + { + var guidUdi = udi as GuidUdi; + if (guidUdi == null) + throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content"); + guids.Add(guidUdi); + } + + return contentService.GetByIds(guids.Select(x => x.Guid)); + } + + /// + /// Method to create an IContent object based on the Udi of a parent + /// + /// + /// + /// + /// + /// + /// + public static IContent CreateContent(this IContentService contentService, string name, Udi parentId, string mediaTypeAlias, int userId = 0) + { + var guidUdi = parentId as GuidUdi; + if (guidUdi == null) + throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by content"); + var parent = contentService.GetById(guidUdi.Guid); + return contentService.CreateContent(name, parent, mediaTypeAlias, userId); + } + /// /// Remove all permissions for this user for all nodes /// diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index e33f46e3f4..7a5c64c0c0 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -161,6 +161,23 @@ namespace Umbraco.Core.Services bool SendToPublication(IContent content, int userId = 0); IEnumerable GetByIds(IEnumerable ids); + IEnumerable GetByIds(IEnumerable ids); + + /// + /// Creates an object using the alias of the + /// that this Content should based on. + /// + /// + /// Note that using this method will simply return a new IContent without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new content objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Content object + /// Id of Parent for the new Content + /// Alias of the + /// Optional id of the user creating the content + /// + IContent CreateContent(string name, Guid parentId, string contentTypeAlias, int userId = 0); /// /// Creates an object using the alias of the diff --git a/src/Umbraco.Core/Services/IMediaService.cs b/src/Umbraco.Core/Services/IMediaService.cs index 8cd2b0eed5..04683e5747 100644 --- a/src/Umbraco.Core/Services/IMediaService.cs +++ b/src/Umbraco.Core/Services/IMediaService.cs @@ -87,6 +87,23 @@ namespace Umbraco.Core.Services int CountDescendants(int parentId, string contentTypeAlias = null); IEnumerable GetByIds(IEnumerable ids); + IEnumerable GetByIds(IEnumerable ids); + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = 0); /// /// Creates an object using the alias of the diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index 38c0c20eb2..ee4cddb382 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -35,17 +35,35 @@ namespace Umbraco.Core.Services private readonly IDataTypeService _dataTypeService; private readonly IUserService _userService; private readonly MediaFileSystem _mediaFileSystem = FileSystemProviderManager.Current.MediaFileSystem; - private readonly IdkMap _idkMap; - public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService, IdkMap idkMap) + public MediaService(IDatabaseUnitOfWorkProvider provider, RepositoryFactory repositoryFactory, ILogger logger, IEventMessagesFactory eventMessagesFactory, IDataTypeService dataTypeService, IUserService userService) : base(provider, repositoryFactory, logger, eventMessagesFactory) { if (dataTypeService == null) throw new ArgumentNullException("dataTypeService"); if (userService == null) throw new ArgumentNullException("userService"); _dataTypeService = dataTypeService; _userService = userService; - _idkMap = idkMap; - } + } + + /// + /// Creates an object using the alias of the + /// that this Media should based on. + /// + /// + /// Note that using this method will simply return a new IMedia without any identity + /// as it has not yet been persisted. It is intended as a shortcut to creating new media objects + /// that does not invoke a save operation against the database. + /// + /// Name of the Media object + /// Id of Parent for the new Media item + /// Alias of the + /// Optional id of the user creating the media item + /// + public IMedia CreateMedia(string name, Guid parentId, string mediaTypeAlias, int userId = 0) + { + var parent = GetById(parentId); + return CreateMedia(name, parent, mediaTypeAlias, userId); + } /// /// Creates an object using the alias of the @@ -307,7 +325,7 @@ namespace Umbraco.Core.Services } /// - /// Gets an object by Id + /// Gets an objects by Ids /// /// Ids of the Media to retrieve /// @@ -322,6 +340,22 @@ namespace Umbraco.Core.Services } } + /// + /// Gets an objects by Ids + /// + /// Ids of the Media to retrieve + /// + public IEnumerable GetByIds(IEnumerable ids) + { + if (ids.Any() == false) return Enumerable.Empty(); + + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateMediaRepository(uow); + return repository.GetAll(ids.ToArray()); + } + } + /// /// Gets an object by its 'UniqueId' /// @@ -329,15 +363,11 @@ namespace Umbraco.Core.Services /// public IMedia GetById(Guid key) { - // the repository implements a cache policy on int identifiers, not guids, - // and we are not changing it now, but we still would like to rely on caching - // instead of running a full query against the database, so relying on the - // id-key map, which is fast. - // - // we should inject the id-key map but ... breaking changes ... yada - - var a = _idkMap.GetIdForKey(key, UmbracoObjectTypes.Media); - return a.Success ? GetById(a.Result) : null; + using (var uow = UowProvider.GetUnitOfWork(readOnly: true)) + { + var repository = RepositoryFactory.CreateMediaRepository(uow); + return repository.Get(key); + } } /// diff --git a/src/Umbraco.Core/Services/MediaServiceExtensions.cs b/src/Umbraco.Core/Services/MediaServiceExtensions.cs new file mode 100644 index 0000000000..41e0ebe55e --- /dev/null +++ b/src/Umbraco.Core/Services/MediaServiceExtensions.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Umbraco.Core.Models; + +namespace Umbraco.Core.Services +{ + /// + /// Media service extension methods + /// + /// + /// Many of these have to do with UDI lookups but we don't need to add these methods to the service interface since a UDI is just a GUID + /// and the services already support GUIDs + /// + public static class MediaServiceExtensions + { + public static IEnumerable GetByIds(this IMediaService mediaService, IEnumerable ids) + { + var guids = new List(); + foreach (var udi in ids) + { + var guidUdi = udi as GuidUdi; + if (guidUdi == null) + throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by media"); + guids.Add(guidUdi); + } + + return mediaService.GetByIds(guids.Select(x => x.Guid)); + } + + public static IMedia CreateMedia(this IMediaService mediaService, string name, Udi parentId, string mediaTypeAlias, int userId = 0) + { + var guidUdi = parentId as GuidUdi; + if (guidUdi == null) + throw new InvalidOperationException("The UDI provided isn't of type " + typeof(GuidUdi) + " which is required by media"); + var parent = mediaService.GetById(guidUdi.Guid); + return mediaService.CreateMedia(name, parent, mediaTypeAlias, userId); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 3da10e132b..144d548b78 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -261,10 +261,10 @@ namespace Umbraco.Core.Services _memberService = new Lazy(() => new MemberService(provider, repositoryFactory, logger, eventMessagesFactory, _memberGroupService.Value, _dataTypeService.Value)); if (_contentService == null) - _contentService = new Lazy(() => new ContentService(provider, repositoryFactory, logger, eventMessagesFactory, _dataTypeService.Value, _userService.Value, idkMap)); + _contentService = new Lazy(() => new ContentService(provider, repositoryFactory, logger, eventMessagesFactory, _dataTypeService.Value, _userService.Value)); if (_mediaService == null) - _mediaService = new Lazy(() => new MediaService(provider, repositoryFactory, logger, eventMessagesFactory, _dataTypeService.Value, _userService.Value, idkMap)); + _mediaService = new Lazy(() => new MediaService(provider, repositoryFactory, logger, eventMessagesFactory, _dataTypeService.Value, _userService.Value)); if (_contentTypeService == null) _contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory, logger, eventMessagesFactory, _contentService.Value, _mediaService.Value)); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 96183f61bf..a676d8caa3 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -705,6 +705,7 @@ + diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index f969488055..22331bc850 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Web; using System.Xml.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.IO; using Umbraco.Core.Models; @@ -41,32 +43,77 @@ namespace Umbraco.Tests.Persistence.Repositories base.TearDown(); } - private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out DataTypeDefinitionRepository dtdRepository) + private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out DataTypeDefinitionRepository dtdRepository, CacheHelper cacheHelper = null) { + cacheHelper = cacheHelper ?? CacheHelper; + TemplateRepository tr; var ctRepository = CreateRepository(unitOfWork, out contentTypeRepository, out tr); - dtdRepository = new DataTypeDefinitionRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, contentTypeRepository); + dtdRepository = new DataTypeDefinitionRepository(unitOfWork, cacheHelper, Logger, SqlSyntax, contentTypeRepository); return ctRepository; } - private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository) + private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, CacheHelper cacheHelper = null) { TemplateRepository tr; - return CreateRepository(unitOfWork, out contentTypeRepository, out tr); + return CreateRepository(unitOfWork, out contentTypeRepository, out tr, cacheHelper); } - private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository) + private ContentRepository CreateRepository(IScopeUnitOfWork unitOfWork, out ContentTypeRepository contentTypeRepository, out TemplateRepository templateRepository, CacheHelper cacheHelper = null) { - templateRepository = new TemplateRepository(unitOfWork, CacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()); - 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()); + cacheHelper = cacheHelper ?? CacheHelper; + + templateRepository = new TemplateRepository(unitOfWork, cacheHelper, Logger, SqlSyntax, Mock.Of(), Mock.Of(), Mock.Of()); + 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()); return repository; } - private UserGroupRepository CreateUserGroupRepository(IScopeUnitOfWork unitOfWork) + [Test] + public void Cache_Active_By_Int_And_Guid() { - return new UserGroupRepository(unitOfWork, CacheHelper, Logger, SqlSyntax); + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + ContentTypeRepository contentTypeRepository; + + var realCache = new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + new StaticCacheProvider(), + new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())); + + using (var repository = CreateRepository(unitOfWork, out contentTypeRepository, cacheHelper: realCache)) + { + DatabaseContext.Database.DisableSqlCount(); + + var contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage"); + var content = MockedContent.CreateSimpleContent(contentType); + contentTypeRepository.AddOrUpdate(contentType); + repository.AddOrUpdate(content); + unitOfWork.Commit(); + + DatabaseContext.Database.EnableSqlCount(); + + //go get it, this should already be cached since the default repository key is the INT + var found = repository.Get(content.Id); + Assert.AreEqual(0, DatabaseContext.Database.SqlCount); + //retrieve again, this should use cache + found = repository.Get(content.Id); + Assert.AreEqual(0, DatabaseContext.Database.SqlCount); + + //reset counter + DatabaseContext.Database.DisableSqlCount(); + DatabaseContext.Database.EnableSqlCount(); + + //now get by GUID, this won't be cached yet because the default repo key is not a GUID + found = repository.Get(content.Key); + var sqlCount = DatabaseContext.Database.SqlCount; + Assert.Greater(sqlCount, 0); + //retrieve again, this should use cache now + found = repository.Get(content.Key); + Assert.AreEqual(sqlCount, DatabaseContext.Database.SqlCount); + } } [Test] @@ -1039,6 +1086,16 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(contents, Is.Not.Null); Assert.That(contents.Any(), Is.True); Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4)); + + contents = repository.GetAll(contents.Select(x => x.Id).ToArray()); + Assert.That(contents, Is.Not.Null); + Assert.That(contents.Any(), Is.True); + Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4)); + + contents = ((IReadRepository)repository).GetAll(contents.Select(x => x.Key).ToArray()); + Assert.That(contents, Is.Not.Null); + Assert.That(contents.Any(), Is.True); + Assert.That(contents.Count(), Is.GreaterThanOrEqualTo(4)); } diff --git a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs index c9f78fb201..a2f82b0ad0 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/MediaRepositoryTest.cs @@ -5,6 +5,7 @@ using System.Xml.Linq; using Moq; using NUnit.Framework; using Umbraco.Core; +using Umbraco.Core.Cache; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Logging; using Umbraco.Core.Models; @@ -35,14 +36,62 @@ namespace Umbraco.Tests.Persistence.Repositories CreateTestData(); } - private MediaRepository CreateRepository(IScopeUnitOfWork unitOfWork, out MediaTypeRepository mediaTypeRepository) + private MediaRepository CreateRepository(IScopeUnitOfWork unitOfWork, out MediaTypeRepository mediaTypeRepository, CacheHelper cacheHelper = null) { - mediaTypeRepository = new MediaTypeRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); - var tagRepository = new TagRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax); - var repository = new MediaRepository(unitOfWork, CacheHelper, Mock.Of(), SqlSyntax, mediaTypeRepository, tagRepository, Mock.Of()); + cacheHelper = cacheHelper ?? CacheHelper; + + mediaTypeRepository = new MediaTypeRepository(unitOfWork, cacheHelper, Mock.Of(), SqlSyntax); + var tagRepository = new TagRepository(unitOfWork, cacheHelper, Mock.Of(), SqlSyntax); + var repository = new MediaRepository(unitOfWork, cacheHelper, Mock.Of(), SqlSyntax, mediaTypeRepository, tagRepository, Mock.Of()); return repository; } + [Test] + public void Cache_Active_By_Int_And_Guid() + { + var provider = new PetaPocoUnitOfWorkProvider(Logger); + var unitOfWork = provider.GetUnitOfWork(); + MediaTypeRepository mediaTypeRepository; + + var realCache = new CacheHelper( + new ObjectCacheRuntimeCacheProvider(), + new StaticCacheProvider(), + new StaticCacheProvider(), + new IsolatedRuntimeCache(t => new ObjectCacheRuntimeCacheProvider())); + + using (var repository = CreateRepository(unitOfWork, out mediaTypeRepository, cacheHelper: realCache)) + { + DatabaseContext.Database.DisableSqlCount(); + + var mediaType = MockedContentTypes.CreateSimpleMediaType("umbTextpage1", "Textpage"); + var media = MockedMedia.CreateSimpleMedia(mediaType, "hello", -1); + mediaTypeRepository.AddOrUpdate(mediaType); + repository.AddOrUpdate(media); + unitOfWork.Commit(); + + DatabaseContext.Database.EnableSqlCount(); + + //go get it, this should already be cached since the default repository key is the INT + var found = repository.Get(media.Id); + Assert.AreEqual(0, DatabaseContext.Database.SqlCount); + //retrieve again, this should use cache + found = repository.Get(media.Id); + Assert.AreEqual(0, DatabaseContext.Database.SqlCount); + + //reset counter + DatabaseContext.Database.DisableSqlCount(); + DatabaseContext.Database.EnableSqlCount(); + + //now get by GUID, this won't be cached yet because the default repo key is not a GUID + found = repository.Get(media.Key); + var sqlCount = DatabaseContext.Database.SqlCount; + Assert.Greater(sqlCount, 0); + //retrieve again, this should use cache now + found = repository.Get(media.Key); + Assert.AreEqual(sqlCount, DatabaseContext.Database.SqlCount); + } + } + [Test] public void Rebuild_All_Xml_Structures() { @@ -627,6 +676,16 @@ namespace Umbraco.Tests.Persistence.Repositories Assert.That(medias, Is.Not.Null); Assert.That(medias.Any(), Is.True); Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3)); + + medias = repository.GetAll(medias.Select(x => x.Id).ToArray()); + Assert.That(medias, Is.Not.Null); + Assert.That(medias.Any(), Is.True); + Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3)); + + medias = ((IReadRepository)repository).GetAll(medias.Select(x => x.Key).ToArray()); + Assert.That(medias, Is.Not.Null); + Assert.That(medias.Any(), Is.True); + Assert.That(medias.Count(), Is.GreaterThanOrEqualTo(3)); } }