Tags

A question on Sitecore Community on how to automate migration of content from Sitecore 6.5 to Sitecore 8 led me to write this post. The idea I proposed was to set the Sitecore 6.5 master database as an additional database in the Sitecore 8 instance and this way be able to read the content from Sitecore 6.5 and switch to the Sitecore 8 db to write that content on it. I was not sure whether such idea would actually work or not, that’s why I decided to put it to a test and write a small PoC on it.

One of the suggestions on how to do it was to simply create a package with Sitecore Package Designer from Sitecore 6.5 and install it on Sitecore 8. However, as it is common on such scenarios, the new solution on Sitecore 8 didn’t have the same architecture, therefore simply packing and installing would not resolve the problem. Templates and fields had actually changed.

In my PoC I am working with a simple but common scenario of architectural change in Sitecore. In this scenario we have one Sitecore 6.5 template with a pre-defined and limited set of fields for only 3 slides, while the new solution on Sitecore 8 we have two templates, with one of the templates being specific for slides, allowing for not just three but multiple slides to be added.

In this PoC I am using Sitecore version 6.5 for the source and and Sitecore 8.1 as my target.

Source: Sitecore 6.5

Template name: Page65

 Field  Type
 Title Single-Line Text
 Content  Rich Text
 Slide1  Image
 Slide2  Image
 Slide3  Image
Target: Sitecore 8.1

Template name: Page81

 Field  Type
 Title Single-Line Text
 Content  Rich Text

Template name: Slide81

Field  Type
Image  Image

Our approach will be the following:

  1. In Sitecore 8.1:
    1. add a connection string to our ConnectionStrings.config file to connect to Sitecore 6.5 master db
    2. add a site using the new connection string to our Sitecore.config file (web.config in previous versions).
  2. Migrate media items first (to ensure we have the right link mappings for the Image fields)
  3. Migrate the Page65 items to Page81 items created on the fly (in a loop going through all Page65 items from Sitecore 6.5) moving Title to Title and Content to Content.
  4. Create a new Slide81 items for each Slide field being used in the Page65 item, moving Slide1, Slide2 or Slide3 to the Image field of the Slide81 item

For the sake of simplicity I created two Page65 items and three images in the Sitecore 65’s Media Library. This is our source content:

Capture

The ConnectionString file in Sitecore 8.1 will look like this:

<?xml version="1.0" encoding="utf-8"?>
<connectionStrings>
 <!-- 
 Sitecore connection strings.
 All database connections for Sitecore are configured here.
 -->
 <add name="core" connectionString="user id=sa;password=pw;Data Source=LAPTOP\SQLEXP;Database=Sitecore811_Core" />
 <add name="master" connectionString="user id=sa;password=pw;Data Source=LAPTOP\SQLEXP;Database=Sitecore811_Master" />
 <add name="master65" connectionString="user id=sa;password=pw;Data Source=LAPTOP\SQLEXPRESS;Database=sc65Sitecore_Master"/>
 <add name="web" connectionString="user id=sa;password=pw;Data Source=LAPTOP\SQLEXP;Database=Sitecore811_Web" />
 <add name="analytics" connectionString="mongodb://localhost/sc811_analytics" />
 <add name="tracking.live" connectionString="mongodb://localhost/sc811_tracking_live" />
 <add name="tracking.history" connectionString="mongodb://localhost/sc811_tracking_history" />
 <add name="tracking.contact" connectionString="mongodb://localhost/sc811_tracking_contact" />
 <add name="reporting" connectionString="user id=sa;password=pw*;Data Source=LAPTOP\SQLEXP;Database=Sitecore811_Analytics" />
</connectionStrings>

The Sitecore.config file additional site (under <databases>):

 <!-- master 65 -->
 <database id="master65" singleInstance="true" type="Sitecore.Data.Database, Sitecore.Kernel">
 <param desc="name">$(id)</param>
 <icon>Images/database_master.png</icon>
 <dataProviders hint="list:AddDataProvider">
 <dataProvider ref="dataProviders/main" param1="$(id)">
 <prefetch hint="raw:AddPrefetch">
 <sc.include file="/App_Config/Prefetch/Common.config" />
 <sc.include file="/App_Config/Prefetch/Master65.config" />
 </prefetch>
 </dataProvider>
 </dataProviders>
 <securityEnabled>true</securityEnabled>
 <proxiesEnabled>false</proxiesEnabled>
 <publishVirtualItems>true</publishVirtualItems>
 <proxyDataProvider ref="proxyDataProviders/main" param1="$(id)" />
 <workflowProvider hint="defer" type="Sitecore.Workflows.Simple.WorkflowProvider, Sitecore.Kernel">
 <param desc="database">$(id)</param>
 <param desc="history store" ref="workflowHistoryStores/main" param1="$(id)" />
 </workflowProvider>
 <archives hint="raw:AddArchive">
 <archive name="archive" />
 <archive name="recyclebin" />
 </archives>
 <Engines.HistoryEngine.Storage>
 <obj type="Sitecore.Data.$(database).$(database)HistoryStorage, Sitecore.Kernel">
 <param connectionStringName="$(id)" />
 <EntryLifeTime>30.00:00:00</EntryLifeTime>
 </obj>
 </Engines.HistoryEngine.Storage>
 <Engines.HistoryEngine.SaveDotNetCallStack>false</Engines.HistoryEngine.SaveDotNetCallStack>
 <NotificationProvider type="Sitecore.Data.DataProviders.$(database).$(database)NotificationProvider, Sitecore.Kernel">
 <param connectionStringName="$(id)">
 </param>
 <param desc="databaseName">$(id)</param>
 </NotificationProvider>
 <cacheSizes hint="setting">
 <data>100MB</data>
 <items>50MB</items>
 <paths>2500KB</paths>
 <itempaths>50MB</itempaths>
 <standardValues>2500KB</standardValues>
 </cacheSizes>
 </database>

Prefetch/Master65.config is just a copy of Prefetch/Master.config. It could possibly just point to the same config file.

Not let’s go to our code. (image screenshots first; text version can be found below in case you want to copy and paste)

First we get references to the databases, starting items (media library and content) and target templates to be used.

Capture

Now we loop through the Media Library items that are children items of Media Library/Images node in Sitecore 6.5 and we migrate them first:

Capture

Finally we migrate the content items that are children items of our Site65 node in Sitecore 6.5 (a real site with multiple nodes would require, of course, a recursive loop to go deeper down in the three and migrate everything, it would also require the appropriate conditions to check the template of each item being read, in our simple example all items are Page65 and they are all children items of Site65):

Capture

And we create one Slide item for each one of the three slide fields from Sitecore 6.5 having content:

Capture

And after using this code in a sub-layout or MVC rendering and executing it, this is the result in Sitecore 8.1:

Untitled

A screenshot of the Slide1 item under Page65-1:Untitled

Some important observations:

  • All content from the Rich-text field is being migrate as is. In a real site migration scenario, where a new visual design is in place, there is probably a lot of cleaning required to be made on any possible HTML and inline styles on the content of the rich-text field. Additional logic for such cleaning should be applied to these fields following the requirements for the new design.
  • The purpose of this post was just to test the concept of having Sitecore’s 6.5 and 8.1 databases being used together in a Sitecore 8.1 environment for content migration purposes. Internal links were not addressed in this post, I plan to do it in a future post. Basically the problem is that you can’t simply direct migrate the content of link fields because in Sitecore 8.1 the target items will have different GUIDs. The recommended approach is that you have the target content tree (skeleton site) created first in Sitecore 8.1 prior to executing the full migration. This way you can get a reference for each target path and get its new GUID when migrating such fields.
  • This approach is also based on the assumption that the source and target content trees will follow the same pattern. On scenarios where the Information Architecture is different it will also be required to have a mapping matrix in place (e.g. /about-us/careers from Sitecore 6.5 maps to /our-company/opportunities/careers in the new Sitecore 8.1 sitemap).
  • In this simple PoC I just worked with images. Additional logic is required for different media types (videos, pdfs, etc.)

If you have any other observations to be considered by people reading this post, feel free to send them in a comment and I will get them published.

Here it is the complete code I used:

 public bool PerformMigration()
 {
 // get reference to the two master databases (Sitecore 6.5 and sitecore 8.1)
 Sitecore.Data.Database dbMaster65 = Sitecore.Data.Database.GetDatabase("master65");
 Sitecore.Data.Database dbMaster81 = Sitecore.Data.Database.GetDatabase("master");

 // get references to the media folders
 Item source_media_item = dbMaster65.GetItem("/sitecore/media library/Images");
 Item target_media_item = dbMaster81.GetItem("/sitecore/media library/Images");
 string target_media_item_path = "/sitecore/media library/Images";

 // get references to the root items
 Item source_root_item = dbMaster65.GetItem("/sitecore/content/Home/Site65");
 Item target_root_item = dbMaster81.GetItem("/sitecore/content/Home/Site81");

 // get references to the templates to be used on Sitecore 8.1 based on their GUID
 TemplateItem page81 = dbMaster81.GetTemplate("103D265E-AF04-44A9-8046-53A765E8C150"); 
 TemplateItem slide81 = dbMaster81.GetTemplate("CE9C91BE-42B9-4F52-A3ED-97203B2B4E28"); 

 // first we migrate the media items
 foreach (Sitecore.Data.Items.Item source_media in source_media_item.Children)
 {
 using (new SecurityDisabler())
 {
 // Media options to create the media item in Sitecore 8.1
 Sitecore.Resources.Media.MediaCreatorOptions options = 
 new Sitecore.Resources.Media.MediaCreatorOptions();
 options.FileBased = false;
 options.IncludeExtensionInItemName = false;
 options.Versioned = true;
 options.Destination = target_media_item_path + "/" + source_media.Name;
 options.Database = dbMaster81;

 // Read the media item stream from Sitecore 6.5
 Stream memoryStream = new MemoryStream();
 MediaItem source = dbMaster65.GetItem(source_media.ID);
 memoryStream = source.GetMediaStream();

 // Recreate the item in Sitecore 8.1 using the stream
 Item new_media_item = 
 Sitecore.Resources.Media.MediaManager.
 Creator.CreateFromStream(memoryStream, source_media.Name, options);

 // Update the media item fields in sitecore 8.1 with the content from Sitecore 6.5
 new_media_item.Editing.BeginEdit();
 new_media_item.Fields["Title"].Value = source_media.Fields["Title"].Value;
 new_media_item.Fields["Keywords"].Value = source_media.Fields["Keywords"].Value;
 new_media_item.Fields["Description"].Value = source_media.Fields["Description"].Value;
 new_media_item.Fields["Extension"].Value = source_media.Fields["Extension"].Value;
 new_media_item.Fields["Mime Type"].Value = source_media.Fields["Mime Type"].Value;
 new_media_item.Fields["Size"].Value = source_media.Fields["Size"].Value;
 new_media_item.Fields["Format"].Value = source_media.Fields["Format"].Value;
 new_media_item.Editing.EndEdit();
 new_media_item.Editing.AcceptChanges();
 }
 }

 // next we migrate the content items
 foreach (Sitecore.Data.Items.Item source_item in source_root_item.Children)
 {
 using (new SecurityDisabler())
 {
 Item new_page_item = target_root_item.Add(source_item.Name, page81);

 new_page_item.Editing.BeginEdit();
 new_page_item.Fields["Title"].Value = source_item.Fields["Title"].Value;
 new_page_item.Fields["Content"].Value = source_item.Fields["Content"].Value;
 new_page_item.Editing.EndEdit();
 new_page_item.Editing.AcceptChanges();

 if (source_item.Fields["Slide1"].Value != "")
 {
 Item new_slide1 = new_page_item.Add("Slide1", slide81);

 new_slide1.Editing.BeginEdit();
 ImageField source_image = source_item.Fields["Slide1"];
 MediaItem target_image = 
 dbMaster81.GetItem(target_media_item_path + "/" + source_image.MediaItem.Name);
 ((ImageField)new_slide1.Fields["Image"]).MediaID = target_image.ID;
 new_slide1.Editing.EndEdit();
 new_slide1.Editing.AcceptChanges();
 }

 if (source_item.Fields["Slide2"].Value != "")
 {
 Item new_slide1 = new_page_item.Add("Slide2", slide81);

 new_slide1.Editing.BeginEdit();
 ImageField source_image = source_item.Fields["Slide2"];
 MediaItem target_image = 
 dbMaster81.GetItem(target_media_item_path + "/" + source_image.MediaItem.Name);
 ((ImageField)new_slide1.Fields["Image"]).MediaID = target_image.ID;
 new_slide1.Editing.EndEdit();
 new_slide1.Editing.AcceptChanges();
 }

 if (source_item.Fields["Slide3"].Value != "")
 {
 Item new_slide1 = new_page_item.Add("Slide3", slide81);

 new_slide1.Editing.BeginEdit();
 ImageField source_image = source_item.Fields["Slide3"];
 MediaItem target_image = 
 dbMaster81.GetItem(target_media_item_path + "/" + source_image.MediaItem.Name);
 ((ImageField)new_slide1.Fields["Image"]).MediaID = target_image.ID;
 new_slide1.Editing.EndEdit();
 new_slide1.Editing.AcceptChanges();
 }
 }
 }
 return true;
 }

Hope this post may be helpful to you! Thanks.