Adding BDC Columns to a Sharepoint List Programatically – working version

I have been working on a SharePoint project that currently requires over a hundred SharePoint lists to contain the data.  Each list consists of the columns from a Content type plus the data from a singe BDC entity.
To set that lot up by hand (which out of the box is the only way) would be far too time consuming. All of my lists exist at the root site level so that each of my various subsites may access them.

This code will create the list with all of the columns from the Content Type and all of the columns from the BDC Entity.  It then updates the default view of the list to show all of these.

This code does use one class that microsoft has marked as do not use in your own application.  But since it is the only way that I have found to get this to work….

Identifying the required using clauses is left as an exercise to the reader.

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Xml;

using Microsoft.SharePoint;

using Microsoft.Office.Server;

using Microsoft.Office.Server.ApplicationRegistry.MetadataModel;

using Microsoft.Office.Server.ApplicationRegistry.Infrastructure;

using Microsoft.SharePoint.Portal;

using Microsoft.SharePoint.Portal.WebControls;

 

namespace AddSharePointBDCListNS

{

   

    /// <summary>

    /// This console application will add a SharePoint list to a site based upon a content type and a bdc entity.

    /// The content type will contain the key field for the bdc data so that the bdc data can be removed and replaced.

    /// </summary>

    class Program

    {

        /// <summary>

        ///

        /// </summary>

        /// <param name=”args”></param>

        static void Main(string[] args)

        {

           

            if (args.Length != 8)

            {

                Console.WriteLine(“AddSharePointBDCList http://site:1234 listName ssp contentType CoreInstance core.Trade_BigList_v TradeRef Trade”);

            }

            else

            {

                try

                {

                    AddSharePointBDCList(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);

                }

                catch (Exception ex)

                {

                    Console.WriteLine(ex);

                }

            }

           

        }

 

        static string ObtainBdcFields(string application,

                                      string entity,

                                      string entityTitle)

        {

            LobSystemInstance instance = ApplicationRegistry.GetLobSystemInstanceByName(application);           

            Entity requiredEntity = instance.GetEntities()[entity];

 

            List<string> rawField = new List<string>();

 

            foreach (Field field in requiredEntity.GetFinderView().Fields)

            {

                rawField.Add(field.Name);

            }

 

            rawField.Sort();

            StringBuilder fieldList = new StringBuilder();

            bool isFirst = true;

            foreach (string field in rawField)

            {

                if (!isFirst)

                {

                    fieldList.Append(“:”);

                }

                isFirst = false;

                fieldList.Append(field);

            }

            return fieldList.ToString();

        }

       

        /// <remarks>

        /// This is serious black belt sharepoint.  It required a lot of trial and error to get this far.

        /// I would like to be able to simply pass the application and the entity but have run out of time.

        /// </remarks>

        ///<summary>

        ///<param name=”siteUrl”></param>

        ///

        ///</summary>

        static void AddSharePointBDCList(string siteUrl,

                                         string listName,

                                         string ssp,

                                         string contentType,

                                         string bdcSystemInstance,

                                         string bdcEntity,

                                         string bdcField,

                                         string bdcDisplayField)

        {

            SPSite site;

            try

            {

                site = new SPSite(siteUrl);

            }

            catch (Exception ex)

            {

                throw new Exception(“Unable to find site (” + siteUrl + “)”, ex);

            }

           

            SPWeb web = site.OpenWeb();           

 

            SPContentType newCT;

            try

            {

                newCT = web.ContentTypes[contentType];

            }

            catch (Exception ex)

            {

                throw new Exception(“Unable to find content type (” + contentType + “)”, ex);

            }

 

            // I have moved these here since it will prevent a list being created if any of the data is wrong.

            try

            {

                SqlSessionProvider.Instance().SetSharedResourceProviderToUse(ssp);

            }

            catch(Exception ex)

            {

                throw new Exception(“Unable to find SSP (” + ssp + “)”, ex);

            }

 

            string rawFields;

 

            try

            {

                rawFields = ObtainBdcFields(bdcSystemInstance, bdcEntity, bdcDisplayField);

            }

            catch (Exception ex)

            {

                throw new Exception(“There is something wrong with at least one of the following:: bdcSystemInstance: ” + bdcSystemInstance + ” bdcEntity: ” + bdcEntity + ” bdcDisplayField: ” + bdcDisplayField , ex);

            }

 

            /* At this point we have all of the data that we need to recreate the lists! */

 

            // You can’t create a list based upon a user-defined content type but you can do this:

            // Add a generic list

            Guid listGuid = web.Lists.Add(listName, listName, SPListTemplateType.GenericList);

            // … Why that did’nt return the list I’ll never know, use the guid instead

            SPList list = web.Lists[listGuid];

 

            // Now mess with the content types:

 

                    SPContentType old = list.ContentTypes[0];

                    list.ContentTypes.Add(newCT);

                    list.ContentTypes.Delete(old.Id);

                    list.Update();

 

            // Now add the missing fields to the view.

            SPView view = list.DefaultView;

                    foreach( SPField field in newCT.Fields)

            {

                                if (!view.ViewFields.Exists(field.InternalName))

                {

                                            view.ViewFields.Add(field);

                }

            }

            view.Update();

 

 

         /* I think that this is now minimal */

         const string contentTemplate = @”<Field Type=’BusinessData’

         DisplayName='{0}’

         Required=’FALSE’         

         StaticName='{0}’

         BaseRenderingType=’Text’

         Name='{0}’

         RowOrdinal=’0′

         SystemInstance='{1}’

         Entity='{2}’

         BdcField='{3}’

         Profile=’/_layouts/ProfileRedirect.aspx?Application={1}&amp;Entity={2}&amp;ItemId=’

         HasActions=’True’

         RelatedField='{4}’         

         RelatedFieldWssStaticName='{4}’         

         AddFieldOption=’AddToDefaultContentType, AddFieldToDefaultView’

         />”;

 

 

            string fieldContent = string.Format(contentTemplate,

                                  bdcDisplayField,                           //0

                                  bdcSystemInstance,                         //1

                                  bdcEntity,                                 //2

                                  bdcField,                                  //3

                                  bdcEntity.Replace(“.”,”_x002e_”) + “_ID”  //4

                                  );

           

            string fieldName = list.Fields.AddFieldAsXml(fieldContent);

            list.Update();

 

            // … You would think that this would be enough, you now need to convince the bdc column to save the

            // bdc specific details.

 

            BusinessDataField busField = list.Fields[fieldName] as BusinessDataField;

            busField.SystemInstanceName = bdcSystemInstance;

            busField.EntityName = bdcEntity;

            busField.BdcFieldName = bdcField;

 

            string[] names = rawFields.Split(new char[1] { ‘:’ });

            busField.SetSecondaryFieldsNames(names);

           

            busField.Profile = string.Format(“/_layouts/ProfileRedirect.aspx?Application={0}&amp;Entity={1}&amp;ItemId=”, bdcSystemInstance, bdcEntity);

            busField.HasActions=true;

 

            busField.Update();           

            list.Update();

                       

            // Lets see if the bdc fields can now be added…

            foreach (SPField field in list.Fields)

            {

                if (!view.ViewFields.Exists(field.InternalName))

                {

                    if (!field.Hidden)

                    {

                        view.ViewFields.Add(field);

                    }

                }

            }

           

            view.Update();

             

        }

    }

}

BDC Columns

This is another technique for adding BDC columns in a feature specific manner.

The BDC in SharePoint (which is only available in the paid-for MOSS edition of SharePoint) allows you to add external business data to a SharePoint list.  There are several catches:

  • The document that you need to enable this is complex and really requires an expensive tool (such as Bdc Metaman – which is useful but not ideal).
  • Normally you must add the BDC column to the site via a GUI (I have found a way to do this in code see another post for today).
  • The business data is cached in the sharepoint list until it is explicitly refreshed. The GUI refresh only provides an all the list refresh option (this can be done in code for a specific row).
  • You are limited to data sources that have a .Net 2.0 database driver (Sybase does not have one out of the box and the Data Direct one is very expensive).  Alternatively you can write a web service to fetch the data.
The BDC is powerful but needs a lot of work to get it going – possibly more than a custom solution would require…

Adding BDC Columns to a Sharepoint List

I am trying to add BDC columns to a sharepoint list programatically.

In fact the application that I am working on has 140 lists each potentially with a different set of BDC extension columns.

This is of course impossible – at least no-one is saying how to do so on the blogs.

You cannot use the simple field constructor as business data is not in the enumeration.

The trick is to extract the XmlSchema from the field and remove the offending gumph (that is the specific guids – which I now think may be the cause of my problems).

There are two methods that I have found useful:

SPField.AddFieldAsXml is the key method on the list to allow the xml to be added.

You need to tell the client which bdc provider to use:

SqlSessionProvider.Instance().SetSharedResourceProviderToUse(“myssp_id”)

This almost works (the fields are added, you get a cryptic error about looking in the logs yet the bdc column does not work).

I suspect that the guids that I removed are the problem.

Normally I only post working examples – this is not one of them.

How the BDC Columns actually work

This post explains how the BDC works under the hood.
It does however miss out on fully updating the underlying data – you need to update each BDC field explicitly.

The big flaw with the BDC is that the externally referenced data is cached in the list itself.  The user needs to update the list (which can be time consuming) or the host app must do it for the user.