From b73efd16dc30ce62a9534e596b05933e5795dd35 Mon Sep 17 00:00:00 2001 From: Shannon Deminick Date: Sat, 15 Dec 2012 10:33:29 +0500 Subject: [PATCH] Fixes ServiceContext to ensure the repo's are lazy instantiated because their ctor's rely on the RepositoryResolver.Current being initialized which normally doesn't occur until after the ServiceContext is constructed. Adds instance level caching for the GetRecursiveValue method in case this is called more than one time for a property in one view. Reverts PetaPocoUnitOfWork to allow more than one call to Commit().. this isn't 'best practices' per se but it is more for performance reasons because otherwise we'd have to create a new repo object + uow for any bulk saving operations... The end result is the same, bulk operations in the Services cannot be processed in one transaction. Fixing up the ContentServiceTests by ensuring that the shared and always open sqlce connection with the legacy SqlCeContextGuardian is closed on TearDown. --- src/SQLCE4Umbraco/SqlCeContextGuardian.cs | 17 ++++-- .../UnitOfWork/PetaPocoUnitOfWork.cs | 46 +++++++------- .../PublishedContentExtensions.cs | 12 ++++ src/Umbraco.Core/Services/ServiceContext.cs | 60 ++++++++++--------- .../Services/ContentServiceTests.cs | 13 ++-- .../TestHelpers/BaseDatabaseFactoryTest.cs | 21 ++++--- 6 files changed, 98 insertions(+), 71 deletions(-) diff --git a/src/SQLCE4Umbraco/SqlCeContextGuardian.cs b/src/SQLCE4Umbraco/SqlCeContextGuardian.cs index eb47a15bfe..8206ac6c53 100644 --- a/src/SQLCE4Umbraco/SqlCeContextGuardian.cs +++ b/src/SQLCE4Umbraco/SqlCeContextGuardian.cs @@ -11,7 +11,7 @@ namespace SQLCE4Umbraco public static class SqlCeContextGuardian { private static SqlCeConnection _constantOpenConnection; - private static object objLock = new object(); + private static readonly object Lock = new object(); // Awesome SQL CE 4 speed improvement by Erik Ejskov Jensen - SQL CE 4 MVP // It's not an issue with SQL CE 4 that we never close the connection @@ -29,7 +29,7 @@ namespace SQLCE4Umbraco connectionStringBuilder.Remove("datalayer"); // SQL CE 4 performs better when there's always a connection open in the background - ensureOpenBackgroundConnection(connectionStringBuilder.ConnectionString); + EnsureOpenBackgroundConnection(connectionStringBuilder.ConnectionString); SqlCeConnection conn = new SqlCeConnection(connectionStringBuilder.ConnectionString); conn.Open(); @@ -38,9 +38,18 @@ namespace SQLCE4Umbraco } - private static void ensureOpenBackgroundConnection(string connectionString) + /// + /// Sometimes we need to ensure this is closed especially in unit tests + /// + internal static void CloseBackgroundConnection() + { + if (_constantOpenConnection != null) + _constantOpenConnection.Close(); + } + + private static void EnsureOpenBackgroundConnection(string connectionString) { - lock (objLock) + lock (Lock) { if (_constantOpenConnection == null) { diff --git a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs index 5d1fa309b7..6f9f5fc220 100644 --- a/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs +++ b/src/Umbraco.Core/Persistence/UnitOfWork/PetaPocoUnitOfWork.cs @@ -18,9 +18,6 @@ namespace Umbraco.Core.Persistence.UnitOfWork private Guid _key; private readonly List _operations = new List(); - private readonly Transaction _transaction; - private bool _committed = false; - /// /// Creates a new unit of work instance @@ -34,7 +31,6 @@ namespace Umbraco.Core.Persistence.UnitOfWork Database = database; _key = Guid.NewGuid(); InstanceId = Guid.NewGuid(); - _transaction = Database.GetTransaction(); } /// @@ -91,34 +87,37 @@ namespace Umbraco.Core.Persistence.UnitOfWork /// /// Commits all batched changes within the scope of a PetaPoco transaction /// + /// + /// Unlike a typical unit of work, this UOW will let you commit more than once since a new transaction is creaed per + /// Commit() call instead of having one Transaction per UOW. + /// public void Commit() { - if (_committed) + + using (var transaction = Database.GetTransaction()) { - throw new InvalidOperationException("Cannot has already been called for this unit of work"); - } - - foreach (var operation in _operations.OrderBy(o => o.ProcessDate)) - { - switch (operation.Type) + foreach (var operation in _operations.OrderBy(o => o.ProcessDate)) { - case TransactionType.Insert: - operation.Repository.PersistNewItem(operation.Entity); - break; - case TransactionType.Delete: - operation.Repository.PersistDeletedItem(operation.Entity); - break; - case TransactionType.Update: - operation.Repository.PersistUpdatedItem(operation.Entity); - break; + switch (operation.Type) + { + case TransactionType.Insert: + operation.Repository.PersistNewItem(operation.Entity); + break; + case TransactionType.Delete: + operation.Repository.PersistDeletedItem(operation.Entity); + break; + case TransactionType.Update: + operation.Repository.PersistUpdatedItem(operation.Entity); + break; + } } + transaction.Complete(); } - _transaction.Complete(); + // Clear everything _operations.Clear(); _key = Guid.NewGuid(); - _committed = true; } public object Key @@ -171,9 +170,6 @@ namespace Umbraco.Core.Persistence.UnitOfWork protected override void DisposeResources() { _operations.Clear(); - _transaction.Dispose(); - //ensure the local object will be gargabe collected - Database = null; } } } \ No newline at end of file diff --git a/src/Umbraco.Core/PublishedContentExtensions.cs b/src/Umbraco.Core/PublishedContentExtensions.cs index c039cf881c..c4ccf3af1c 100644 --- a/src/Umbraco.Core/PublishedContentExtensions.cs +++ b/src/Umbraco.Core/PublishedContentExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Data; using System.Net.Mime; using System.Web; +using Umbraco.Core.Dynamics; using Umbraco.Core.Models; using umbraco.interfaces; @@ -83,6 +84,13 @@ namespace Umbraco.Core /// public static string GetRecursiveValue(this IPublishedContent publishedContent, string fieldname) { + //check for the cached value in the objects properties first + var cachedVal = publishedContent["__recursive__" + fieldname]; + if (cachedVal != null) + { + return cachedVal.ToString(); + } + var contentValue = ""; var currentContent = publishedContent; @@ -102,6 +110,10 @@ namespace Umbraco.Core contentValue = val.ToString(); //we've found a recursive val } } + + //cache this lookup in a new custom (hidden) property + publishedContent.Properties.Add(new PropertyResult("__recursive__" + fieldname, contentValue, Guid.Empty, PropertyResultType.CustomProperty)); + return contentValue; } diff --git a/src/Umbraco.Core/Services/ServiceContext.cs b/src/Umbraco.Core/Services/ServiceContext.cs index 4e375733de..4f5d0d696e 100644 --- a/src/Umbraco.Core/Services/ServiceContext.cs +++ b/src/Umbraco.Core/Services/ServiceContext.cs @@ -1,3 +1,4 @@ +using System; using Umbraco.Core.Persistence; using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Publishing; @@ -11,14 +12,14 @@ namespace Umbraco.Core.Services /// public class ServiceContext { - private ContentService _contentService; - private UserService _userService; - private MediaService _mediaService; - private MacroService _macroService; - private ContentTypeService _contentTypeService; - private DataTypeService _dataTypeService; - private FileService _fileService; - private LocalizationService _localizationService; + private Lazy _contentService; + private Lazy _userService; + private Lazy _mediaService; + private Lazy _macroService; + private Lazy _contentTypeService; + private Lazy _dataTypeService; + private Lazy _fileService; + private Lazy _localizationService; /// /// Constructor @@ -28,7 +29,10 @@ namespace Umbraco.Core.Services /// internal ServiceContext(IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, IPublishingStrategy publishingStrategy) { - BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy, RepositoryResolver.Current.Factory); + BuildServiceCache(dbUnitOfWorkProvider, fileUnitOfWorkProvider, publishingStrategy, + //this needs to be lazy because when we create the service context it's generally before the + //resolvers have been initialized! + new Lazy(() => RepositoryResolver.Current.Factory)); } /// @@ -38,34 +42,34 @@ namespace Umbraco.Core.Services IDatabaseUnitOfWorkProvider dbUnitOfWorkProvider, IUnitOfWorkProvider fileUnitOfWorkProvider, IPublishingStrategy publishingStrategy, - RepositoryFactory repositoryFactory) + Lazy repositoryFactory) { var provider = dbUnitOfWorkProvider; - var fileProvider = fileUnitOfWorkProvider; + var fileProvider = fileUnitOfWorkProvider; - if(_userService == null) - _userService = new UserService(provider, repositoryFactory); + if (_userService == null) + _userService = new Lazy(() => new UserService(provider, repositoryFactory.Value)); if (_contentService == null) - _contentService = new ContentService(provider, repositoryFactory, publishingStrategy, _userService); + _contentService = new Lazy(() => new ContentService(provider, repositoryFactory.Value, publishingStrategy, _userService.Value)); if(_mediaService == null) - _mediaService = new MediaService(provider, repositoryFactory); + _mediaService = new Lazy(() => new MediaService(provider, repositoryFactory.Value)); if(_macroService == null) - _macroService = new MacroService(fileProvider, repositoryFactory); + _macroService = new Lazy(() => new MacroService(fileProvider, repositoryFactory.Value)); if(_contentTypeService == null) - _contentTypeService = new ContentTypeService(provider, repositoryFactory, _contentService, _mediaService); + _contentTypeService = new Lazy(() => new ContentTypeService(provider, repositoryFactory.Value, _contentService.Value, _mediaService.Value)); if(_dataTypeService == null) - _dataTypeService = new DataTypeService(provider, repositoryFactory); + _dataTypeService = new Lazy(() => new DataTypeService(provider, repositoryFactory.Value)); if(_fileService == null) - _fileService = new FileService(fileProvider, provider, repositoryFactory); + _fileService = new Lazy(() => new FileService(fileProvider, provider, repositoryFactory.Value)); if(_localizationService == null) - _localizationService = new LocalizationService(provider, repositoryFactory); + _localizationService = new Lazy(() => new LocalizationService(provider, repositoryFactory.Value)); } /// @@ -73,7 +77,7 @@ namespace Umbraco.Core.Services /// public IContentService ContentService { - get { return _contentService; } + get { return _contentService.Value; } } /// @@ -81,7 +85,7 @@ namespace Umbraco.Core.Services /// public IContentTypeService ContentTypeService { - get { return _contentTypeService; } + get { return _contentTypeService.Value; } } /// @@ -89,7 +93,7 @@ namespace Umbraco.Core.Services /// public IDataTypeService DataTypeService { - get { return _dataTypeService; } + get { return _dataTypeService.Value; } } /// @@ -97,7 +101,7 @@ namespace Umbraco.Core.Services /// public IFileService FileService { - get { return _fileService; } + get { return _fileService.Value; } } /// @@ -105,7 +109,7 @@ namespace Umbraco.Core.Services /// public ILocalizationService LocalizationService { - get { return _localizationService; } + get { return _localizationService.Value; } } /// @@ -113,7 +117,7 @@ namespace Umbraco.Core.Services /// public IMediaService MediaService { - get { return _mediaService; } + get { return _mediaService.Value; } } /// @@ -121,7 +125,7 @@ namespace Umbraco.Core.Services /// internal IMacroService MacroService { - get { return _macroService; } + get { return _macroService.Value; } } /// @@ -129,7 +133,7 @@ namespace Umbraco.Core.Services /// internal IUserService UserService { - get { return _userService; } + get { return _userService.Value; } } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index d04cd84b26..d474752823 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using NUnit.Framework; +using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Core.Models.Rdbms; using Umbraco.Core.Persistence; @@ -11,6 +12,8 @@ using Umbraco.Core.Persistence.UnitOfWork; using Umbraco.Core.Services; using Umbraco.Tests.TestHelpers; using Umbraco.Tests.TestHelpers.Entities; +using umbraco.editorControls.tinyMCE3; +using umbraco.interfaces; namespace Umbraco.Tests.Services { @@ -26,17 +29,17 @@ namespace Umbraco.Tests.Services public override void Initialize() { //this ensures its reset - //PluginManager.Current = new PluginManager(); + PluginManager.Current = new PluginManager(); //for testing, we'll specify which assemblies are scanned for the PluginTypeResolver - /*PluginManager.Current.AssembliesToScan = new[] + PluginManager.Current.AssembliesToScan = new[] { typeof(IDataType).Assembly, typeof(tinyMCE3dataType).Assembly }; DataTypesResolver.Current = new DataTypesResolver( - PluginManager.Current.ResolveDataTypes());*/ + PluginManager.Current.ResolveDataTypes()); base.Initialize(); @@ -47,8 +50,8 @@ namespace Umbraco.Tests.Services public override void TearDown() { //reset the app context - //DataTypesResolver.Reset(); - //PluginManager.Current = null; + DataTypesResolver.Reset(); + PluginManager.Current = null; base.TearDown(); } diff --git a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs index 4644977b5a..f21b02e14a 100644 --- a/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs +++ b/src/Umbraco.Tests/TestHelpers/BaseDatabaseFactoryTest.cs @@ -5,6 +5,7 @@ using System.IO; using System.Web.Routing; using System.Xml; using NUnit.Framework; +using SQLCE4Umbraco; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.ObjectResolution; @@ -74,23 +75,25 @@ namespace Umbraco.Tests.TestHelpers public virtual void TearDown() { DatabaseContext.Database.Dispose(); + //reset the app context + ApplicationContext.ApplicationCache.ClearAllCache(); + + //legacy API database connection close + SqlCeContextGuardian.CloseBackgroundConnection(); + + ApplicationContext.Current = null; + Resolution.IsFrozen = false; + RepositoryResolver.Reset(); TestHelper.CleanContentDirectories(); - - //reset the app context - ApplicationContext.ApplicationCache.ClearAllCache(); - ApplicationContext.Current = null; - Resolution.IsFrozen = false; - - RepositoryResolver.Reset(); - + string path = TestHelper.CurrentAssemblyDirectory; AppDomain.CurrentDomain.SetData("DataDirectory", null); string filePath = string.Concat(path, "\\UmbracoPetaPocoTests.sdf"); if (File.Exists(filePath)) { - //File.Delete(filePath); + File.Delete(filePath); } }