diff --git a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs index 8917368e21..c962224689 100644 --- a/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs +++ b/src/Umbraco.Core/Persistence/PetaPocoExtensions.cs @@ -39,39 +39,47 @@ namespace Umbraco.Core.Persistence using (var tr = db.GetTransaction()) { - try + db.BulkInsertRecords(collection, tr); + } + } + + public static void BulkInsertRecords(this Database db, IEnumerable collection, Transaction tr) + { + //don't do anything if there are no records. + if (collection.Any() == false) + return; + + try + { + if (SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider) { - if (SqlSyntaxContext.SqlSyntaxProvider is SqlCeSyntaxProvider) + //SqlCe doesn't support bulk insert statements! + + foreach (var poco in collection) { - //SqlCe doesn't support bulk insert statements! - - foreach (var poco in collection) - { - db.Insert(poco); - } - + db.Insert(poco); } - else + } + else + { + string[] sqlStatements; + var cmds = db.GenerateBulkInsertCommand(collection, db.Connection, out sqlStatements); + for (var i = 0; i < sqlStatements.Length; i++) { - string[] sqlStatements; - var cmds = db.GenerateBulkInsertCommand(collection, db.Connection, out sqlStatements); - for (var i = 0; i < sqlStatements.Length; i++) + using (var cmd = cmds[i]) { - using (var cmd = cmds[i]) - { - cmd.CommandText = sqlStatements[i]; - cmd.ExecuteNonQuery(); - } + cmd.CommandText = sqlStatements[i]; + cmd.ExecuteNonQuery(); } } + } - tr.Complete(); - } - catch - { - tr.Dispose(); - throw; - } + tr.Complete(); + } + catch + { + tr.Dispose(); + throw; } } diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index b47879bda1..3dbc03fab2 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1378,41 +1378,46 @@ namespace Umbraco.Core.Services var uow = _uowProvider.GetUnitOfWork(); using (var repository = _repositoryFactory.CreateContentRepository(uow)) { - if (contentTypeIds.Any() == false) - { - //Remove all Document records from the cmsContentXml table (DO NOT REMOVE Media/Members!) - uow.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN - (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml - INNER JOIN cmsDocument ON cmsContentXml.nodeId = cmsDocument.nodeId)"); + //First we're going to get the data that needs to be inserted before clearing anything, this + //ensures that we don't accidentally leave the content xml table empty if something happens + //during the lookup process. - list.AddRange(GetAllPublished()); - } - else - { - foreach (var id in contentTypeIds) - { - //first we'll clear out the data from the cmsContentXml table for this type - uow.Database.Execute(@"delete from cmsContentXml where nodeId in -(select cmsDocument.nodeId from cmsDocument - inner join cmsContent on cmsDocument.nodeId = cmsContent.nodeId - where published = 1 and contentType = @contentTypeId)", new {contentTypeId = id}); - - //now get all published content objects of this type and add to the list - list.AddRange(GetPublishedContentOfContentType(id)); - } - } + list.AddRange(contentTypeIds.Any() == false + ? GetAllPublished() + : contentTypeIds.SelectMany(GetPublishedContentOfContentType)); var xmlItems = new List(); foreach (var c in list) { - //generate the xml var xml = c.ToXml(); - //create the dto to insert - xmlItems.Add(new ContentXmlDto { NodeId = c.Id, Xml = xml.ToString(SaveOptions.None) }); + xmlItems.Add(new ContentXmlDto {NodeId = c.Id, Xml = xml.ToString(SaveOptions.None)}); } - //bulk insert it into the database - uow.Database.BulkInsertRecords(xmlItems); + //Ok, now we need to remove the data and re-insert it, we'll do this all in one transaction too. + using (var tr = uow.Database.GetTransaction()) + { + if (contentTypeIds.Any() == false) + { + //Remove all Document records from the cmsContentXml table (DO NOT REMOVE Media/Members!) + uow.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml + INNER JOIN cmsDocument ON cmsContentXml.nodeId = cmsDocument.nodeId)"); + } + else + { + foreach (var id in contentTypeIds) + { + //first we'll clear out the data from the cmsContentXml table for this type + uow.Database.Execute(@"delete from cmsContentXml where nodeId in +(select cmsDocument.nodeId from cmsDocument + inner join cmsContent on cmsDocument.nodeId = cmsContent.nodeId + where published = 1 and contentType = @contentTypeId)", new { contentTypeId = id }); + } + } + + //bulk insert it into the database + uow.Database.BulkInsertRecords(xmlItems, tr); + } } Audit.Add(AuditTypes.Publish, "RebuildXmlStructures completed, the xml has been regenerated in the database", 0, -1); } diff --git a/src/Umbraco.Tests/Services/PerformanceTests.cs b/src/Umbraco.Tests/Services/PerformanceTests.cs index e49d2a1da3..4a7ad2026a 100644 --- a/src/Umbraco.Tests/Services/PerformanceTests.cs +++ b/src/Umbraco.Tests/Services/PerformanceTests.cs @@ -211,6 +211,27 @@ namespace Umbraco.Tests.Services } } + //now, test truncating but then do bulk insertion of records + using (DisposableTimer.DebugDuration("Starting truncate + bulk insert test in one transaction")) + { + //do this 10x! + for (var i = 0; i < 10; i++) + { + //now we insert each record for the ones we've deleted like we do in the content service. + var xmlItems = nodes.Select(node => new ContentXmlDto { NodeId = node.NodeId, Xml = UpdatedXmlStructure }).ToList(); + + using (var tr = DatabaseContext.Database.GetTransaction()) + { + //clear all the xml entries + DatabaseContext.Database.Execute(@"DELETE FROM cmsContentXml WHERE nodeId IN + (SELECT DISTINCT cmsContentXml.nodeId FROM cmsContentXml + INNER JOIN cmsContent ON cmsContentXml.nodeId = cmsContent.nodeId)"); + + + DatabaseContext.Database.BulkInsertRecords(xmlItems, tr); + } + } + } }