Another possible solution is to build the Publish Queue programatically, then publish all at once as a single Publish operation. This method will then execute the publish:end
and publish:end:remote
events once. Events such as clearing the cache, updating indexes, then only happen once. It should be very quick.
I created some utilities to assist a while back: https://gist.github.com/jraps20/638312ea825f9a5ca5ee8da2c6ff92c1
public static class PublishUtils
{
public static void CreateAndPublishQueue(Database sourceDatabase, Database[] targetDatabases, Language[] targetLanguages, IEnumerable<ID> itemIds, bool skipEvents = false, bool useSecurityDisabler = true)
{
Assert.IsNotNull(sourceDatabase, "sourceDatabase");
Assert.IsNotNull(targetDatabases, "targetDatabases");
Assert.IsNotNull(targetLanguages, "targetLanguages");
var publishingCandidates = SetPublishingCandidates(sourceDatabase, targetDatabases, targetLanguages, itemIds);
ProcessCandidates(sourceDatabase, targetDatabases, targetLanguages, publishingCandidates, skipEvents, useSecurityDisabler);
}
private static List<PublishingCandidate> SetPublishingCandidates(Database sourceDatabase, Database[] targetDatabases, Language[] targetLanguages, IEnumerable<ID> itemIds)
{
var publishingCandidates = new List<PublishingCandidate>();
foreach (var itemId in itemIds)
{
publishingCandidates.AddRange(CreatePublishingCandidatesFromItemId(sourceDatabase, targetDatabases, targetLanguages, itemId));
}
return publishingCandidates;
}
public static List<PublishingCandidate> CreatePublishingCandidatesFromItemId(Database sourceDatabase, Database[] targetDatabases, Language[] targetLanguages, ID itemId)
{
if (sourceDatabase == null)
{
Log.Info("sourceDatabase == null", new object());
return Enumerable.Empty<PublishingCandidate>().ToList();
}
if (targetDatabases == null)
{
Log.Info("targetDatabases == null", new object());
return Enumerable.Empty<PublishingCandidate>().ToList();
}
if (targetLanguages == null)
{
Log.Info("targetLanguages == null", new object());
return Enumerable.Empty<PublishingCandidate>().ToList();
}
var publishingCandidates = new List<PublishingCandidate>();
var item = sourceDatabase.GetItem(itemId);
if (item == null)
{
Log.Info("item == null", new object());
return Enumerable.Empty<PublishingCandidate>().ToList();
}
foreach (var publishingTargetDatabase in targetDatabases.Where(tDb => tDb != null))
{
foreach (var publishingTargetLanguage in targetLanguages.Where(lang => lang != null))
{
var publishOptions = new PublishOptions(
Database.GetDatabase("master"),
publishingTargetDatabase,
PublishMode.Full,
publishingTargetLanguage,
DateTime.Now)
{
CompareRevisions = false
};
publishingCandidates.Add(new PublishingCandidate(itemId, publishOptions));
Log.Info($"Added item {item.Paths.FullPath} to the publish queue, target language: {publishingTargetLanguage}, targetDatabase: {publishingTargetDatabase}", new object());
}
}
return publishingCandidates;
}
public static void ProcessCandidates(Database sourceDatabase, Database[] targetDatabases, Language[] targetLanguages, List<PublishingCandidate> publishingCandidates, bool skipEvents = false, bool useSecurityDisabler = true)
{
Assert.IsNotNull(sourceDatabase, "sourceDatabase");
Assert.IsNotNull(targetDatabases, "targetDatabases");
Assert.IsNotNull(targetLanguages, "targetLanguages");
Log.Info($"{publishingCandidates.Count} items added to publish queue", new object());
var triggerDatabase = targetDatabases.First();
var triggerLanguage = targetLanguages.First();
var defaultPublishOptions = new PublishOptions(
sourceDatabase,
triggerDatabase, // need to pass database to satisfy contructor
PublishMode.Full,
triggerLanguage, // need to pass language to satisfy contructor
DateTime.Now)
{
CompareRevisions = false
};
var publishContext = PublishManager.CreatePublishContext(defaultPublishOptions);
// required or null exception thrown
publishContext.Languages = targetLanguages;
publishContext.Queue.Add(publishingCandidates);
if (skipEvents)
{
var queue = new ProcessQueue();
Log.Info("Processing Publish Queue, skipEvents = true", new object());
if (useSecurityDisabler)
{
Log.Info("Processing Publish Queue, security disabled", new object());
using (new SecurityDisabler())
queue.Process(publishContext);
}
else
{
Log.Info("Processing Publish Queue, security enabled", new object());
queue.Process(publishContext);
}
}
else
{
Log.Info("Processing Publish Queue, skipEvents = false", new object());
if (useSecurityDisabler)
{
Log.Info("Processing Publish Queue, security disabled", new object());
using (new SecurityDisabler())
CorePipeline.Run("publish", publishContext);
}
else
{
Log.Info("Processing Publish Queue, security enabled", new object());
CorePipeline.Run("publish", publishContext);
}
Log.Info("Raising publish:end event", new object());
global::Sitecore.Events.Event.RaiseEvent("publish:end", new Publisher(defaultPublishOptions));
Log.Info("Raising publish:end:remote event", new object());
global::Sitecore.Events.Event.RaiseEvent("publish:end:remote", new Publisher(defaultPublishOptions));
}
Log.Info("Finished processing Publish Queue", new object());
}
}
Usage
// set target database(s)
Database[] targetDatabases = { Database.GetDatabase("web")};
// get languages
var source = new List<Language>();
foreach (var database in targetDatabases)
{
source.AddRange(LanguageManager.GetLanguages(database));
}
var languages = source.Distinct().ToArray();
// set source database
var sourceDatabase = Database.GetDatabase("master");
// set Item Ids required to be published
ID[] ids = <populate with item ids>
// publish all as one operation
PublishUtils.CreateAndPublishQueue(sourceDatabase, targetDatabases, languages, ids);