From feff68ada88855c5a8d801e86ec9fccb05a4e177 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Tue, 11 Dec 2012 12:03:36 +0500 Subject: [PATCH] Refactor new Services for thread safety, plus unit tests. Not all services are updated to be correct, only the ContentService, the rest need to be updated as well. --- src/Umbraco.Core/DatabaseContext.cs | 5 +- src/Umbraco.Core/EventArgs.cs | 30 +- src/Umbraco.Core/Models/ContentExtensions.cs | 5 +- .../Persistence/DatabaseFactory.cs | 59 +- .../Repositories/ContentRepository.cs | 4 +- .../Repositories/ContentTypeBaseRepository.cs | 6 +- .../Repositories/ContentTypeRepository.cs | 4 +- .../DataTypeDefinitionRepository.cs | 5 +- .../Repositories/DictionaryRepository.cs | 5 +- .../Repositories/FileRepository.cs | 13 +- .../Repositories/Interfaces/IRepository.cs | 5 +- .../Repositories/LanguageRepository.cs | 5 +- .../Repositories/MacroRepository.cs | 5 +- .../Repositories/MediaRepository.cs | 4 +- .../Repositories/MediaTypeRepository.cs | 4 +- .../Repositories/PetaPocoRepositoryBase.cs | 16 +- .../Repositories/RelationRepository.cs | 5 +- .../Repositories/RelationTypeRepository.cs | 5 +- .../Repositories/RepositoryBase.cs | 34 +- .../Repositories/TemplateRepository.cs | 9 +- .../Repositories/UserRepository.cs | 4 +- .../Repositories/UserTypeRepository.cs | 6 +- .../Persistence/RepositoryFactory.cs | 26 +- .../Persistence/UnitOfWork/FileUnitOfWork.cs | 1 + .../UnitOfWork/IDatabaseUnitOfWork.cs | 12 + .../UnitOfWork/IDatabaseUnitOfWorkProvider.cs | 10 + .../Persistence/UnitOfWork/IUnitOfWork.cs | 7 +- .../UnitOfWork/IUnitOfWorkProvider.cs | 2 +- .../UnitOfWork/PetaPocoUnitOfWork.cs | 39 +- .../UnitOfWork/PetaPocoUnitOfWorkProvider.cs | 16 +- src/Umbraco.Core/Services/ContentService.cs | 729 ++++++++++-------- .../Services/ContentTypeService.cs | 4 +- src/Umbraco.Core/Services/DataTypeService.cs | 4 +- src/Umbraco.Core/Services/FileService.cs | 4 +- .../Services/LocalizationService.cs | 4 +- src/Umbraco.Core/Services/MediaService.cs | 30 +- src/Umbraco.Core/Services/ServiceContext.cs | 21 +- src/Umbraco.Core/Services/UserService.cs | 4 +- src/Umbraco.Core/Umbraco.Core.csproj | 2 + src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs | 3 - src/Umbraco.Tests/Models/ContentXmlTest.cs | 2 - .../Repositories/ContentRepositoryTest.cs | 2 +- .../Persistence/RepositoryResolverTests.cs | 27 +- .../Publishing/PublishingStrategyTests.cs | 4 +- .../Services/ContentServiceTests.cs | 4 +- .../Services/ThreadSafetyServiceTest.cs | 257 ++++++ .../TestHelpers/BaseDatabaseFactoryTest.cs | 7 +- src/Umbraco.Tests/Umbraco.Tests.csproj | 1 + src/Umbraco.Web/UmbracoModule.cs | 17 +- 49 files changed, 987 insertions(+), 490 deletions(-) create mode 100644 src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWork.cs create mode 100644 src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWorkProvider.cs create mode 100644 src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs diff --git a/src/Umbraco.Core/DatabaseContext.cs b/src/Umbraco.Core/DatabaseContext.cs index 655801dddf..306033b08e 100644 --- a/src/Umbraco.Core/DatabaseContext.cs +++ b/src/Umbraco.Core/DatabaseContext.cs @@ -49,7 +49,10 @@ namespace Umbraco.Core /// public Database Database { - get { return DatabaseFactory.Current.Database; } + get + { + return DatabaseFactory.Current.Database; + } } /// diff --git a/src/Umbraco.Core/EventArgs.cs b/src/Umbraco.Core/EventArgs.cs index 2d5fee1bcd..24edc6003f 100644 --- a/src/Umbraco.Core/EventArgs.cs +++ b/src/Umbraco.Core/EventArgs.cs @@ -1,8 +1,11 @@ using Umbraco.Core.Models; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Services; namespace Umbraco.Core { - //Publishing Events + + //Publishing Events public class PublishingEventArgs : System.ComponentModel.CancelEventArgs { } public class SendToPublishEventArgs : System.ComponentModel.CancelEventArgs { } @@ -39,7 +42,30 @@ namespace Umbraco.Core /// public int Id { get; set; } } - public class SaveEventArgs : System.ComponentModel.CancelEventArgs { } + public class SaveEventArgs : System.ComponentModel.CancelEventArgs + { + /// + /// public constructor + /// + public SaveEventArgs() + { + + } + + /// + /// internal constructor used for unit testing + /// + /// + internal SaveEventArgs(IUnitOfWork unitOfWork) + { + UnitOfWork = unitOfWork; + } + + /// + /// Used for unit testing + /// + internal IUnitOfWork UnitOfWork { get; private set; } + } public class NewEventArgs : System.ComponentModel.CancelEventArgs { /// diff --git a/src/Umbraco.Core/Models/ContentExtensions.cs b/src/Umbraco.Core/Models/ContentExtensions.cs index fa5b470979..0639bfa86f 100644 --- a/src/Umbraco.Core/Models/ContentExtensions.cs +++ b/src/Umbraco.Core/Models/ContentExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Umbraco.Core.Configuration; using Umbraco.Core.Models.Membership; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.Repositories; @@ -51,7 +52,7 @@ namespace Umbraco.Core.Models public static IProfile GetCreatorProfile(this IContent content) { var repository = RepositoryResolver.Current.Factory.CreateUserRepository( - new PetaPocoUnitOfWork()); + new PetaPocoUnitOfWork(DatabaseContext.Current.Database)); return repository.GetProfileById(content.CreatorId); } @@ -61,7 +62,7 @@ namespace Umbraco.Core.Models public static IProfile GetWriterProfile(this IContent content) { var repository = RepositoryResolver.Current.Factory.CreateUserRepository( - new PetaPocoUnitOfWork()); + new PetaPocoUnitOfWork(DatabaseContext.Current.Database)); return repository.GetProfileById(content.WriterId); } } diff --git a/src/Umbraco.Core/Persistence/DatabaseFactory.cs b/src/Umbraco.Core/Persistence/DatabaseFactory.cs index ded49f887c..28daf4967d 100644 --- a/src/Umbraco.Core/Persistence/DatabaseFactory.cs +++ b/src/Umbraco.Core/Persistence/DatabaseFactory.cs @@ -1,23 +1,27 @@ using System; +using System.Collections.Concurrent; +using System.Threading; +using System.Web; using Umbraco.Core.Configuration; namespace Umbraco.Core.Persistence { /// - /// Provides access to the PetaPoco database as Singleton, so the database is created once in app lifetime. - /// This is necessary for transactions to work properly. + /// Creates a database object for PetaPoco depending on the context. If we are running in an http context + /// it will create on per context, otherwise it will create a global singleton /// /// /// Because the Database is created static, the configuration has to be checked and set in /// another class, which is where the DatabaseContext comes in. /// - public sealed class DatabaseFactory + internal sealed class DatabaseFactory { + #region Singleton - private static readonly Database _database = new Database(GlobalSettings.UmbracoConnectionName); - private static readonly Lazy lazy = new Lazy(() => new DatabaseFactory()); - + private static readonly Lazy lazy = new Lazy(() => new DatabaseFactory()); + private static volatile Database _globalInstance = null; + private static readonly object Locker = new object(); public static DatabaseFactory Current { get { return lazy.Value; } } private DatabaseFactory() @@ -26,12 +30,41 @@ namespace Umbraco.Core.Persistence #endregion - /// - /// Returns an instance of the PetaPoco database - /// - public Database Database - { - get { return _database; } - } + /// + /// Returns an instance of the PetaPoco database + /// + /// + /// If we are running in an http context + /// it will create on per context, otherwise it will create transient objects (new instance each time) + /// + public Database Database + { + get + { + //no http context, create the singleton global object + if (HttpContext.Current == null) + { + if (_globalInstance == null) + { + lock(Locker) + { + //double check + if (_globalInstance == null) + { + _globalInstance = new Database(GlobalSettings.UmbracoConnectionName); + } + } + } + return _globalInstance; + } + + //we have an http context, so only create one per request + if (!HttpContext.Current.Items.Contains(typeof (DatabaseFactory))) + { + HttpContext.Current.Items.Add(typeof (DatabaseFactory), new Database(GlobalSettings.UmbracoConnectionName)); + } + return (Database) HttpContext.Current.Items[typeof (DatabaseFactory)]; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs index 8452f81ac9..ce2874e1af 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentRepository.cs @@ -20,14 +20,14 @@ namespace Umbraco.Core.Persistence.Repositories private readonly IContentTypeRepository _contentTypeRepository; private readonly ITemplateRepository _templateRepository; - public ContentRepository(IUnitOfWork work, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository) + public ContentRepository(IDatabaseUnitOfWork work, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository) : base(work) { _contentTypeRepository = contentTypeRepository; _templateRepository = templateRepository; } - public ContentRepository(IUnitOfWork work, IRepositoryCacheProvider cache, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository) + public ContentRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, IContentTypeRepository contentTypeRepository, ITemplateRepository templateRepository) : base(work, cache) { _contentTypeRepository = contentTypeRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs index 818dec4f49..0f7918b680 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeBaseRepository.cs @@ -22,11 +22,13 @@ namespace Umbraco.Core.Persistence.Repositories internal abstract class ContentTypeBaseRepository : PetaPocoRepositoryBase where TEntity : IContentTypeComposition { - protected ContentTypeBaseRepository(IUnitOfWork work) : base(work) + protected ContentTypeBaseRepository(IDatabaseUnitOfWork work) + : base(work) { } - protected ContentTypeBaseRepository(IUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + protected ContentTypeBaseRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) + : base(work, cache) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs index 6ae710f903..2984b5a43d 100644 --- a/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/ContentTypeRepository.cs @@ -18,13 +18,13 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly ITemplateRepository _templateRepository; - public ContentTypeRepository(IUnitOfWork work, ITemplateRepository templateRepository) + public ContentTypeRepository(IDatabaseUnitOfWork work, ITemplateRepository templateRepository) : base(work) { _templateRepository = templateRepository; } - public ContentTypeRepository(IUnitOfWork work, IRepositoryCacheProvider cache, ITemplateRepository templateRepository) + public ContentTypeRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, ITemplateRepository templateRepository) : base(work, cache) { _templateRepository = templateRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs index 0c7820a541..5ef72413be 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DataTypeDefinitionRepository.cs @@ -17,11 +17,12 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class DataTypeDefinitionRepository : PetaPocoRepositoryBase, IDataTypeDefinitionRepository { - public DataTypeDefinitionRepository(IUnitOfWork work) : base(work) + public DataTypeDefinitionRepository(IDatabaseUnitOfWork work) + : base(work) { } - public DataTypeDefinitionRepository(IUnitOfWork work, IRepositoryCacheProvider cache) + public DataTypeDefinitionRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs index b6b64716d6..1daf2cf2fe 100644 --- a/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/DictionaryRepository.cs @@ -19,12 +19,13 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly ILanguageRepository _languageRepository; - public DictionaryRepository(IUnitOfWork work, ILanguageRepository languageRepository) : base(work) + public DictionaryRepository(IDatabaseUnitOfWork work, ILanguageRepository languageRepository) + : base(work) { _languageRepository = languageRepository; } - public DictionaryRepository(IUnitOfWork work, IRepositoryCacheProvider cache, ILanguageRepository languageRepository) + public DictionaryRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, ILanguageRepository languageRepository) : base(work, cache) { _languageRepository = languageRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs index b1a28a74cd..7cf63b33ba 100644 --- a/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/FileRepository.cs @@ -8,7 +8,7 @@ using Umbraco.Core.Persistence.UnitOfWork; namespace Umbraco.Core.Persistence.Repositories { - internal abstract class FileRepository : IUnitOfWorkRepository, IRepository + internal abstract class FileRepository : DisposableObject, IUnitOfWorkRepository, IRepository where TEntity : IFile { private IUnitOfWork _work; @@ -110,5 +110,16 @@ namespace Umbraco.Core.Persistence.Repositories } #endregion + + /// + /// Dispose any disposable properties + /// + /// + /// Dispose the unit of work + /// + protected override void DisposeResources() + { + _work.DisposeIfDisposable(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs index 55c2381391..2a825d3449 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Interfaces/IRepository.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Umbraco.Core.Persistence.Repositories { @@ -9,7 +10,7 @@ namespace Umbraco.Core.Persistence.Repositories /// Currently this interface is empty but it is useful for flagging a repository without having generic parameters, it also might /// come in handy if we need to add anything to the base/non-generic repository interface. /// - public interface IRepository + public interface IRepository : IDisposable { } diff --git a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs index 3b85317be4..c215d08580 100644 --- a/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/LanguageRepository.cs @@ -16,11 +16,12 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class LanguageRepository : PetaPocoRepositoryBase, ILanguageRepository { - public LanguageRepository(IUnitOfWork work) : base(work) + public LanguageRepository(IDatabaseUnitOfWork work) + : base(work) { } - public LanguageRepository(IUnitOfWork work, IRepositoryCacheProvider cache) + public LanguageRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs index 51e8d2a6aa..72345c19e0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MacroRepository.cs @@ -20,12 +20,13 @@ namespace Umbraco.Core.Persistence.Repositories private IFileSystem _fileSystem; private SerializationService _serializationService; - public MacroRepository(IUnitOfWork work) : base(work) + public MacroRepository(IUnitOfWork work) + : base(work) { EnsureDependencies(); } - public MacroRepository(IUnitOfWork work, IRepositoryCacheProvider cache) + public MacroRepository(IUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) { EnsureDependencies(); diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs index 370f6bcf4a..c01e76c203 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaRepository.cs @@ -19,13 +19,13 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly IMediaTypeRepository _mediaTypeRepository; - public MediaRepository(IUnitOfWork work, IMediaTypeRepository mediaTypeRepository) + public MediaRepository(IDatabaseUnitOfWork work, IMediaTypeRepository mediaTypeRepository) : base(work) { _mediaTypeRepository = mediaTypeRepository; } - public MediaRepository(IUnitOfWork work, IRepositoryCacheProvider cache, IMediaTypeRepository mediaTypeRepository) + public MediaRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, IMediaTypeRepository mediaTypeRepository) : base(work, cache) { _mediaTypeRepository = mediaTypeRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs index 98e1c159d2..50477a679e 100644 --- a/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/MediaTypeRepository.cs @@ -16,12 +16,12 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class MediaTypeRepository : ContentTypeBaseRepository, IMediaTypeRepository { - public MediaTypeRepository(IUnitOfWork work) + public MediaTypeRepository(IDatabaseUnitOfWork work) : base(work) { } - public MediaTypeRepository(IUnitOfWork work, IRepositoryCacheProvider cache) + public MediaTypeRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs index a4a4526de0..6ccbe5d004 100644 --- a/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/PetaPocoRepositoryBase.cs @@ -15,17 +15,27 @@ namespace Umbraco.Core.Persistence.Repositories internal abstract class PetaPocoRepositoryBase : RepositoryBase where TEntity : IAggregateRoot { - protected PetaPocoRepositoryBase(IUnitOfWork work) : base(work) + protected PetaPocoRepositoryBase(IDatabaseUnitOfWork work) + : base(work) { } - protected PetaPocoRepositoryBase(IUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + protected PetaPocoRepositoryBase(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) + : base(work, cache) { } + /// + /// Returns the database Unit of Work added to the repository + /// + protected internal new IDatabaseUnitOfWork UnitOfWork + { + get { return (IDatabaseUnitOfWork)base.UnitOfWork; } + } + protected Database Database { - get { return DatabaseContext.Current.Database; } + get { return UnitOfWork.Database; } } #region Abstract Methods diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs index 1dd7bf60dd..89f5510708 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationRepository.cs @@ -18,12 +18,13 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly IRelationTypeRepository _relationTypeRepository; - public RelationRepository(IUnitOfWork work, IRelationTypeRepository relationTypeRepository) : base(work) + public RelationRepository(IDatabaseUnitOfWork work, IRelationTypeRepository relationTypeRepository) + : base(work) { _relationTypeRepository = relationTypeRepository; } - public RelationRepository(IUnitOfWork work, IRepositoryCacheProvider cache, IRelationTypeRepository relationTypeRepository) + public RelationRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, IRelationTypeRepository relationTypeRepository) : base(work, cache) { _relationTypeRepository = relationTypeRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs index d89219a41b..e30e992861 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RelationTypeRepository.cs @@ -15,11 +15,12 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class RelationTypeRepository : PetaPocoRepositoryBase, IRelationTypeRepository { - public RelationTypeRepository(IUnitOfWork work) : base(work) + public RelationTypeRepository(IDatabaseUnitOfWork work) + : base(work) { } - public RelationTypeRepository(IUnitOfWork work, IRepositoryCacheProvider cache) + public RelationTypeRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) { } diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 5206449af3..13282369ef 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -13,18 +13,18 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Type of entity for which the repository is used /// Type of the Id used for this entity - internal abstract class RepositoryBase : IRepositoryQueryable, - IUnitOfWorkRepository where TEntity : IAggregateRoot + internal abstract class RepositoryBase : DisposableObject, IRepositoryQueryable, IUnitOfWorkRepository + where TEntity : IAggregateRoot { - private IUnitOfWork _work; + private readonly IUnitOfWork _work; private readonly IRepositoryCacheProvider _cache; - protected RepositoryBase(IUnitOfWork work) + protected RepositoryBase(IUnitOfWork work) : this(work, RuntimeCacheProvider.Current) { } - internal RepositoryBase(IUnitOfWork work, IRepositoryCacheProvider cache) + internal RepositoryBase(IUnitOfWork work, IRepositoryCacheProvider cache) { _work = work; _cache = cache; @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence.Repositories /// /// Returns the Unit of Work added to the repository /// - protected IUnitOfWork UnitOfWork + protected internal IUnitOfWork UnitOfWork { get { return _work; } } @@ -181,16 +181,7 @@ namespace Umbraco.Core.Persistence.Repositories public int Count(IQuery query) { return PerformCount(query); - } - - /// - /// Sets the repository's Unit Of Work with the passed in - /// - /// - public void SetUnitOfWork(IUnitOfWork work) - { - _work = work; - } + } #endregion @@ -259,5 +250,16 @@ namespace Umbraco.Core.Persistence.Repositories { return id.EncodeAsGuid(); } + + /// + /// Dispose disposable properties + /// + /// + /// Ensure the unit of work is disposed + /// + protected override void DisposeResources() + { + UnitOfWork.DisposeIfDisposable(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs index 65a5073f02..fb8307d940 100644 --- a/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/TemplateRepository.cs @@ -23,17 +23,20 @@ namespace Umbraco.Core.Persistence.Repositories private IFileSystem _masterpagesFileSystem; private IFileSystem _viewsFileSystem; - public TemplateRepository(IUnitOfWork work) : base(work) + public TemplateRepository(IDatabaseUnitOfWork work) + : base(work) { EnsureDepedencies(); } - public TemplateRepository(IUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + public TemplateRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) + : base(work, cache) { EnsureDepedencies(); } - internal TemplateRepository(IUnitOfWork work, IRepositoryCacheProvider cache, IFileSystem masterpageFileSystem, IFileSystem viewFileSystem) : base(work, cache) + internal TemplateRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, IFileSystem masterpageFileSystem, IFileSystem viewFileSystem) + : base(work, cache) { _masterpagesFileSystem = masterpageFileSystem; _viewsFileSystem = viewFileSystem; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs index ad3d7aefb9..bfe896e6b5 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserRepository.cs @@ -17,13 +17,13 @@ namespace Umbraco.Core.Persistence.Repositories { private readonly IUserTypeRepository _userTypeRepository; - public UserRepository(IUnitOfWork work, IUserTypeRepository userTypeRepository) + public UserRepository(IDatabaseUnitOfWork work, IUserTypeRepository userTypeRepository) : base(work) { _userTypeRepository = userTypeRepository; } - public UserRepository(IUnitOfWork work, IRepositoryCacheProvider cache, IUserTypeRepository userTypeRepository) + public UserRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache, IUserTypeRepository userTypeRepository) : base(work, cache) { _userTypeRepository = userTypeRepository; diff --git a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs b/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs index 2e8cf45137..3618ef89b0 100644 --- a/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/UserTypeRepository.cs @@ -15,11 +15,13 @@ namespace Umbraco.Core.Persistence.Repositories /// internal class UserTypeRepository : PetaPocoRepositoryBase, IUserTypeRepository { - public UserTypeRepository(IUnitOfWork work) : base(work) + public UserTypeRepository(IDatabaseUnitOfWork work) + : base(work) { } - public UserTypeRepository(IUnitOfWork work, IRepositoryCacheProvider cache) : base(work, cache) + public UserTypeRepository(IDatabaseUnitOfWork work, IRepositoryCacheProvider cache) + : base(work, cache) { } diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 5862025fd4..085a5cd87b 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -9,14 +9,14 @@ namespace Umbraco.Core.Persistence /// public class RepositoryFactory { - internal virtual IUserTypeRepository CreateUserTypeRepository(IUnitOfWork uow) + internal virtual IUserTypeRepository CreateUserTypeRepository(IDatabaseUnitOfWork uow) { return new UserTypeRepository( uow, NullCacheProvider.Current); } - internal virtual IUserRepository CreateUserRepository(IUnitOfWork uow) + internal virtual IUserRepository CreateUserRepository(IDatabaseUnitOfWork uow) { return new UserRepository( uow, @@ -24,7 +24,7 @@ namespace Umbraco.Core.Persistence CreateUserTypeRepository(uow)); } - public virtual IContentRepository CreateContentRepository(IUnitOfWork uow) + public virtual IContentRepository CreateContentRepository(IDatabaseUnitOfWork uow) { return new ContentRepository( uow, @@ -33,7 +33,7 @@ namespace Umbraco.Core.Persistence CreateTemplateRepository(uow)); } - public virtual IContentTypeRepository CreateContentTypeRepository(IUnitOfWork uow) + public virtual IContentTypeRepository CreateContentTypeRepository(IDatabaseUnitOfWork uow) { return new ContentTypeRepository( uow, @@ -41,14 +41,14 @@ namespace Umbraco.Core.Persistence new TemplateRepository(uow, NullCacheProvider.Current)); } - public virtual IDataTypeDefinitionRepository CreateDataTypeDefinitionRepository(IUnitOfWork uow) + public virtual IDataTypeDefinitionRepository CreateDataTypeDefinitionRepository(IDatabaseUnitOfWork uow) { return new DataTypeDefinitionRepository( uow, NullCacheProvider.Current); } - public virtual IDictionaryRepository CreateDictionaryRepository(IUnitOfWork uow) + public virtual IDictionaryRepository CreateDictionaryRepository(IDatabaseUnitOfWork uow) { return new DictionaryRepository( uow, @@ -56,21 +56,21 @@ namespace Umbraco.Core.Persistence CreateLanguageRepository(uow)); } - public virtual ILanguageRepository CreateLanguageRepository(IUnitOfWork uow) + public virtual ILanguageRepository CreateLanguageRepository(IDatabaseUnitOfWork uow) { return new LanguageRepository( uow, InMemoryCacheProvider.Current); } - internal virtual IMacroRepository CreateMacroRepository(IUnitOfWork uow) + internal virtual IMacroRepository CreateMacroRepository(IUnitOfWork uow) { return new MacroRepository( uow, InMemoryCacheProvider.Current); } - public virtual IMediaRepository CreateMediaRepository(IUnitOfWork uow) + public virtual IMediaRepository CreateMediaRepository(IDatabaseUnitOfWork uow) { return new MediaRepository( uow, @@ -78,14 +78,14 @@ namespace Umbraco.Core.Persistence CreateMediaTypeRepository(uow)); } - public virtual IMediaTypeRepository CreateMediaTypeRepository(IUnitOfWork uow) + public virtual IMediaTypeRepository CreateMediaTypeRepository(IDatabaseUnitOfWork uow) { return new MediaTypeRepository( uow, InMemoryCacheProvider.Current); } - public virtual IRelationRepository CreateRelationRepository(IUnitOfWork uow) + public virtual IRelationRepository CreateRelationRepository(IDatabaseUnitOfWork uow) { return new RelationRepository( uow, @@ -93,7 +93,7 @@ namespace Umbraco.Core.Persistence CreateRelationTypeRepository(uow)); } - public virtual IRelationTypeRepository CreateRelationTypeRepository(IUnitOfWork uow) + public virtual IRelationTypeRepository CreateRelationTypeRepository(IDatabaseUnitOfWork uow) { return new RelationTypeRepository( uow, @@ -110,7 +110,7 @@ namespace Umbraco.Core.Persistence return new StylesheetRepository(uow); } - public virtual ITemplateRepository CreateTemplateRepository(IUnitOfWork uow) + public virtual ITemplateRepository CreateTemplateRepository(IDatabaseUnitOfWork uow) { return new TemplateRepository(uow, NullCacheProvider.Current); } diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWork.cs index 5e441fe242..7c15df55af 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/FileUnitOfWork.cs @@ -140,5 +140,6 @@ namespace Umbraco.Core.Persistence.UnitOfWork } #endregion + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWork.cs new file mode 100644 index 0000000000..8653733677 --- /dev/null +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWork.cs @@ -0,0 +1,12 @@ +using System; + +namespace Umbraco.Core.Persistence.UnitOfWork +{ + /// + /// Defines a unit of work when working with a database object + /// + public interface IDatabaseUnitOfWork : IUnitOfWork, IDisposable + { + Database Database { get; } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWorkProvider.cs new file mode 100644 index 0000000000..c18c08d8bb --- /dev/null +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IDatabaseUnitOfWorkProvider.cs @@ -0,0 +1,10 @@ +namespace Umbraco.Core.Persistence.UnitOfWork +{ + /// + /// Defines a Unit of Work Provider for working with an IDatabaseUnitOfWork + /// + public interface IDatabaseUnitOfWorkProvider + { + IDatabaseUnitOfWork GetUnitOfWork(); + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs index 809c2340e4..13d2bb8be9 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWork.cs @@ -1,12 +1,13 @@ -using Umbraco.Core.Models.EntityBase; +using System; +using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Persistence.UnitOfWork { - /// + /// /// Defines a Unit Of Work /// public interface IUnitOfWork - { + { void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository); void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository); void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository); diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs index 7b87d9a2ee..006bda0820 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/IUnitOfWorkProvider.cs @@ -1,6 +1,6 @@ namespace Umbraco.Core.Persistence.UnitOfWork { - /// + /// /// Defines a Unit of Work Provider /// public interface IUnitOfWorkProvider diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs index 6266053390..896ac82d0f 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs @@ -8,17 +8,26 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// /// Represents the Unit of Work implementation for PetaPoco /// - internal class PetaPocoUnitOfWork : IUnitOfWork + internal class PetaPocoUnitOfWork : DisposableObject, IDatabaseUnitOfWork { + + /// + /// Used for testing + /// + internal Guid InstanceId { get; private set; } + private Guid _key; private readonly List _operations = new List(); - public PetaPocoUnitOfWork() - { - _key = Guid.NewGuid(); - } - /// + public PetaPocoUnitOfWork(Database database) + { + Database = database; + _key = Guid.NewGuid(); + InstanceId = Guid.NewGuid(); + } + + /// /// Registers an instance to be added through this /// /// The @@ -74,7 +83,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// public void Commit() { - using(Transaction transaction = DatabaseFactory.Current.Database.GetTransaction()) + using(Transaction transaction = Database.GetTransaction()) { foreach (var operation in _operations.OrderBy(o => o.ProcessDate)) { @@ -104,6 +113,8 @@ namespace Umbraco.Core.Persistence.UnitOfWork get { return _key; } } + public Database Database { get; private set; } + #region Operation /// @@ -137,5 +148,19 @@ namespace Umbraco.Core.Persistence.UnitOfWork } #endregion + + /// + /// Ensures disposable objects are disposed + /// + /// + /// We will not dispose the database because this will get disposed of automatically when + /// in the HttpContext by the UmbracoModule because the DatabaseFactory stores the instance in HttpContext.Items + /// when in a web context. + /// When not in a web context, we may possibly be re-using the database context. + /// + protected override void DisposeResources() + { + _operations.Clear(); + } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs index 647e020b1a..ee46f56745 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWorkProvider.cs @@ -1,15 +1,21 @@ -namespace Umbraco.Core.Persistence.UnitOfWork +using System; +using System.Threading; +using System.Web; +using Umbraco.Core.Configuration; + +namespace Umbraco.Core.Persistence.UnitOfWork { /// /// Represents a Unit of Work Provider for creating a /// - internal class PetaPocoUnitOfWorkProvider : IUnitOfWorkProvider + internal class PetaPocoUnitOfWorkProvider : IDatabaseUnitOfWorkProvider { - #region Implementation of IUnitOfWorkProvider + + #region Implementation of IUnitOfWorkProvider - public IUnitOfWork GetUnitOfWork() + public IDatabaseUnitOfWork GetUnitOfWork() { - return new PetaPocoUnitOfWork(); + return new PetaPocoUnitOfWork(DatabaseFactory.Current.Database); } #endregion diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 3104bec51f..04fe684939 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -18,28 +18,22 @@ namespace Umbraco.Core.Services /// public class ContentService : IContentService { - private readonly IPublishingStrategy _publishingStrategy; - private readonly IUserService _userService; - private readonly IUnitOfWork _unitOfWork; + private readonly IDatabaseUnitOfWorkProvider _uowProvider; + private readonly IPublishingStrategy _publishingStrategy; + private readonly IUserService _userService; private HttpContextBase _httpContext; - private readonly IContentRepository _contentRepository; - private readonly IContentTypeRepository _contentTypeRepository; - - public ContentService(IUnitOfWorkProvider provider, IPublishingStrategy publishingStrategy) + + public ContentService(IDatabaseUnitOfWorkProvider provider, IPublishingStrategy publishingStrategy) { - _publishingStrategy = publishingStrategy; - _unitOfWork = provider.GetUnitOfWork(); - _contentRepository = RepositoryResolver.Current.Factory.CreateContentRepository(_unitOfWork); - _contentTypeRepository = RepositoryResolver.Current.Factory.CreateContentTypeRepository(_unitOfWork); + _uowProvider = provider; + _publishingStrategy = publishingStrategy; } - internal ContentService(IUnitOfWorkProvider provider, IPublishingStrategy publishingStrategy, IUserService userService) + internal ContentService(IDatabaseUnitOfWorkProvider provider, IPublishingStrategy publishingStrategy, IUserService userService) { _publishingStrategy = publishingStrategy; _userService = userService; - _unitOfWork = provider.GetUnitOfWork(); - _contentRepository = RepositoryResolver.Current.Factory.CreateContentRepository(_unitOfWork); - _contentTypeRepository = RepositoryResolver.Current.Factory.CreateContentTypeRepository(_unitOfWork); + _uowProvider = provider; } //TODO Add GetLatestUnpublishedVersions(int id){} @@ -54,7 +48,8 @@ namespace Umbraco.Core.Services /// public IContent CreateContent(int parentId, string contentTypeAlias, int userId = -1) { - var repository = _contentTypeRepository; + var uow = _uowProvider.GetUnitOfWork(); + var repository = RepositoryResolver.Current.Factory.CreateContentTypeRepository(uow); var query = Query.Builder.Where(x => x.Alias == contentTypeAlias); var contentTypes = repository.GetByQuery(query); @@ -92,8 +87,11 @@ namespace Umbraco.Core.Services /// public IContent GetById(int id) { - var repository = _contentRepository; - return repository.Get(id); + using(var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + return repository.Get(id); + } + } /// @@ -103,10 +101,12 @@ namespace Umbraco.Core.Services /// public IContent GetById(Guid key) { - var repository = _contentRepository; - var query = Query.Builder.Where(x => x.Key == key); - var contents = repository.GetByQuery(query); - return contents.SingleOrDefault(); + using(var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Key == key); + var contents = repository.GetByQuery(query); + return contents.SingleOrDefault(); + } } @@ -117,12 +117,13 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentOfContentType(int id) { - var repository = _contentRepository; + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ContentTypeId == id); + var contents = repository.GetByQuery(query); - var query = Query.Builder.Where(x => x.ContentTypeId == id); - var contents = repository.GetByQuery(query); - - return contents; + return contents; + } } /// @@ -132,12 +133,13 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetByLevel(int level) { - var repository = _contentRepository; + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Level == level); + var contents = repository.GetByQuery(query); - var query = Query.Builder.Where(x => x.Level == level); - var contents = repository.GetByQuery(query); - - return contents; + return contents; + } } /// @@ -147,12 +149,13 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetChildren(int id) { - var repository = _contentRepository; + using(var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == id); + var contents = repository.GetByQuery(query); - var query = Query.Builder.Where(x => x.ParentId == id); - var contents = repository.GetByQuery(query); - - return contents; + return contents; + } } /// @@ -162,9 +165,12 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetVersions(int id) { - var repository = _contentRepository; - var versions = repository.GetAllVersions(id); - return versions; + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var versions = repository.GetAllVersions(id); + return versions; + } + } /// @@ -173,12 +179,13 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetRootContent() { - var repository = _contentRepository; + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == -1); + var contents = repository.GetByQuery(query); - var query = Query.Builder.Where(x => x.ParentId == -1); - var contents = repository.GetByQuery(query); - - return contents; + return contents; + } } /// @@ -187,12 +194,13 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentForExpiration() { - var repository = _contentRepository; + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Published == true && x.ExpireDate <= DateTime.UtcNow); + var contents = repository.GetByQuery(query); - var query = Query.Builder.Where(x => x.Published == true && x.ExpireDate <= DateTime.UtcNow); - var contents = repository.GetByQuery(query); - - return contents; + return contents; + } } /// @@ -201,12 +209,13 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentForRelease() { - var repository = _contentRepository; + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.UtcNow); + var contents = repository.GetByQuery(query); - var query = Query.Builder.Where(x => x.Published == false && x.ReleaseDate <= DateTime.UtcNow); - var contents = repository.GetByQuery(query); - - return contents; + return contents; + } } /// @@ -215,12 +224,13 @@ namespace Umbraco.Core.Services /// An Enumerable list of objects public IEnumerable GetContentInRecycleBin() { - var repository = _contentRepository; + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + var query = Query.Builder.Where(x => x.ParentId == -20); + var contents = repository.GetByQuery(query); - var query = Query.Builder.Where(x => x.ParentId == -20); - var contents = repository.GetByQuery(query); - - return contents; + return contents; + } } /// @@ -230,41 +240,43 @@ namespace Umbraco.Core.Services /// True if publishing succeeded, otherwise False public bool RePublishAll(int userId = -1) { - var repository = _contentRepository; - - var list = new List(); - - //Consider creating a Path query instead of recursive method: - //var query = Query.Builder.Where(x => x.Path.StartsWith("-1")); - - var rootContent = GetRootContent(); - foreach (var content in rootContent) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) { - if(content.IsValid()) - { - list.Add(content); - list.AddRange(GetChildrenDeep(content.Id)); - } + var list = new List(); + + //Consider creating a Path query instead of recursive method: + //var query = Query.Builder.Where(x => x.Path.StartsWith("-1")); + + var rootContent = GetRootContent(); + foreach (var content in rootContent) + { + if (content.IsValid()) + { + list.Add(content); + list.AddRange(GetChildrenDeep(content.Id)); + } + } + + //Publish and then update the database with new status + var published = _publishingStrategy.PublishWithChildren(list, userId); + if (published) + { + //Only loop through content where the Published property has been updated + foreach (var item in list.Where(x => ((ICanBeDirty)x).IsPropertyDirty("Published"))) + { + SetWriter(item, userId); + repository.AddOrUpdate(item); + } + + uow.Commit(); + + //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content (singleton) class. + //global::umbraco.library.RefreshContent(); + } + + return published; } - - //Publish and then update the database with new status - var published = _publishingStrategy.PublishWithChildren(list, userId); - if (published) - { - //Only loop through content where the Published property has been updated - foreach (var item in list.Where(x => ((ICanBeDirty)x).IsPropertyDirty("Published"))) - { - SetWriter(item, userId); - repository.AddOrUpdate(item); - } - - _unitOfWork.Commit(); - - //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content (singleton) class. - //global::umbraco.library.RefreshContent(); - } - - return published; } /// @@ -286,52 +298,54 @@ namespace Umbraco.Core.Services /// True if publishing succeeded, otherwise False public bool PublishWithChildren(IContent content, int userId = -1) { - var repository = _contentRepository; - - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != -1 && content.ParentId != -20 && !GetById(content.ParentId).Published) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) { - LogHelper.Info( - string.Format("Content '{0}' with Id '{1}' could not be published because its parent is not published.", - content.Name, content.Id)); - return false; - } + //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published + if (content.ParentId != -1 && content.ParentId != -20 && !GetById(content.ParentId).Published) + { + LogHelper.Info( + string.Format("Content '{0}' with Id '{1}' could not be published because its parent is not published.", + content.Name, content.Id)); + return false; + } - //Content contains invalid property values and can therefore not be published - fire event? - if (!content.IsValid()) - { - LogHelper.Info( - string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.", - content.Name, content.Id)); - return false; - } + //Content contains invalid property values and can therefore not be published - fire event? + if (!content.IsValid()) + { + LogHelper.Info( + string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.", + content.Name, content.Id)); + return false; + } - //Consider creating a Path query instead of recursive method: - //var query = Query.Builder.Where(x => x.Path.StartsWith(content.Path)); + //Consider creating a Path query instead of recursive method: + //var query = Query.Builder.Where(x => x.Path.StartsWith(content.Path)); - var list = new List(); - list.Add(content); - list.AddRange(GetChildrenDeep(content.Id)); + var list = new List(); + list.Add(content); + list.AddRange(GetChildrenDeep(content.Id)); - //Publish and then update the database with new status - var published = _publishingStrategy.PublishWithChildren(list, userId); - if (published) - { - //Only loop through content where the Published property has been updated - foreach (var item in list.Where(x => ((ICanBeDirty)x).IsPropertyDirty("Published"))) - { - SetWriter(item, userId); - repository.AddOrUpdate(item); - } + //Publish and then update the database with new status + var published = _publishingStrategy.PublishWithChildren(list, userId); + if (published) + { + //Only loop through content where the Published property has been updated + foreach (var item in list.Where(x => ((ICanBeDirty)x).IsPropertyDirty("Published"))) + { + SetWriter(item, userId); + repository.AddOrUpdate(item); + } - _unitOfWork.Commit(); + uow.Commit(); - //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content (singleton) class. - //TODO Need to investigate if it will also update the cache for children of the Content object - //global::umbraco.library.UpdateDocumentCache(content.Id); - } + //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content (singleton) class. + //TODO Need to investigate if it will also update the cache for children of the Content object + //global::umbraco.library.UpdateDocumentCache(content.Id); + } - return published; + return published; + } } /// @@ -342,39 +356,41 @@ namespace Umbraco.Core.Services /// True if unpublishing succeeded, otherwise False public bool UnPublish(IContent content, int userId = -1) { - var repository = _contentRepository; - - //Look for children and unpublish them if any exists, otherwise just unpublish the passed in Content. - var children = GetChildrenDeep(content.Id); - var hasChildren = children.Any(); - - if(hasChildren) - children.Add(content); - - var unpublished = hasChildren - ? _publishingStrategy.UnPublish(children, userId) - : _publishingStrategy.UnPublish(content, userId); - - if (unpublished) + var uow = _uowProvider.GetUnitOfWork(); + using(var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) { - repository.AddOrUpdate(content); + //Look for children and unpublish them if any exists, otherwise just unpublish the passed in Content. + var children = GetChildrenDeep(content.Id); + var hasChildren = children.Any(); - if (hasChildren) - { - foreach (var child in children) - { - SetWriter(child, userId); - repository.AddOrUpdate(child); - } - } + if (hasChildren) + children.Add(content); - _unitOfWork.Commit(); + var unpublished = hasChildren + ? _publishingStrategy.UnPublish(children, userId) + : _publishingStrategy.UnPublish(content, userId); - //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content class. - //global::umbraco.library.UnPublishSingleNode(content.Id); + if (unpublished) + { + repository.AddOrUpdate(content); + + if (hasChildren) + { + foreach (var child in children) + { + SetWriter(child, userId); + repository.AddOrUpdate(child); + } + } + + uow.Commit(); + + //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content class. + //global::umbraco.library.UnPublishSingleNode(content.Id); + } + + return unpublished; } - - return unpublished; } /// @@ -410,54 +426,58 @@ namespace Umbraco.Core.Services /// Optional Id of the User issueing the publishing /// True if publishing succeeded, otherwise False public bool SaveAndPublish(IContent content, int userId = -1) - { - var e = new SaveEventArgs(); - if (Saving != null) - Saving(content, e); + { + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) + { + var e = new SaveEventArgs(uow); + if (Saving != null) + Saving(content, e); - if (!e.Cancel) - { - var repository = _contentRepository; + if (!e.Cancel) + { - //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published - if (content.ParentId != -1 && content.ParentId != -20 && GetById(content.ParentId).Published == false) - { - LogHelper.Info( - string.Format( - "Content '{0}' with Id '{1}' could not be published because its parent is not published.", - content.Name, content.Id)); - return false; - } + //Check if parent is published (although not if its a root node) - if parent isn't published this Content cannot be published + if (content.ParentId != -1 && content.ParentId != -20 && GetById(content.ParentId).Published == false) + { + LogHelper.Info( + string.Format( + "Content '{0}' with Id '{1}' could not be published because its parent is not published.", + content.Name, content.Id)); + return false; + } - //Content contains invalid property values and can therefore not be published - fire event? - if (!content.IsValid()) - { - LogHelper.Info( - string.Format( - "Content '{0}' with Id '{1}' could not be published because of invalid properties.", - content.Name, content.Id)); - return false; - } + //Content contains invalid property values and can therefore not be published - fire event? + if (!content.IsValid()) + { + LogHelper.Info( + string.Format( + "Content '{0}' with Id '{1}' could not be published because of invalid properties.", + content.Name, content.Id)); + return false; + } - //Publish and then update the database with new status - bool published = _publishingStrategy.Publish(content, userId); - if (published) - { - SetWriter(content, userId); - repository.AddOrUpdate(content); - _unitOfWork.Commit(); + //Publish and then update the database with new status + bool published = _publishingStrategy.Publish(content, userId); + if (published) + { + SetWriter(content, userId); + repository.AddOrUpdate(content); + uow.Commit(); - //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content (singleton) class. - //global::umbraco.library.UpdateDocumentCache(content.Id); - } + //TODO Change this so we can avoid a depencency to the horrible library method / umbraco.content (singleton) class. + //global::umbraco.library.UpdateDocumentCache(content.Id); + } - if (Saved != null) - Saved(content, e); + if (Saved != null) + Saved(content, e); - return published; - } + return published; + } - return false; + + return false; + } } /// @@ -467,22 +487,25 @@ namespace Umbraco.Core.Services /// Optional Id of the User saving the Content public void Save(IContent content, int userId = -1) { - var e = new SaveEventArgs(); - if (Saving != null) - Saving(content, e); + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) + { + var e = new SaveEventArgs(uow); + if (Saving != null) + Saving(content, e); - if (!e.Cancel) - { - var repository = _contentRepository; + if (!e.Cancel) + { - SetWriter(content, userId); - content.ChangePublishedState(false); - repository.AddOrUpdate(content); - _unitOfWork.Commit(); + SetWriter(content, userId); + content.ChangePublishedState(false); + repository.AddOrUpdate(content); + uow.Commit(); - if (Saved != null) - Saved(content, e); - } + if (Saved != null) + Saved(content, e); + } + } } /// @@ -496,41 +519,44 @@ namespace Umbraco.Core.Services /// Optional Id of the User saving the Content public void Save(IEnumerable contents, int userId = -1) { - var repository = _contentRepository; - var containsNew = contents.Any(x => x.HasIdentity == false); - - var e = new SaveEventArgs(); - if (Saving != null) - Saving(contents, e); - - if (!e.Cancel) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) { - if (containsNew) - { - foreach (var content in contents) - { - SetWriter(content, userId); - content.ChangePublishedState(false); - repository.AddOrUpdate(content); - _unitOfWork.Commit(); - } - } - else - { - foreach (var content in contents) - { - if (Saving != null) - Saving(content, e); + var containsNew = contents.Any(x => x.HasIdentity == false); - SetWriter(content, userId); - repository.AddOrUpdate(content); - } - _unitOfWork.Commit(); - } + var e = new SaveEventArgs(uow); + if (Saving != null) + Saving(contents, e); - if (Saved != null) - Saved(contents, e); - } + if (!e.Cancel) + { + if (containsNew) + { + foreach (var content in contents) + { + SetWriter(content, userId); + content.ChangePublishedState(false); + repository.AddOrUpdate(content); + uow.Commit(); + } + } + else + { + foreach (var content in contents) + { + if (Saving != null) + Saving(content, e); + + SetWriter(content, userId); + repository.AddOrUpdate(content); + } + uow.Commit(); + } + + if (Saved != null) + Saved(contents, e); + } + } } /// @@ -544,24 +570,26 @@ namespace Umbraco.Core.Services /// Optional Id of the User saving the Content public void Save(IEnumerable> contents, int userId = -1) { - var repository = _contentRepository; + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) + { + var e = new SaveEventArgs(uow); + if (Saving != null) + Saving(contents, e); - var e = new SaveEventArgs(); - if (Saving != null) - Saving(contents, e); - - if (!e.Cancel) - { - foreach (var content in contents) - { - SetWriter(content.Value, userId); - content.Value.ChangePublishedState(false); - repository.AddOrUpdate(content.Value); - _unitOfWork.Commit(); - } - if (Saved != null) - Saved(contents, e); - } + if (!e.Cancel) + { + foreach (var content in contents) + { + SetWriter(content.Value, userId); + content.Value.ChangePublishedState(false); + repository.AddOrUpdate(content.Value); + uow.Commit(); + } + if (Saved != null) + Saved(contents, e); + } + } } /// @@ -571,29 +599,31 @@ namespace Umbraco.Core.Services /// Id of the public void DeleteContentOfType(int contentTypeId) { - var repository = _contentRepository; - - //NOTE What about content that has the contenttype as part of its composition? - var query = Query.Builder.Where(x => x.ContentTypeId == contentTypeId); - var contents = repository.GetByQuery(query); - - var e = new DeleteEventArgs { Id = contentTypeId }; - if (Deleting != null) - Deleting(contents, e); - - if (!e.Cancel) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) { - foreach (var content in contents) - { - ((Content) content).ChangeTrashedState(true); - repository.AddOrUpdate(content); - } + //NOTE What about content that has the contenttype as part of its composition? + var query = Query.Builder.Where(x => x.ContentTypeId == contentTypeId); + var contents = repository.GetByQuery(query); - _unitOfWork.Commit(); + var e = new DeleteEventArgs { Id = contentTypeId }; + if (Deleting != null) + Deleting(contents, e); - if (Deleted != null) - Deleted(contents, e); - } + if (!e.Cancel) + { + foreach (var content in contents) + { + ((Content)content).ChangeTrashedState(true); + repository.AddOrUpdate(content); + } + + uow.Commit(); + + if (Deleted != null) + Deleted(contents, e); + } + } } /// @@ -614,13 +644,17 @@ namespace Umbraco.Core.Services if (!e.Cancel) { - var repository = _contentRepository; - SetWriter(content, userId); - repository.Delete(content); - _unitOfWork.Commit(); + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) + { + SetWriter(content, userId); + repository.Delete(content); + uow.Commit(); - if (Deleted != null) - Deleted(content, e); + if (Deleted != null) + Deleted(content, e); + } + } } @@ -661,11 +695,14 @@ namespace Umbraco.Core.Services if (!e.Cancel) { - var repository = _contentRepository; - repository.Delete(id, versionDate); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) + { + repository.Delete(id, versionDate); - if (Deleted != null) - Deleted(versionDate, e); + if (Deleted != null) + Deleted(versionDate, e); + } + } } @@ -678,25 +715,26 @@ namespace Umbraco.Core.Services /// Optional Id of the User deleting versions of a Content object public void Delete(int id, Guid versionId, bool deletePriorVersions, int userId = -1) { - var repository = _contentRepository; - - if(deletePriorVersions) + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(_uowProvider.GetUnitOfWork())) { - var content = repository.GetByVersion(id, versionId); - Delete(id, content.UpdateDate, userId); - } + if (deletePriorVersions) + { + var content = repository.GetByVersion(id, versionId); + Delete(id, content.UpdateDate, userId); + } - var e = new DeleteEventArgs {Id = id}; - if (Deleting != null) - Deleting(versionId, e); + var e = new DeleteEventArgs { Id = id }; + if (Deleting != null) + Deleting(versionId, e); - if (!e.Cancel) - { - repository.Delete(id, versionId); + if (!e.Cancel) + { + repository.Delete(id, versionId); - if (Deleted != null) - Deleted(versionId, e); - } + if (Deleted != null) + Deleted(versionId, e); + } + } } /// @@ -709,11 +747,15 @@ namespace Umbraco.Core.Services { //TODO If content item has children those should also be moved to the recycle bin //TODO Unpublish deleted content + children - var repository = _contentRepository; - SetWriter(content, userId); - content.ChangeTrashedState(true); - repository.AddOrUpdate(content); - _unitOfWork.Commit(); + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) + { + SetWriter(content, userId); + content.ChangeTrashedState(true); + repository.AddOrUpdate(content); + uow.Commit(); + } + } /// @@ -767,16 +809,18 @@ namespace Umbraco.Core.Services /// public void EmptyRecycleBin() { - var repository = _contentRepository; - - var query = Query.Builder.Where(x => x.ParentId == -20); - var contents = repository.GetByQuery(query); - - foreach (var content in contents) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) { - repository.Delete(content); - } - _unitOfWork.Commit(); + var query = Query.Builder.Where(x => x.ParentId == -20); + var contents = repository.GetByQuery(query); + + foreach (var content in contents) + { + repository.Delete(content); + } + uow.Commit(); + } } /// @@ -801,12 +845,14 @@ namespace Umbraco.Core.Services copy.ParentId = parentId; copy.Name = copy.Name + " (1)"; - var repository = _contentRepository; + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) + { + SetWriter(content, userId); - SetWriter(content, userId); - - repository.AddOrUpdate(copy); - _unitOfWork.Commit(); + repository.AddOrUpdate(copy); + uow.Commit(); + } } if(Copied != null) @@ -867,25 +913,28 @@ namespace Umbraco.Core.Services { var e = new RollbackEventArgs(); - var repository = _contentRepository; - var content = repository.GetByVersion(id, versionId); - - if (Rollingback != null) - Rollingback(content, e); - - if (!e.Cancel) + var uow = _uowProvider.GetUnitOfWork(); + using (var repository = RepositoryResolver.Current.Factory.CreateContentRepository(uow)) { - SetUser(content, userId); - SetWriter(content, userId); + var content = repository.GetByVersion(id, versionId); - repository.AddOrUpdate(content); - _unitOfWork.Commit(); + if (Rollingback != null) + Rollingback(content, e); - if (Rolledback != null) - Rolledback(content, e); + if (!e.Cancel) + { + SetUser(content, userId); + SetWriter(content, userId); + + repository.AddOrUpdate(content); + uow.Commit(); + + if (Rolledback != null) + Rolledback(content, e); + } + + return content; } - - return content; } /// diff --git a/src/Umbraco.Core/Services/ContentTypeService.cs b/src/Umbraco.Core/Services/ContentTypeService.cs index 77c728d37b..efd63bb503 100644 --- a/src/Umbraco.Core/Services/ContentTypeService.cs +++ b/src/Umbraco.Core/Services/ContentTypeService.cs @@ -19,7 +19,7 @@ namespace Umbraco.Core.Services { private readonly IContentService _contentService; private readonly IMediaService _mediaService; - private readonly IUnitOfWork _unitOfWork; + private readonly IDatabaseUnitOfWork _unitOfWork; private readonly IContentTypeRepository _contentTypeRepository; private readonly IMediaTypeRepository _mediaTypeRepository; @@ -27,7 +27,7 @@ namespace Umbraco.Core.Services : this(contentService, mediaService, new PetaPocoUnitOfWorkProvider()) {} - public ContentTypeService(IContentService contentService, IMediaService mediaService, IUnitOfWorkProvider provider) + public ContentTypeService(IContentService contentService, IMediaService mediaService, IDatabaseUnitOfWorkProvider provider) { _contentService = contentService; _mediaService = mediaService; diff --git a/src/Umbraco.Core/Services/DataTypeService.cs b/src/Umbraco.Core/Services/DataTypeService.cs index cf9f68896e..119eb8411d 100644 --- a/src/Umbraco.Core/Services/DataTypeService.cs +++ b/src/Umbraco.Core/Services/DataTypeService.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Services /// public class DataTypeService : IDataTypeService { - private readonly IUnitOfWork _unitOfWork; + private readonly IDatabaseUnitOfWork _unitOfWork; private readonly IDataTypeDefinitionRepository _dataTypeService; private readonly IContentTypeRepository _contentTypeRepository; @@ -23,7 +23,7 @@ namespace Umbraco.Core.Services { } - public DataTypeService(IUnitOfWorkProvider provider) + public DataTypeService(IDatabaseUnitOfWorkProvider provider) { _unitOfWork = provider.GetUnitOfWork(); _dataTypeService = RepositoryResolver.Current.Factory.CreateDataTypeDefinitionRepository(_unitOfWork); diff --git a/src/Umbraco.Core/Services/FileService.cs b/src/Umbraco.Core/Services/FileService.cs index 9695db5c11..131b987df9 100644 --- a/src/Umbraco.Core/Services/FileService.cs +++ b/src/Umbraco.Core/Services/FileService.cs @@ -12,7 +12,7 @@ namespace Umbraco.Core.Services public class FileService : IFileService { private readonly IUnitOfWork _fileUnitOfWork; - private readonly IUnitOfWork _dataUnitOfWork; + private readonly IDatabaseUnitOfWork _dataUnitOfWork; private readonly IStylesheetRepository _stylesheetRepository; private readonly IScriptRepository _scriptRepository; private readonly ITemplateRepository _templateRepository; @@ -21,7 +21,7 @@ namespace Umbraco.Core.Services { } - public FileService(IUnitOfWorkProvider fileProvider, IUnitOfWorkProvider dataProvider) + public FileService(IUnitOfWorkProvider fileProvider, IDatabaseUnitOfWorkProvider dataProvider) { _fileUnitOfWork = fileProvider.GetUnitOfWork(); _dataUnitOfWork = dataProvider.GetUnitOfWork(); diff --git a/src/Umbraco.Core/Services/LocalizationService.cs b/src/Umbraco.Core/Services/LocalizationService.cs index df7770c138..6c1edee8f9 100644 --- a/src/Umbraco.Core/Services/LocalizationService.cs +++ b/src/Umbraco.Core/Services/LocalizationService.cs @@ -15,7 +15,7 @@ namespace Umbraco.Core.Services public class LocalizationService : ILocalizationService { private static readonly Guid RootParentId = new Guid("41c7638d-f529-4bff-853e-59a0c2fb1bde"); - private readonly IUnitOfWork _unitOfWork; + private readonly IDatabaseUnitOfWork _unitOfWork; private readonly IDictionaryRepository _dictionaryRepository; private readonly ILanguageRepository _languageRepository; @@ -23,7 +23,7 @@ namespace Umbraco.Core.Services { } - public LocalizationService(IUnitOfWorkProvider provider) + public LocalizationService(IDatabaseUnitOfWorkProvider provider) { _unitOfWork = provider.GetUnitOfWork(); _dictionaryRepository = RepositoryResolver.Current.Factory.CreateDictionaryRepository(_unitOfWork); diff --git a/src/Umbraco.Core/Services/MediaService.cs b/src/Umbraco.Core/Services/MediaService.cs index b4d13cd947..a8333e1691 100644 --- a/src/Umbraco.Core/Services/MediaService.cs +++ b/src/Umbraco.Core/Services/MediaService.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Generic; using Umbraco.Core.Models; using Umbraco.Core.Persistence; @@ -12,14 +13,14 @@ namespace Umbraco.Core.Services /// public class MediaService : IMediaService { - private readonly IUnitOfWork _unitOfWork; + private readonly IDatabaseUnitOfWork _unitOfWork; private readonly IMediaRepository _mediaRepository; public MediaService() : this(new PetaPocoUnitOfWorkProvider()) { } - public MediaService(IUnitOfWorkProvider provider) + public MediaService(IDatabaseUnitOfWorkProvider provider) { _unitOfWork = provider.GetUnitOfWork(); _mediaRepository = RepositoryResolver.Current.Factory.CreateMediaRepository(_unitOfWork); @@ -202,8 +203,19 @@ namespace Umbraco.Core.Services public void Save(IMedia media, int userId) { var repository = _mediaRepository; - repository.AddOrUpdate(media); - _unitOfWork.Commit(); + + var e = new SaveEventArgs(_unitOfWork); + if (Saving != null) + Saving(media, e); + + if (!e.Cancel) + { + repository.AddOrUpdate(media); + _unitOfWork.Commit(); + + if (Saved != null) + Saved(media, e); + } } /// @@ -220,5 +232,15 @@ namespace Umbraco.Core.Services } _unitOfWork.Commit(); } + + /// + /// Occurs before Save + /// + public static event EventHandler Saving; + + /// + /// Occurs after Save + /// + public static event EventHandler Saved; } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 6e44a4c405..1cf810d0f9 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -31,18 +31,29 @@ namespace Umbraco.Core.Services private ServiceContext() { - BuildServiceCache(); + BuildServiceCache(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy()); } + + /// + /// Internal constructor used for unit tests + /// + /// + /// + /// + internal ServiceContext(IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, IPublishingStrategy publishingStrategy) + { + BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy); + } + #endregion /// /// Builds the various services /// - private void BuildServiceCache() + private void BuildServiceCache(IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, IPublishingStrategy publishingStrategy) { - var provider = new PetaPocoUnitOfWorkProvider(); - var fileProvider = new FileUnitOfWorkProvider(); - var publishingStrategy = new PublishingStrategy(); + var provider = dbUnitOfWorkProvider; + var fileProvider = fileUnitOfWorkProvider; if(_userService == null) _userService = new UserService(provider); diff --git a/src/Umbraco.Core/Services/UserService.cs b/src/Umbraco.Core/Services/UserService.cs index 3281800830..16e94d3af2 100644 --- a/src/Umbraco.Core/Services/UserService.cs +++ b/src/Umbraco.Core/Services/UserService.cs @@ -12,10 +12,10 @@ namespace Umbraco.Core.Services /// internal class UserService : IUserService { - private readonly IUnitOfWork _unitOfWork; + private readonly IDatabaseUnitOfWork _unitOfWork; private readonly IUserRepository _userRepository; - public UserService(IUnitOfWorkProvider provider) + public UserService(IDatabaseUnitOfWorkProvider provider) { _unitOfWork = provider.GetUnitOfWork(); _userRepository = RepositoryResolver.Current.Factory.CreateUserRepository(_unitOfWork); diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 33b1f5aa46..a228f0fec5 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -400,6 +400,8 @@ + + diff --git a/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs index 659243a258..3d6c3e6a9a 100644 --- a/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs +++ b/src/Umbraco.Tests/CodeFirst/CodeFirstTests.cs @@ -228,9 +228,6 @@ namespace Umbraco.Tests.CodeFirst [TearDown] public override void TearDown() { - - DatabaseContext.Database.Dispose(); - base.TearDown(); //reset the app context diff --git a/src/Umbraco.Tests/Models/ContentXmlTest.cs b/src/Umbraco.Tests/Models/ContentXmlTest.cs index 0512a94116..343dc0be9b 100644 --- a/src/Umbraco.Tests/Models/ContentXmlTest.cs +++ b/src/Umbraco.Tests/Models/ContentXmlTest.cs @@ -38,8 +38,6 @@ namespace Umbraco.Tests.Models [TearDown] public override void TearDown() { - DatabaseContext.Database.Dispose(); - //reset the app context DataTypesResolver.Reset(); diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs index 521d4d94b9..906a1909ff 100644 --- a/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs +++ b/src/Umbraco.Tests/Persistence/Repositories/ContentRepositoryTest.cs @@ -13,7 +13,7 @@ using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Persistence.Repositories { - [TestFixture] + [TestFixture] public class ContentRepositoryTest : BaseDatabaseFactoryTest { [SetUp] diff --git a/src/Umbraco.Tests/Persistence/RepositoryResolverTests.cs b/src/Umbraco.Tests/Persistence/RepositoryResolverTests.cs index 48e5acb4ac..80c05585c1 100644 --- a/src/Umbraco.Tests/Persistence/RepositoryResolverTests.cs +++ b/src/Umbraco.Tests/Persistence/RepositoryResolverTests.cs @@ -29,14 +29,14 @@ namespace Umbraco.Tests.Persistence //[TestCase(typeof(IUserTypeRepository))] //[TestCase(typeof(IUserRepository))] + //[TestCase(typeof(IMacroRepository))] [TestCase(typeof(IContentRepository))] [TestCase(typeof(IMediaRepository))] [TestCase(typeof(IMediaTypeRepository))] [TestCase(typeof(IContentTypeRepository))] [TestCase(typeof(IDataTypeDefinitionRepository))] [TestCase(typeof(IDictionaryRepository))] - [TestCase(typeof(ILanguageRepository))] - //[TestCase(typeof(IMacroRepository))] + [TestCase(typeof(ILanguageRepository))] [TestCase(typeof(IRelationRepository))] [TestCase(typeof(IRelationTypeRepository))] [TestCase(typeof(IScriptRepository))] @@ -46,29 +46,16 @@ namespace Umbraco.Tests.Persistence { var method = typeof(RepositoryResolver).GetMethod("ResolveByType", BindingFlags.NonPublic | BindingFlags.Instance); var gMethod = method.MakeGenericMethod(repoType); - var repo = gMethod.Invoke(RepositoryResolver.Current, new object[] { new PetaPocoUnitOfWork() }); + var repo = gMethod.Invoke(RepositoryResolver.Current, new object[] { new PetaPocoUnitOfWork(DatabaseFactory.Current.Database) }); Assert.IsNotNull(repo); Assert.IsTrue(TypeHelper.IsTypeAssignableFrom(repoType, repo.GetType())); } - //[Test] - //public void Can_Resolve_All_Repositories() - //{ - // // Arrange - // var uow = new PetaPocoUnitOfWork(); - - // // Act - // RepositoryResolver.RegisterRepositories(); - - // // Assert - // Assert.That(RepositoryResolver.RegisteredRepositories(), Is.EqualTo(15)); - //} - - [Test] + [Test] public void Can_Verify_UOW_In_Repository() { // Arrange - var uow = new PetaPocoUnitOfWork(); + var uow = new PetaPocoUnitOfWork(DatabaseFactory.Current.Database); // Act var repository = RepositoryResolver.Current.ResolveByType(uow); @@ -87,8 +74,8 @@ namespace Umbraco.Tests.Persistence Assert.That(isSubclassOf, Is.False); Assert.That(isAssignableFrom, Is.True); - - var uow = new PetaPocoUnitOfWork(); + + var uow = new PetaPocoUnitOfWork(DatabaseFactory.Current.Database); var repository = RepositoryResolver.Current.ResolveByType(uow); bool subclassOf = repository.GetType().IsSubclassOf(typeof (IRepository)); diff --git a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs index 9e6ea58ed8..17b9ad9130 100644 --- a/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs +++ b/src/Umbraco.Tests/Publishing/PublishingStrategyTests.cs @@ -46,8 +46,8 @@ namespace Umbraco.Tests.Publishing [TearDown] public override void TearDown() { - DatabaseContext.Database.Dispose(); - + base.TearDown(); + //TestHelper.ClearDatabase(); //reset the app context diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index f5fb884ef3..9096df8ad9 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -14,7 +14,7 @@ using Umbraco.Tests.TestHelpers.Entities; namespace Umbraco.Tests.Services { - /// + /// /// Tests covering all methods in the ContentService class. /// This is more of an integration test as it involves multiple layers /// as well as configuration. @@ -668,7 +668,7 @@ namespace Umbraco.Tests.Services [Test] public void Can_Save_Lazy_Content() { - var unitOfWork = new PetaPocoUnitOfWork(); + var unitOfWork = new PetaPocoUnitOfWork(DatabaseFactory.Current.Database); var contentType = ServiceContext.ContentTypeService.GetContentType("umbTextpage"); var root = ServiceContext.ContentService.GetById(1046); diff --git a/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs new file mode 100644 index 0000000000..1be8d4afbe --- /dev/null +++ b/src/Umbraco.Tests/Services/ThreadSafetyServiceTest.cs @@ -0,0 +1,257 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using Umbraco.Core; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; +using Umbraco.Core.Services; +using Umbraco.Tests.TestHelpers; +using Umbraco.Tests.TestHelpers.Entities; + +namespace Umbraco.Tests.Services +{ + [TestFixture] + public class ThreadSafetyServiceTest : BaseDatabaseFactoryTest + { + private PerThreadPetaPocoUnitOfWorkProvider _uowProvider; + + [SetUp] + public override void Initialize() + { + base.Initialize(); + + //here we are going to override the ServiceContext because normally with our test cases we use a + //global Database object but this is NOT how it should work in the web world or in any multi threaded scenario. + //we need a new Database object for each thread. + _uowProvider = new PerThreadPetaPocoUnitOfWorkProvider(); + ServiceContext = new ServiceContext(_uowProvider, new FileUnitOfWorkProvider(), new PublishingStrategy()); + + CreateTestData(); + } + + [TearDown] + public override void TearDown() + { + _error = null; + _lastUowIdWithThread = null; + + //dispose! + _uowProvider.Dispose(); + + base.TearDown(); + + ServiceContext = null; + } + + /// + /// Used to track exceptions during multi-threaded tests, volatile so that it is not locked in CPU registers. + /// + private volatile Exception _error = null; + + private int _maxThreadCount = 20; + private object _locker = new object(); + private Tuple _lastUowIdWithThread = null; + + [Test] + public void Ensure_All_Threads_Reference_Different_Units_Of_Work_Content_Service() + { + //we will mimick the ServiceContext in that each repository in a service (i.e. ContentService) is a singleton + var contentService = (ContentService)ServiceContext.ContentService; + + var threads = new List(); + + Debug.WriteLine("Starting test..."); + + //bind to event to determine what is going on during the saving process + ContentService.Saving += HandleSaving; + + for (var i = 0; i < _maxThreadCount; i++) + { + var t = new Thread(() => + { + try + { + Debug.WriteLine("Created content on thread: " + Thread.CurrentThread.ManagedThreadId); + + //create 2 content items + + var content1 = contentService.CreateContent(-1, "umbTextpage", 0); + content1.Name = "test" + Guid.NewGuid(); + Debug.WriteLine("Saving content1 on thread: " + Thread.CurrentThread.ManagedThreadId); + contentService.Save(content1); + + Thread.Sleep(100); //quick pause for maximum overlap! + + var content2 = contentService.CreateContent(-1, "umbTextpage", 0); + content2.Name = "test" + Guid.NewGuid(); + Debug.WriteLine("Saving content2 on thread: " + Thread.CurrentThread.ManagedThreadId); + contentService.Save(content2); + } + catch(Exception e) + { + _error = e; + } + }); + threads.Add(t); + } + + //start all threads + threads.ForEach(x => x.Start()); + + //wait for all to complete + threads.ForEach(x => x.Join()); + + //kill them all + threads.ForEach(x => x.Abort()); + + if (_error == null) + { + //now look up all items, there should be 40! + var items = contentService.GetRootContent(); + Assert.AreEqual(40, items.Count()); + } + else + { + Assert.Fail("ERROR! " + _error); + } + + } + + [Test] + public void Ensure_All_Threads_Reference_Different_Units_Of_Work_Media_Service() + { + //we will mimick the ServiceContext in that each repository in a service (i.e. ContentService) is a singleton + var mediaService = (MediaService)ServiceContext.MediaService; + + var threads = new List(); + + Debug.WriteLine("Starting test..."); + + //bind to event to determine what is going on during the saving process + ContentService.Saving += HandleSaving; + + for (var i = 0; i < _maxThreadCount; i++) + { + var t = new Thread(() => + { + try + { + var folderMediaType = ServiceContext.ContentTypeService.GetMediaType(1031); + + Debug.WriteLine("Created content on thread: " + Thread.CurrentThread.ManagedThreadId); + + //create 2 content items + + var folder1 = MockedMedia.CreateMediaFolder(folderMediaType, -1); + folder1.Name = "test" + Guid.NewGuid(); + Debug.WriteLine("Saving folder1 on thread: " + Thread.CurrentThread.ManagedThreadId); + mediaService.Save(folder1, 0); + + Thread.Sleep(100); //quick pause for maximum overlap! + + var folder2 = MockedMedia.CreateMediaFolder(folderMediaType, -1); + folder2.Name = "test" + Guid.NewGuid(); + Debug.WriteLine("Saving folder2 on thread: " + Thread.CurrentThread.ManagedThreadId); + mediaService.Save(folder2, 0); + } + catch (Exception e) + { + _error = e; + } + }); + threads.Add(t); + } + + //start all threads + threads.ForEach(x => x.Start()); + + //wait for all to complete + threads.ForEach(x => x.Join()); + + //kill them all + threads.ForEach(x => x.Abort()); + + if (_error == null) + { + //now look up all items, there should be 40! + var items = mediaService.GetRootMedia(); + Assert.AreEqual(40, items.Count()); + } + else + { + Assert.Fail("ERROR! " + _error); + } + + } + + private void HandleSaving(object sender, SaveEventArgs args) + { + if (_error == null) + { + lock (_locker) + { + //get the instance id of the unit of work + var uowId = ((PetaPocoUnitOfWork)args.UnitOfWork).InstanceId; + if (_lastUowIdWithThread == null) + { + Debug.WriteLine("Initial UOW found = " + uowId + " with thread id " + Thread.CurrentThread.ManagedThreadId); + + _lastUowIdWithThread = new Tuple( + Thread.CurrentThread.ManagedThreadId, uowId); + } + else + { + Debug.WriteLine("Next thread running. UOW found = " + uowId + " with thread id " + Thread.CurrentThread.ManagedThreadId); + + var newTuple = new Tuple( + Thread.CurrentThread.ManagedThreadId, uowId); + + //check if the uowId is the same as the last and if the thread Id's are different then we have a problem + if (newTuple.Item2 == _lastUowIdWithThread.Item2 + && newTuple.Item1 != _lastUowIdWithThread.Item1) + { + _error = new Exception("The threads: " + newTuple.Item1 + " and " + _lastUowIdWithThread.Item1 + " are both referencing the Unit of work: " + _lastUowIdWithThread.Item2); + } + } + } + } + } + + public void CreateTestData() + { + //Create and Save ContentType "umbTextpage" -> 1045 + ContentType contentType = MockedContentTypes.CreateSimpleContentType("umbTextpage", "Textpage"); + contentType.Key = new Guid("1D3A8E6E-2EA9-4CC1-B229-1AEE19821522"); + ServiceContext.ContentTypeService.Save(contentType); + } + + /// + /// Creates a Database object per thread, this mimics the web context which is per HttpContext + /// + internal class PerThreadPetaPocoUnitOfWorkProvider : DisposableObject, IDatabaseUnitOfWorkProvider + { + private readonly ConcurrentDictionary _databases = new ConcurrentDictionary(); + + public IDatabaseUnitOfWork GetUnitOfWork() + { + //Create or get a database instance for this thread. + var db = _databases.GetOrAdd(Thread.CurrentThread.ManagedThreadId, i => new Database(Umbraco.Core.Configuration.GlobalSettings.UmbracoConnectionName)); + + return new PetaPocoUnitOfWork(db); + } + + protected override void DisposeResources() + { + //dispose the databases + _databases.ForEach(x => x.Value.Dispose()); + } + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index a613ab097a..9926e7d92a 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -10,6 +10,8 @@ using Umbraco.Core.Configuration; using Umbraco.Core.IO; using Umbraco.Core.ObjectResolution; using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.UnitOfWork; +using Umbraco.Core.Publishing; using Umbraco.Core.Services; using Umbraco.Tests.Stubs; using Umbraco.Web; @@ -18,6 +20,7 @@ using umbraco.BusinessLogic; namespace Umbraco.Tests.TestHelpers { + /// /// Use this abstract class for tests that requires a Sql Ce database populated with the umbraco db schema. /// The PetaPoco Database class should be used through the singleton. @@ -57,7 +60,7 @@ namespace Umbraco.Tests.TestHelpers Resolution.Freeze(); ApplicationContext = new ApplicationContext() { IsReady = true }; DatabaseContext = DatabaseContext.Current; - ServiceContext = ServiceContext.Current; + ServiceContext = new ServiceContext(new PetaPocoUnitOfWorkProvider(), new FileUnitOfWorkProvider(), new PublishingStrategy()); //Configure the Database and Sql Syntax based on connection string set in config DatabaseContext.Initialize(); @@ -69,6 +72,8 @@ namespace Umbraco.Tests.TestHelpers [TearDown] public virtual void TearDown() { + DatabaseContext.Database.Dispose(); + TestHelper.CleanContentDirectories(); //reset the app context diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 7ae3071121..09ed380ec0 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -235,6 +235,7 @@ + diff --git a/src/Umbraco.Web/UmbracoModule.cs b/src/Umbraco.Web/UmbracoModule.cs index 29457c2ce7..90ded62fd2 100644 --- a/src/Umbraco.Web/UmbracoModule.cs +++ b/src/Umbraco.Web/UmbracoModule.cs @@ -421,13 +421,15 @@ namespace Umbraco.Web app.EndRequest += (sender, args) => { - var httpContext = ((HttpApplication)sender).Context; + var httpContext = ((HttpApplication)sender).Context; if (UmbracoContext.Current != null && UmbracoContext.Current.IsFrontEndUmbracoRequest) { //write the trace output for diagnostics at the end of the request httpContext.Trace.Write("UmbracoModule", "Umbraco request completed"); LogHelper.Debug("Total milliseconds for umbraco request to process: " + DateTime.Now.Subtract(UmbracoContext.Current.ObjectCreated).TotalMilliseconds); - } + } + + DisposeHttpContextItems(httpContext); }; } @@ -438,5 +440,16 @@ namespace Umbraco.Web #endregion + /// + /// Any object that is in the HttpContext.Items collection that is IDisposable will get disposed on the end of the request + /// + /// + private static void DisposeHttpContextItems(HttpContext http) + { + foreach(var i in http.Items) + { + i.DisposeIfDisposable(); + } + } } }