ProcessBatchData Helper Class
There are a few ways to add multiple rows to a SharePoint list. The iterative approach (i.e.: line by line) is simple, but this pattern will fail a CAF report. The better way to get the data up is loading a batch file using the ProcessBatchData method on your trusty SPWeb object. (The official blurb on the method is here.)
The problem with this is that is requires a well formatted XML string. The easiest way to accomplish this is to writer a helper class and it is quite a life saver.
Here is the class, followed by a sample of how to use the code.
public class ProcessBatchDataHelper
{
private const string ProcessBatchStartXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
private const string ProcessBatchRowsStartXml = "<ows:Batch OnError=\"Continue\">";
private const string ProcessBatchEndXml = @"</ows:Batch>";
private List<BatchProcessMethod> _methods = new List<BatchProcessMethod>();
private int _methodId;
public ProcessBatchDataHelper()
{
}
private string GetMethodId()
{
return String.Format("M{0}", ++_methodId);
}
public BatchProcessMethod NewAddMethod(Guid listId)
{
var method = new BatchProcessAddMethod(GetMethodId(), listId.ToString("D"));
_methods.Add(method);
return method;
}
public BatchProcessMethod NewUpdateMethod(Guid listId, string itemId)
{
var method = new BatchProcessUpdateMethod(GetMethodId(), listId.ToString("D"), itemId);
_methods.Add(method);
return method;
}
public void NewDeleteMethod(Guid listId, string itemId)
{
var method = new BatchProcessDeleteMethod(GetMethodId(), listId.ToString("D"), itemId);
_methods.Add(method);
}
public string ToCAML()
{
StringBuilder caml = new StringBuilder(10000);
caml.AppendLine(ProcessBatchDataHelper.ProcessBatchStartXml);
caml.AppendLine(ProcessBatchDataHelper.ProcessBatchRowsStartXml);
_methods.ForEach(method => caml.AppendLine(method.ToCAML()));
caml.AppendLine(ProcessBatchDataHelper.ProcessBatchEndXml);
return caml.ToString();
}
}
public enum BatchProcessCommand { Save, Delete };
public abstract class BatchProcessMethod
{
private const string ProcessBatchSetVarXml = "\t\t<SetVar Name=\"urn:schemas-microsoft-com:office:office#{0}\">{1}</SetVar>";
private const string ProcessBatchMethodEndXml = "\t</Method>";
private const string ProcessBatchMethodStartXml = "\t<Method ID=\"{0}\"><SetList>{1}</SetList><SetVar Name=\"ID\">{2}</SetVar><SetVar Name=\"Cmd\">{3}</SetVar>";
private string _methodId;
private string _listId;
private string _itemId;
private BatchProcessCommand _cmd;
private List<string> _setVars = new List<string>();
protected BatchProcessMethod(string methodId, string listId, string itemId, BatchProcessCommand cmd)
{
_methodId = methodId;
_listId = listId;
_itemId = itemId;
_cmd = cmd;
}
public void SetVar(string varName, string val)
{
_setVars.Add(String.Format(BatchProcessMethod.ProcessBatchSetVarXml, varName, val ?? String.Empty));
}
public string ToCAML()
{
StringBuilder caml = new StringBuilder(5000);
caml.AppendLine(String.Format(BatchProcessMethod.ProcessBatchMethodStartXml, _methodId, _listId, _itemId, _cmd.ToString()));
_setVars.ForEach(var => caml.AppendLine(var));
caml.AppendLine(BatchProcessMethod.ProcessBatchMethodEndXml);
return caml.ToString();
}
}
public sealed class BatchProcessAddMethod : BatchProcessMethod
{
internal BatchProcessAddMethod(string methodId, string listId)
: base(methodId, listId, "New", BatchProcessCommand.Save)
{
}
}
public sealed class BatchProcessUpdateMethod : BatchProcessMethod
{
internal BatchProcessUpdateMethod(string methodId, string listId, string itemId)
: base(methodId, listId, itemId, BatchProcessCommand.Save)
{
}
}
public sealed class BatchProcessDeleteMethod : BatchProcessMethod
{
internal BatchProcessDeleteMethod(string methodId, string listId, string itemId)
: base(methodId, listId, itemId, BatchProcessCommand.Delete)
{
}
}
The following code does a batch update for the list "list" to the second list "list2" (yes, I know the names are AWESOME). The portion to be aware of is the item loop - I am only setting the Title column, but it can easily be extended. I have added a helpful comment to point you in the right direction.
ProcessBatchData requires the internal column name - I have created a helper method called 'InternalName', which will extract the name from the title.
NOTE: This method does not take into account the size of the source list - if there are 1000000 rows, it will be done in a single batch. It would probably be best to send the records in batches of 100.
private void ProcessBatch()
{
string sourceListName = "list";
string targetListName = "list2";
using (SPSite site = new SPSite(@"http://mysite/"))
{
using (SPWeb web = site.RootWeb)
{
SPList sourceList = web.Lists[sourceListName];
SPList targetList = web.Lists[targetListName];
ProcessBatchDataHelper cmd = new ProcessBatchDataHelper();
SPListItemCollection items = sourceList.Items;
foreach (SPListItem item in items)
{
BatchProcessMethod method = cmd.NewAddMethod(targetList.ID);
string titleField = InternalName("Title", sourceList);
string titleValue = item[titleField].ToString();
method.SetVar(titleField, titleValue);
// Add additional columns here
// method.SetVar(secondFieldInternalName, secondFieldValue);
}
web.ProcessBatchData(cmd.ToCAML());
}
}
}
private string InternalName(string displayName, SPList sourceList)
{
SPFieldCollection fields = sourceList.Fields;
if (fields.ContainsField(displayName))
{
if (string.Compare(displayName, "Title", true) == 0)
return "Title";
else
return fields[displayName].InternalName;
}
return string.Empty;
}
The problem with this is that is requires a well formatted XML string. The easiest way to accomplish this is to writer a helper class and it is quite a life saver.
Here is the class, followed by a sample of how to use the code.
public class ProcessBatchDataHelper
{
private const string ProcessBatchStartXml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>";
private const string ProcessBatchRowsStartXml = "<ows:Batch OnError=\"Continue\">";
private const string ProcessBatchEndXml = @"</ows:Batch>";
private List<BatchProcessMethod> _methods = new List<BatchProcessMethod>();
private int _methodId;
public ProcessBatchDataHelper()
{
}
private string GetMethodId()
{
return String.Format("M{0}", ++_methodId);
}
public BatchProcessMethod NewAddMethod(Guid listId)
{
var method = new BatchProcessAddMethod(GetMethodId(), listId.ToString("D"));
_methods.Add(method);
return method;
}
public BatchProcessMethod NewUpdateMethod(Guid listId, string itemId)
{
var method = new BatchProcessUpdateMethod(GetMethodId(), listId.ToString("D"), itemId);
_methods.Add(method);
return method;
}
public void NewDeleteMethod(Guid listId, string itemId)
{
var method = new BatchProcessDeleteMethod(GetMethodId(), listId.ToString("D"), itemId);
_methods.Add(method);
}
public string ToCAML()
{
StringBuilder caml = new StringBuilder(10000);
caml.AppendLine(ProcessBatchDataHelper.ProcessBatchStartXml);
caml.AppendLine(ProcessBatchDataHelper.ProcessBatchRowsStartXml);
_methods.ForEach(method => caml.AppendLine(method.ToCAML()));
caml.AppendLine(ProcessBatchDataHelper.ProcessBatchEndXml);
return caml.ToString();
}
}
public enum BatchProcessCommand { Save, Delete };
public abstract class BatchProcessMethod
{
private const string ProcessBatchSetVarXml = "\t\t<SetVar Name=\"urn:schemas-microsoft-com:office:office#{0}\">{1}</SetVar>";
private const string ProcessBatchMethodEndXml = "\t</Method>";
private const string ProcessBatchMethodStartXml = "\t<Method ID=\"{0}\"><SetList>{1}</SetList><SetVar Name=\"ID\">{2}</SetVar><SetVar Name=\"Cmd\">{3}</SetVar>";
private string _methodId;
private string _listId;
private string _itemId;
private BatchProcessCommand _cmd;
private List<string> _setVars = new List<string>();
protected BatchProcessMethod(string methodId, string listId, string itemId, BatchProcessCommand cmd)
{
_methodId = methodId;
_listId = listId;
_itemId = itemId;
_cmd = cmd;
}
public void SetVar(string varName, string val)
{
_setVars.Add(String.Format(BatchProcessMethod.ProcessBatchSetVarXml, varName, val ?? String.Empty));
}
public string ToCAML()
{
StringBuilder caml = new StringBuilder(5000);
caml.AppendLine(String.Format(BatchProcessMethod.ProcessBatchMethodStartXml, _methodId, _listId, _itemId, _cmd.ToString()));
_setVars.ForEach(var => caml.AppendLine(var));
caml.AppendLine(BatchProcessMethod.ProcessBatchMethodEndXml);
return caml.ToString();
}
}
public sealed class BatchProcessAddMethod : BatchProcessMethod
{
internal BatchProcessAddMethod(string methodId, string listId)
: base(methodId, listId, "New", BatchProcessCommand.Save)
{
}
}
public sealed class BatchProcessUpdateMethod : BatchProcessMethod
{
internal BatchProcessUpdateMethod(string methodId, string listId, string itemId)
: base(methodId, listId, itemId, BatchProcessCommand.Save)
{
}
}
public sealed class BatchProcessDeleteMethod : BatchProcessMethod
{
internal BatchProcessDeleteMethod(string methodId, string listId, string itemId)
: base(methodId, listId, itemId, BatchProcessCommand.Delete)
{
}
}
The following code does a batch update for the list "list" to the second list "list2" (yes, I know the names are AWESOME). The portion to be aware of is the item loop - I am only setting the Title column, but it can easily be extended. I have added a helpful comment to point you in the right direction.
ProcessBatchData requires the internal column name - I have created a helper method called 'InternalName', which will extract the name from the title.
NOTE: This method does not take into account the size of the source list - if there are 1000000 rows, it will be done in a single batch. It would probably be best to send the records in batches of 100.
private void ProcessBatch()
{
string sourceListName = "list";
string targetListName = "list2";
using (SPSite site = new SPSite(@"http://mysite/"))
{
using (SPWeb web = site.RootWeb)
{
SPList sourceList = web.Lists[sourceListName];
SPList targetList = web.Lists[targetListName];
ProcessBatchDataHelper cmd = new ProcessBatchDataHelper();
SPListItemCollection items = sourceList.Items;
foreach (SPListItem item in items)
{
BatchProcessMethod method = cmd.NewAddMethod(targetList.ID);
string titleField = InternalName("Title", sourceList);
string titleValue = item[titleField].ToString();
method.SetVar(titleField, titleValue);
// Add additional columns here
// method.SetVar(secondFieldInternalName, secondFieldValue);
}
web.ProcessBatchData(cmd.ToCAML());
}
}
}
private string InternalName(string displayName, SPList sourceList)
{
SPFieldCollection fields = sourceList.Fields;
if (fields.ContainsField(displayName))
{
if (string.Compare(displayName, "Title", true) == 0)
return "Title";
else
return fields[displayName].InternalName;
}
return string.Empty;
}
Comments
Post a Comment