Oct1

Business Data Columns : updating programmatically

Categories: SharePoint

When you add a Business Data Column and additional data fields to a list or library, setting the values of this column actually stores the data in the list, rather than a pointer to the BDC data. This means the BDC data is not refreshed automatically when a users views a document or the entire library, but actually relies on them clicking the little refresh button in the list. Of course getting the BDC data to update everytime the list/library is displayed may not be ideal as it could create a huge burden on your LOB System, but a setting would be nice so users could choose which way it would work.

Anyway, this blog post originates due to the fact that I've seen this problem enquired about, and also a number of people have asked how do you programmatically set BDC additional data fields in code. SharePoint must be able to do this as it does this exact functionality when you click on the little refresh button to refresh all your fields, hopefully this blog post might give you a little insight into how to use reflector and other tools incase you need to investigate how SharePoint does stuff, so you can use it or change it for your own solution.

Out of the box to refresh your Business Data Columns you need to click the little refresh button in the document library

image

When you click the refresh button you are taken to the page _layouts/BusinessDataSynchronizer.aspx where you are asked this operation may take some time, are you sure you want to continue. As we hopefully all know all the pages that are displayed from the _layouts directory come from the 12\TEMPLATE\LAYOUTS directory on the file system. We can find our BusinessDataSynchronizer.aspx page in the LAYOUTS directory and open it up in notepad. Right at the top of the code you'll find the controls and assemblies registered that are going to be used in the page. The one that should stick out at us is:

Microsoft.SharePoint.Portal.WebControls.BusinessDataSynchronizerPage

Now we know the namespace of the code we are looking for we can open Reflector (an application that should be in every SharePoint developers toolbox) and open the Microsoft.SharePoint.Portal dll and drill down to the namespace we require. The event that actually fires the synchronization is the btnSave click event so if we select that we'll see the code disassembled that SharePoint uses.

image

So the BusinessDataSynchronizerJob looks like the class we'd want to create and then run it's start method. Clicking on the BusinessDataSynchronizer link will take us to the code that is in that class...

But what do we see? They have marked the BusinessDataSynchronizer class as Internal? Why? Why leave it un-obfuscated but mark it as Internal? Grrrrrr (Adam I feel your pain! :-))

Well even though it is marked as Internal at least we can see what it does. And the answer is nothing that clever. It gets the data from the LOB System and then sets new values, iterating through the BDC Data Columns aditional fields also setting the values when it matches. Reflector does a good job of decompiliing the code in the BusinessDataSyncronizer class, but it does come up with a lot of GOTO and LABEL statements. Hopefully the BDC team didn't really include code like that and this is just Reflectors interpretation of 'foreach' and 'if' statements. But from this code we can see what they are doing, and we can piece it all together to perform the same work. The original question I had was how do you programatically set additional BDC Data Column fields, which this code will do, but I'm sure you'll be able to take it and fit it to whatever scenario you want to use it for. The code here will actually refresh every list item and the respective BDC data column although I'm sure you can see how to do it for one individual item if you needed. Also note I have hard coded the name of the SSP...

using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using Microsoft.SharePoint; using Microsoft.Office.Server.ApplicationRegistry.Infrastructure; using Microsoft.SharePoint.Portal.WebControls; using Microsoft.Office.Server.ApplicationRegistry.MetadataModel;
namespace SetBDCData { public partial class Form1 : Form { public Form1() { InitializeComponent(); }
 private void Form1_Load(object sender, EventArgs e) { SPSite siteCollection = new SPSite("http://localhost");
 SPWeb site = siteCollection.AllWebs["bdc"];
 SPList list = site.Lists["Tasks"];
 RefreshBDCFields refreshF = new RefreshBDCFields(); refreshF.List = list; refreshF.ColumnName = "Product";
 refreshF.DoWork(); 
 } }
 public class RefreshBDCFields { public SPList List = null; public string ColumnName = "";
 LobSystemInstance sysinst = null; Entity entity = null; Microsoft.Office.Server.ApplicationRegistry.MetadataModel.View specificFinderView = null; SPListItemCollection items = null;
 public RefreshBDCFields(SPList list, string columnName) { this.List = list; this.ColumnName = columnName; }
 public RefreshBDCFields() { }
 public void DoWork() { SPField fieldByInternalName = List.Fields.GetFieldByInternalName(this.ColumnName); if (!(fieldByInternalName is BusinessDataField)) { throw new BusinessDataListConfigurationException("The field " + this.ColumnName + " is not a business data field"); } BusinessDataField bizDataField = (BusinessDataField)fieldByInternalName;
 string[] secondaryFieldsNames = bizDataField.GetSecondaryFieldsNames(); string[] secondaryWssFieldNames = new string[0]; string property = bizDataField.GetProperty("SecondaryFieldWssNames");
 secondaryWssFieldNames = property.Split(new char[] { ':' });
 SqlSessionProvider.Instance().SetSharedResourceProviderToUse("SharedServices1");
 sysinst = ApplicationRegistry.GetLobSystemInstanceByName(bizDataField.SystemInstanceName); entity = sysinst.GetEntities()[bizDataField.EntityName]; specificFinderView = entity.GetSpecificFinderView(); items = List.Items;
 foreach (SPListItem item in items) { UpdateListItem(item, bizDataField, sysinst, entity, specificFinderView, secondaryFieldsNames, secondaryWssFieldNames); item.Update(); }
 }
 private void UpdateListItem(SPListItem item, BusinessDataField bizDataField, LobSystemInstance sysinst, Entity entity, Microsoft.Office.Server.ApplicationRegistry.MetadataModel.View view, string[] secondaryBdcFieldNames, string[] secondaryWssFieldNames) { string bdcFieldName = bizDataField.BdcFieldName; string encodedId = null; 
 object[] objArray; IList<object> identifierValues = null; object[] objArray2 = null; 
 List<Field>.Enumerator enumerator; encodedId = (string)item[bizDataField.RelatedField]; if (encodedId != null) { objArray = EntityInstanceIdEncoder.DecodeEntityInstanceId(encodedId); identifierValues = entity.FindSpecific(objArray, sysinst).GetIdentifierValues(); objArray2 = new object[identifierValues.Count]; }
 for (int i = 0; i < identifierValues.Count; i++) { objArray2[i] = identifierValues[i]; } 
 item[bizDataField.RelatedField] = EntityInstanceIdEncoder.EncodeEntityInstanceId(objArray2); enumerator = view.Fields.GetEnumerator();
 Microsoft.Office.Server.ApplicationRegistry.Runtime.IEntityInstance instance = entity.FindSpecific(objArray2, sysinst);
 Field field; string name;
 while (enumerator.MoveNext()) { field = enumerator.Current; name = field.Name; if (name == bdcFieldName) { item[bizDataField.InternalName] = Convert.ToString(instance.GetFormatted(field)); }
 for (int i = 0; i < secondaryBdcFieldNames.Length; i++) { if (secondaryBdcFieldNames[i] == field.Name) { item[secondaryWssFieldNames[i]] = Convert.ToString(instance.GetFormatted(field)); } }
 item.Update(); 
 }
 } }
}

Sure the code could do with a few comments and a bit more error checking but hopefully you get the jist.

So what could you do with this code? Well if you wanted to update Business Data Columns nightly or hourly why not wrap it up in site a Timer job? I'm still thinking of how it can be used to get LOB data every time a document library is viewed. Unfortunately there is nothing simple such as a View event, and of course updating the doc lib every time it was viewed may be hitting your LOB system quite a bit.

We're working on an administration Feature so you can setup a timer job to update these BDC fields at set times. What this space for more info on that soon.

 
 

Comments

On 17 Oct 2007 09:54, John Deary said:

It looks like the page only updates BDC columns that are attached to documents in a document library. Folders are excluded from the syncronization. My documents library happens to have BDC columns on the folders.

On 18 Oct 2007 04:31, John Deary said:

It turns out that this is easily remedied with the following code block. It can be added right after the original for each block: items = List.Folders; foreach (SPListItem item in items) { UpdateListItem(item, bizDataField, sysinst, entity, specificFinderView, secondaryFieldsNames, secondaryWssFieldNames); item.Update(); }

On 16 Jan 2008 08:45, Patrick said:

Thank you for the excellent article. We would be interested in any work you are doing on an administration feature to update the BDC fields at set times. Let me know how it is going.

On 11 Mar 2008 11:39, Yosry said:

Hello, Thanks for sharing this piece of code. I encountered one issue: when I ran the code using a Windows Forms application, it worked great; however, when I ran the code in an event handler, an exception was thrown saying "Access denied by BDC". The exception is thrown on this line: identifierValues = entity.FindSpecific(objArray, sysinst).GetIdentifierValues(); Seems to me like a permissions issue, but how can I go about fixing it? Thanks.

On 12 Aug 2008 11:56, Eric said:

This has saved my life, thank you so much! I added some error handling to this and used it to fix a problem where I couldn't even refresh the list using the OOTB functionality. I'll be blogging about this and linking back to this post soon. Thanks!

On 13 Aug 2008 04:04, Eric said:

I blogged here about a solution I created based on your post. http://www.esotericdelights.com/post.aspx?id=1290e814-f058-4e4e-955e-c888d213bc28 Thanks again!

Leave a comment