Après vous avoir parlé de ce que l'on pouvait faire avec les extensions de méthode et un peu de "réflection", j'aimerais vous montrer aujourd'hui une autre combinaison très intéressante :
Les extensions de méthode et ProcessBatchData : Voici un exemple avec une extension de méthode qui supprime tout les éléments d'une liste ou d'un bibliothèque de document.
Ci-dessous, je compare deux méthodes, la suppression classique d'éléments dans une liste et la suppression en utilisant l'extension de méthode DeleteAllItems.
using (SPSite sPSite = new SPSite("http://localhost"))
{
using (SPWeb sPWeb = sPSite.RootWeb)
{
SPList sPList;
Stopwatch stopWatch = new Stopwatch();
if (sPWeb.Lists.TryGet(STR_Annonces, out sPList))
{
Console.WriteLine("List : {0} Delete via Foreach\r\n\tBefore \tNB Items : {1}", STR_Annonces, sPList.ItemCount);
stopWatch.Start();
List<int> listId = new List<int>();
foreach (SPListItem item in sPList.Items)
listId.Add(item.ID);
foreach (int itemId in listId)
sPList.Items.DeleteItemById(itemId);
sPList.Update();
stopWatch.Stop();
Console.WriteLine("\tAfter \tNB Items : {1}\tElapsed : {2} Msec",
STR_Annonces,
sPList.ItemCount,
stopWatch.ElapsedMilliseconds);
stopWatch.Reset();
}
#region Rajout d'Elements à la liste STR_Annonces
#endregion
if (sPWeb.Lists.TryGet(STR_Annonces, out sPList))
{
Console.WriteLine("List : {0} Delete via Extension Method\r\n\tBefore \tNB Items : {1}", STR_Annonces, sPList.ItemCount);
stopWatch.Start();
sPList.DeleteAllItems(true, null);
stopWatch.Stop();
Console.WriteLine("\tAfter \tNB Items : {1}\tElapsed : {2} Msec",
STR_Annonces,
sPList.ItemCount,
stopWatch.ElapsedMilliseconds);
stopWatch.Reset();
}
}
}
En terme de performance, c'est assez impressionnant !
Voilà la méthode qui permet de réaliser ce petit tour de magie.
L'idée est d'utiliser ProcessBatchData pour effectuer sur un ensemble d'éléments la même action (ici, un Delete) en une seule fois. l'avantage de la méthode ProcessBatchData est qu'elle est extrêmement rapide (notamment car elle nécessite moins de traffics avec le serveur SQL).
Cependant, cette méthode n'est pas forcément des plus intuitives à utiliser et j'ai trouver intéressant de l'encapsuler dans une extension de méthode. L'ensemble de la requête est basé sur du CAML et l'utilisation de mot clef (SetList, Set Var, etc...)
PS : Dans les exemples ci-dessus, je n'utilise pas le paramètre metadata, j'en expliquerais l'utilité par la suite.
/// <summary>
/// Delete all the items specified via the metadata dictionnary (ID, FileUrl), FileUrl has to be null for SPList
/// If metadata is null, it will delete every items in the list
/// Use SPWeb.ProcessBatchData to execute the delete command
/// cf : http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.spweb.processbatchdata.aspx
/// </summary>
/// <param name="oList">The List</param>
/// <param name="forceUpdate">Will force the list update</param>
/// <param name="metadata">Dictionnary with Key = Id and Value = Fileurl (if needed)</param>
public static string DeleteAllItems(this SPList oList, bool forceUpdate, Dictionary<string, string> metadata)
{
bool isDocumentLivrary = oList is SPDocumentLibrary;
StringBuilder sbDelete = new StringBuilder(170 * oList.ItemCount);
sbDelete.Append("<?xml version=\"1.0\" encoding=\"UTF-8\"?><Batch OnError='Return'>");
StringBuilder cmd = new StringBuilder(200);
//Get the ListGuid, format to receive the Item ID and set the CMD to delete.
cmd.AppendFormat("<Method><SetList Scope=\"Request\">{0}</SetList><SetVar Name=\"ID\">{{0}}</SetVar><SetVar Name=\"Cmd\">Delete</SetVar>", oList.ID);
//Cmd Delete needs FileRef when it's a DocumentLibrary http://www.sharepointblogs.com/smc750/archive/2008/04/03/spweb-processbatchdata-a-list-is-a-list-is-a-list.aspx
if (isDocumentLivrary)
cmd.Append("<SetVar Name=\"owsfileref\">{1}</SetVar>");
cmd.Append("</Method>");
//Only items defined in the dictionnary will be deleted
if (metadata != null)
foreach (KeyValuePair<string, string> pair in metadata)
{
string itemId = pair.Key;
if (isDocumentLivrary)
sbDelete.Append(string.Format(cmd.ToString(), itemId, pair.Value));
else
sbDelete.Append(string.Format(cmd.ToString(), itemId));
}
else
foreach (SPListItem item in oList.Items)
{
string itemId = item.ID.ToString();
if (isDocumentLivrary)
sbDelete.Append(string.Format(cmd.ToString(), itemId, item.File.Url));
else
sbDelete.Append(string.Format(cmd.ToString(), itemId));
}
sbDelete.Append("</Batch>");
SPWeb oListParentWeb = oList.ParentWeb;
string batchResult = oListParentWeb.ProcessBatchData(sbDelete.ToString());
if (forceUpdate)
oList.Update();
//Dispose !!! http://blogs.msdn.com/rogerla/archive/2008/02/12/sharepoint-2007-and-wss-3-0-dispose-patterns-by-example.aspx
oListParentWeb.Dispose();
return ResultFromBatchCommand(batchResult);
}
private static string ResultFromBatchCommand(string batchResult)
{
string result = String.Empty;
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.LoadXml(batchResult);
XmlNodeList xmlErrNodes = xmlDoc.SelectNodes("/Results/Result/ErrorText");
int nbErrors = xmlErrNodes.Count;
if (nbErrors > 0)
result = xmlErrNodes[0].InnerText;
return result;
}
Vous l'aurez compris, c'est une méthode très intéressante dès que vous aurez une action à effectuer sur un ensemble d'éléments (ce qui arrive souvent avec SharePoint).
Pour revenir sur l'utilisation du paramètre metadata, j'avais envie de pouvoir supprimer simplement un ensemble d'éléments dans une liste. Pour ce faire, j'avais besoin de préciser l'ID et éventuellement l'URL vers le fichier (si c'est une DocumentLibrary) du coup, j'ai décider d'utiliser un dictionnary : La clef serait l'ID et éventuellement en valeur je stocke l'URL du fichier.
En pratique, si je souhaite supprimer tout les éléments de la liste Annonces qui ont pour couleur "Red", il me suffit de faire la requête CAML adéquate et de passer les informations nécessaires à ma méthode
if (sPWeb.Lists.TryGet(STR_Annonces, out sPList))
{
SPQuery query = new SPQuery();
query.Query = "<Where><Eq><FieldRef Name='Color' /><Value Type='Text'>Red</Value></Eq></Where><OrderBy />";
SPListItemCollection itemCollection = sPList.GetItems(query);
Dictionary<string, string> metadata = new Dictionary<string, string>();
foreach (SPListItem item in itemCollection)
metadata.Add(item.ID.ToString(), null);
Console.WriteLine("List : {0} Delete via Extension Method\r\n\tBefore \tNB Items : {1}", STR_Annonces, sPList.ItemCount);
sPList.DeleteAllItems(true, metadata);
Console.WriteLine("\tAfter \tNB Items : {1}", STR_Annonces, sPList.ItemCount);
}
Il me reste encore quelques détails à re-travailler sur cette méthode (notamment gérer les erreurs) , n'hésitez pas à me faire part de vos remarques/optimisations.
<Philippe/>