You might wonder what bottom up aggregation means in this context.. well.. let me explain this by using the following example:
Let’s say you have a portal with the following structure
and you want to aggregate all the information from the bottom (Product A) to the upper most level (Main Portal). But you don’t want any information from other products or even from Departments. Now by default the CQWP will aggregate everything under a given site/sitecollection.
So.. what to do? First we create our own version of the CQWP by inheriting from it like this:
public class BottomUpAggregationWebPart :
Microsoft.SharePoint.Publishing.WebControls.ContentByQueryWebPart
To change the result of the CQWP we can manipulate the DataTable that is created by the CQWP by using a delegate like this:
protected override void OnInit(EventArgs e)
{
this.ProcessDataDelegate += new ProcessData(modifyData);
base.OnInit(e);
}
private DataTable modifyData(DataTable dt)
{
//do something
return dt;
}
For every row in the DataTable there is a column called “WebId’. This GUID is , as you might have guessed, the ID of the web where the item belongs to. So, all we need to do is filter out all the rows where the WebId is not part of the parent chain. To determine which web is part of the chain I’ve created the following recursive method:
private void DetermineDepth(SPWeb web, ref List<Guid> webIds)
{
if (web != null)
{
if (web.Url != SPContext.Current.Site.RootWeb.Url)
{
webIds.Add(web.ID);
DetermineDepth(web.ParentWeb, ref webIds);
}
else
{
webIds.Add(SPContext.Current.Site.RootWeb.ID);
}
}
}
Next is the filtering itself which looks this
private DataTable modifyData(DataTable dt)
{
//Getting a List with all the SPWeb.ID's from the current SPWeb to the RootWeb of the SiteCollection
List<Guid> webIds = null;
webIds = new List<Guid>();
DetermineDepth(SPContext.Current.Web, ref webIds);
webIds.Sort();
List<DataRow> rowsToDelete = new List<DataRow>();
foreach (DataRow row in dt.Rows)
{
if (!webIds.Contains(new Guid(row["WebId"].ToString())))
{
rowsToDelete.Add(row);
}
}
//Delete the rows
foreach (DataRow row in rowsToDelete)
{
row.Delete();
}
return dt;
}
And that’s it! ;)
The only thing to keep in mind is that when you have set the item limit in the presentation settings, this item limit is set before you have the chance to modify the DataTable. So it’s best to to not have an item limit at all or a modified one. If you wish to modify the property in code, here’s the snippet which you can use:
this.ItemLimit = -1;
While I’m covering the subject of the CQWP, I also want to share some experiences and assumptions that I had when dealing with it. (Please note that for the most time when I’m developing with SharePoint I really tend to stick to WSS ;)
One of the assumptions I had was that the CQWP aggregated based on a given content type OR list type/template in the properties of the webpart. However, as I found out, this is not the case.. it’s an AND operation.. so when setting these properties programmatically it took me a while to figure out which ones to use. But fortunately enough I managed ;)
- ServerTemplate, the template ID of the list/document library. So 101 for Document Libraries for example
- ContentTypeName, the name of the ContentType
- ContentTypeBeginsWithId, the id of the Parent ContentType. Please note here, that in some cases you need to have the Parent of the Parent of the Child ContentType due the fact that when a ContentType is added to a list, it automatically is being marked as a child.
To conclude this post, here are some articles that helped get going: