You get a warning when you try to delete a file or EPiServer page if it is referenced form another page.
EPiServer uses EPiServer.DataAbstraction.SoftLink for this feature and all links to files, other EPiServer pages and external urls are stored here every time you publish a page.
It is also worth to notice that EPiServer built-in keyword table that is used for text searching pages also is updated during publish.
Corrupt data = Broken Links
The index can be corrupt or out of sync and then it is possible to delete a file or a page without getting a warning that it has incoming links. Text search can also fail or return wrong data.
I have seen this very often after a migration from EPiServer CMS 4. EPiServer CMS 4 databases tends to have really poor data for soft links.
The index is updated when you publish a page with a delay controled by indexingDelayAfterPublish setting. If the delay it is very large and the application restarts before the page is indexed, neither soft links or keywords will be updated.
How to repair EPiServer CMS soft link and keyword index
I have created an admin tool that we always run after migration from EPiServer 4 and also if we want to repair the index if the settings has been strange.
You sould set indexingDelayAfterPublish to a very large delay during the time you run the tool because if you try to use LaxyIndexer.IndexPage() in more than one thread at the same time you are guaranteed to get deadlocks or get other strange SQL exceptions sooner or later.
System.Data.SqlClient.SqlException:Violation of PRIMARY KEY constraint ‘PK_tblPageKeyword’. Cannot insert duplicate key in object ‘dbo.tblPageKeyword’.
Example code
[GuiPlugIn(DisplayName = "Reindex pages", Description = "Tool to index keywords and soft links", Area = PlugInArea.AdminMenu, Url = "~/Common/FixIndexing.aspx")] public partial class FixIndexing : System.Web.UI.Page { private static readonly ILog log; private static long QueueLength; private static long IndexedPages; private static DateTime Started = DateTime.MinValue; private static readonly Queue<PageData> Queue = new Queue<PageData>(); private static object LockRoot = new object(); static FixIndexing() { log = LogManager.GetLogger(typeof(FixIndexing)); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); UpdateStatus(); } private void UpdateStatus() { long q = Interlocked.Read(ref QueueLength); long n = Interlocked.Read(ref IndexedPages); if (Started != DateTime.MinValue) { StatusLabel.Text = string.Format("Started: {0} Indexed pages: {1} Queue: {2}", Started, n, q); } FixPageSoftLinkButton.Enabled = q <= 0; } protected void FixPageSoftLinkButton_Click(object sender, EventArgs e) { log.Info("Start LazyIndex of all pages!"); Started = DateTime.Now; ThreadPool.QueueUserWorkItem(ThreadProc); UpdateStatus(); } // This thread procedure performs the task. static void ThreadProc(Object stateInfo) { if (!Monitor.TryEnter(LockRoot)) return; //Job was already running try { IndexedPages = 0; PageData page; Queue.Enqueue(DataFactory.Instance.GetPage(PageReference.RootPage, LanguageSelector.MasterLanguage())); Interlocked.Increment(ref QueueLength); while ((page = Queue.Dequeue()) != null) { Interlocked.Decrement(ref QueueLength); try { log.Debug(string.Format("Indexing page {0}:{1}...", page.PageLink.ID, page.PageName)); LazyIndexer.IndexPage(page.PageLink.ID); Interlocked.Increment(ref IndexedPages); } catch (Exception ex) { int id = (stateInfo as PageData) != null ? ((PageData)stateInfo).PageLink.ID : -1; log.Error(string.Format("Error while indexing {0}", id), ex); } try { PageDataCollection pages = DataFactory.Instance.GetChildren(page.PageLink, LanguageSelector.MasterLanguage()); log.Debug(string.Format("Done indexing page {0}. Adding {1} children to Queue...", page.PageLink.ID, pages.Count)); foreach (PageData data in pages) { Queue.Enqueue(data); Interlocked.Increment(ref QueueLength); } } catch (Exception ex) { int id = (stateInfo as PageData) != null ? ((PageData)stateInfo).PageLink.ID : -1; log.Error(string.Format("Error while adding children for {0}", id), ex); } } } finally { Monitor.Exit(LockRoot); } } }
Index as Scheduled task
Another way to use the code above is to just add an Execute() method that calls ThreadProc and register it as a Scheduled Job plug-in.