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
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.
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.