Massively improves performance for the packaging service (converting docs to XML) which is used in republishing the tree.

This commit is contained in:
Shannon
2013-08-05 13:11:47 +10:00
parent b7a1123319
commit d78a03a610
12 changed files with 229 additions and 13 deletions

2
.gitignore vendored
View File

@@ -79,3 +79,5 @@ src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/
src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/
build/_BuildOutput/
tools/NDepend/
src/*.vspx
src/*.psess

View File

@@ -22,10 +22,15 @@ namespace Umbraco.Core.Models
/// <returns>Xml of the property and its value</returns>
public static XElement ToXml(this Property property)
{
string nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias();
return property.ToXml(ApplicationContext.Current.Services.DataTypeService);
}
internal static XElement ToXml(this Property property, IDataTypeService dataTypeService)
{
var nodeName = UmbracoSettings.UseLegacyXmlSchema ? "data" : property.Alias.ToSafeAlias();
var xd = new XmlDocument();
XmlNode xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, "");
var xmlNode = xd.CreateNode(XmlNodeType.Element, nodeName, "");
//Add the property alias to the legacy schema
if (UmbracoSettings.UseLegacyXmlSchema)
@@ -37,9 +42,17 @@ namespace Umbraco.Core.Models
//This seems to fail during testing
//SD: With the new null checks below, this shouldn't fail anymore.
var dt = property.PropertyType.DataType(property.Id);
var dt = property.PropertyType.DataType(property.Id, dataTypeService);
if (dt != null && dt.Data != null)
{
{
//We've already got the value for the property so we're going to give it to the
// data type's data property so it doesn't go re-look up the value from the db again.
var defaultData = dt.Data as IDataValueSetter;
if (defaultData != null)
{
defaultData.SetValue(property.Value, property.PropertyType.DataTypeDatabaseType.ToString());
}
xmlNode.AppendChild(dt.Data.ToXMl(xd));
}

View File

@@ -1,4 +1,5 @@
using umbraco.interfaces;
using Umbraco.Core.Services;
using umbraco.interfaces;
namespace Umbraco.Core.Models
{
@@ -9,6 +10,7 @@ namespace Umbraco.Core.Models
/// </summary>
/// <param name="propertyType">PropertyType that references a DataType</param>
/// <param name="propertyId">Id of the Property which references this DataType through its PropertyType</param>
/// <param name="dataTypeService"></param>
/// <returns><see cref="IDataType"/></returns>
/// <remarks>
/// This extension method is left internal because we don't want to take
@@ -16,10 +18,10 @@ namespace Umbraco.Core.Models
/// be replaced by PropertyEditors. It is however needed to generate xml
/// for a property/propertytype when publishing.
/// </remarks>
internal static IDataType DataType(this PropertyType propertyType, int propertyId)
internal static IDataType DataType(this PropertyType propertyType, int propertyId, IDataTypeService dataTypeService)
{
Mandate.ParameterNotNull(propertyType, "propertyType");
var dataType = ApplicationContext.Current.Services.DataTypeService.GetDataTypeById(propertyType.DataTypeId);
var dataType = dataTypeService.GetDataTypeById(propertyType.DataTypeId);
if (dataType == null)
{
return null;

View File

@@ -0,0 +1,102 @@
using System;
using System.Diagnostics;
using System.Xml;
using NUnit.Framework;
using Rhino.Mocks;
using Rhino.Mocks.Interfaces;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Core.Strings;
using Umbraco.Tests.TestHelpers;
using umbraco.cms.businesslogic.datatype;
using umbraco.interfaces;
namespace Umbraco.Tests.Models
{
[TestFixture]
public class DataValueSetterTests : BaseUmbracoApplicationTest
{
protected override void FreezeResolution()
{
ShortStringHelperResolver.Current = new ShortStringHelperResolver(new DefaultShortStringHelper());
base.FreezeResolution();
}
[Test]
public void LoadValueFromDatabase_Is_Not_Called_When_SetValue_Is_Used()
{
// Arrange
var baseDataType = MockRepository.GenerateStub<BaseDataType>();
var dataTypeData = MockRepository.GenerateMock<DefaultData>(baseDataType);
dataTypeData.Stub(x => x.Value).CallOriginalMethod(OriginalCallOptions.NoExpectation);
// Act
((IDataValueSetter)dataTypeData).SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString());
var val = dataTypeData.Value;
// Assert
dataTypeData.AssertWasNotCalled(data => data.LoadValueFromDatabase());
}
[Test]
public void LoadValueFromDatabase_Is_Called_When_SetValue_Is_Not_Used()
{
// Arrange
var baseDataType = MockRepository.GenerateStub<BaseDataType>();
var dataTypeData = MockRepository.GenerateMock<DefaultData>(baseDataType);
dataTypeData
.Stub(data => data.LoadValueFromDatabase()).WhenCalled(invocation => Debug.WriteLine("asdf"));
dataTypeData.Stub(x => x.Value).CallOriginalMethod(OriginalCallOptions.NoExpectation);
// Act
var val = dataTypeData.Value;
// Assert
dataTypeData.AssertWasCalled(data => data.LoadValueFromDatabase());
}
[Test]
public void SetValue_Is_Called_When_Executing_ToXml_On_A_Property_With_DataType_That_Implements_IDataValueSetter()
{
// Arrange
var dataTypeId = Guid.NewGuid();
var dataTypeData = MockRepository.GenerateMock<IData, IDataValueSetter>();
dataTypeData
.Stub(data => data.ToXMl(Arg<XmlDocument>.Is.Anything))
.Return(null) // you have to call Return() even though we're about to override it
.WhenCalled(invocation =>
{
var xmlDoc = (XmlDocument) invocation.Arguments[0];
invocation.ReturnValue = xmlDoc.CreateElement("test");
});
var dataType = MockRepository.GenerateStub<IDataType>();
dataType.Stub(type => type.Data).Return(dataTypeData);
var dataTypeSvc = MockRepository.GenerateStub<IDataTypeService>();
dataTypeSvc.Stub(service => service.GetDataTypeById(dataTypeId)).Return(dataType);
var property = new Property(
1234,
Guid.NewGuid(),
new PropertyType(dataTypeId, DataTypeDatabaseType.Nvarchar)
{
Alias = "test"
}, "Hello world");
// Act
var xml = property.ToXml(dataTypeSvc);
// Assert
((IDataValueSetter)dataTypeData).AssertWasCalled(setter => setter.SetValue("Hello world", DataTypeDatabaseType.Nvarchar.ToString()));
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Tests.TestHelpers.Entities;
namespace Umbraco.Tests.Services
{
//[TestFixture]
//public class PackagingServiceTests : BaseServiceTest
//{
// [Test]
// public void Export_Content()
// {
// var yesNo = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.TrueFalse));
// var txtField = DataTypesResolver.Current.GetById(new Guid(Constants.PropertyEditors.Textbox));
// var contentWithDataType = MockedContentTypes.CreateSimpleContentType(
// "test",
// "Test",
// new PropertyTypeCollection(
// new PropertyType[]
// {
// new PropertyType(new DataTypeDefinition(-1, txtField.Id)
// {
// Name = "Testing Textfield", DatabaseType = DataTypeDatabaseType.Ntext
// }),
// new PropertyType(new DataTypeDefinition(-1, yesNo.Id)
// {
// Name = "Testing intfield", DatabaseType = DataTypeDatabaseType.Integer
// })
// }));
// var content = MockedContent.CreateSimpleContent(contentWithDataType);
// content.Name = "Test";
// var exported = ServiceContext.PackagingService.Export(content);
// }
//}
}

View File

@@ -46,6 +46,8 @@ namespace Umbraco.Tests.TestHelpers
//Used to flag if its the first test in the current fixture
private bool _isFirstTestInFixture = false;
private ApplicationContext _appContext;
[SetUp]
public override void Initialize()
{
@@ -59,7 +61,7 @@ namespace Umbraco.Tests.TestHelpers
var dbFactory = new DefaultDatabaseFactory(
GetDbConnectionString(),
GetDbProviderName());
ApplicationContext.Current = new ApplicationContext(
_appContext = new ApplicationContext(
//assign the db context
new DatabaseContext(dbFactory),
//assign the service context
@@ -79,7 +81,12 @@ namespace Umbraco.Tests.TestHelpers
//ensure the configuration matches the current version for tests
SettingsForTests.ConfigurationStatus = UmbracoVersion.Current.ToString(3);
}
protected override void SetupApplicationContext()
{
ApplicationContext.Current = _appContext;
}
/// <summary>
/// The database behavior to use for the test/fixture
/// </summary>

View File

@@ -25,6 +25,8 @@ namespace Umbraco.Tests.TestHelpers
SetupPluginManager();
SetupApplicationContext();
FreezeResolution();
}
@@ -40,7 +42,7 @@ namespace Umbraco.Tests.TestHelpers
ApplicationContext.Current = null;
ResetPluginManager();
}
/// <summary>
/// By default this returns false which means the plugin manager will not be reset so it doesn't need to re-scan
/// all of the assemblies. Inheritors can override this if plugin manager resetting is required, generally needs

View File

@@ -185,9 +185,11 @@
<Compile Include="Configurations\FileSystemProviderTests.cs" />
<Compile Include="CoreXml\FrameworkXmlTests.cs" />
<Compile Include="Integration\CreateContent.cs" />
<Compile Include="Models\DataValueSetterTests.cs" />
<Compile Include="Persistence\PetaPocoExtensionsTest.cs" />
<Compile Include="Persistence\Repositories\UserRepositoryTest.cs" />
<Compile Include="Persistence\Repositories\UserTypeRepositoryTest.cs" />
<Compile Include="Services\PackagingServiceTests.cs" />
<Compile Include="Services\PerformanceTests.cs" />
<Compile Include="Services\UserServiceTests.cs" />
<Compile Include="TestHelpers\BaseSeleniumTest.cs" />

View File

@@ -14,7 +14,7 @@ namespace umbraco.cms.businesslogic.datatype
/// <summary>
/// Default implementation of the <c>IData</c> interface that stores data inside the Umbraco database.
/// </summary>
public class DefaultData : IData, IDataWithPreview
public class DefaultData : IData, IDataWithPreview, IDataValueSetter
{
private int _propertyId;
private object _value;
@@ -57,10 +57,29 @@ namespace umbraco.cms.businesslogic.datatype
_value = InitValue;
}
/// <summary>
/// This is here for performance reasons since in some cases we will have already resolved the value from the db
/// and want to just give this object the value so it doesn't go re-look it up from the database.
/// </summary>
/// <param name="val"></param>
/// <param name="strDbType"></param>
void IDataValueSetter.SetValue(object val, string strDbType)
{
_value = val;
//now that we've set our value, we can update our BaseDataType object with the correct values from the db
//instead of making it query for itself. This is a peformance optimization enhancement.
var dbType = BaseDataType.GetDBType(strDbType);
var fieldName = BaseDataType.GetDataFieldName(dbType);
_dataType.SetDataTypeProperties(fieldName, dbType);
//ensures that it doesn't go back to the db
_valueLoaded = true;
}
/// <summary>
/// Loads the data value from the database.
/// </summary>
protected virtual void LoadValueFromDatabase()
protected internal virtual void LoadValueFromDatabase()
{
var sql = new Sql();
sql.Select("*")
@@ -243,5 +262,7 @@ namespace umbraco.cms.businesslogic.datatype
}
#endregion
}
}

View File

@@ -3,6 +3,15 @@ using System.Xml;
namespace umbraco.interfaces
{
/// <summary>
/// Internal interface used to decorate any IData that can be optimized when exporting
/// XML like in the packaging service. Instead of relying on the IData to go get the value
/// from the db, any IData that implements this can have it's value set from the packaging service.
/// </summary>
internal interface IDataValueSetter
{
void SetValue(object val, string strDbType);
}
/// <summary>
/// The IData is part of the IDataType interface for creating new data types in the umbraco backoffice.

View File

@@ -9,4 +9,11 @@ using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("umbraco.interfaces")]
[assembly: AssemblyDescription("Core assembly containing legacy interfaces")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyProduct("Umbraco CMS")]
[assembly: AssemblyProduct("Umbraco CMS")]
[assembly: InternalsVisibleTo("cms")]
[assembly: InternalsVisibleTo("Umbraco.Core")]
[assembly: InternalsVisibleTo("Umbraco.Tests")]
//allow this to be mocked in our unit tests
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]

View File

@@ -67,6 +67,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NuSpecs", "NuSpecs", "{227C
..\build\NuSpecs\UmbracoCms.nuspec = ..\build\NuSpecs\UmbracoCms.nuspec
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99794542-89EC-43BA-88BE-E31A9D61423B}"
ProjectSection(SolutionItems) = preProject
Performance1.psess = Performance1.psess
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -157,4 +162,7 @@ Global
{73529637-28F5-419C-A6BB-D094E39DE614} = {DD32977B-EF54-475B-9A1B-B97A502C6E58}
{B555AAE6-0F56-442F-AC9F-EF497DB38DE7} = {DD32977B-EF54-475B-9A1B-B97A502C6E58}
EndGlobalSection
GlobalSection(Performance) = preSolution
HasPerformanceSessions = true
EndGlobalSection
EndGlobal