Second Life of a Hungarian SharePoint Geek

March 3, 2011

A more developer-friendly way of programmatically creating SharePoint 2010 external content types

Filed under: BCS, Reflection, SP 2010 — Tags: , , — Peter Holpar @ 00:51

Todd Baginski held a presentation (SPC 405 – Business Connectivity Services Runtime and Object Model Deep Dive) on 2009 Microsoft SharePoint Conference about creating ECTs from code. You can read more about it and find the original code on his blog.

The code in Todd’s post is really valuable when learning the object model, but I found that creating ECTs this way is very error-prone. If you need to alter the model, you have serious chance to make something wrong.

In this post I show you the main issues with that approach and try to offer you an alternative way to achieve the same result in a more developer-friendly manner.

First, I don’t like specifying type descriptor parameters as string values. It is longer than necessary and easy to mistype.

  1. TypeDescriptor returnRootCollectionTypeDescriptor2 =
  2.     customersParameter.CreateRootTypeDescriptor("Customers", true,
  3.     "System.Data.IDataReader, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
  4.     "Customers", null, null, TypeDescriptorFlags.IsCollection, null, catalog);

As a first step, one could compute the string from the original type and store it in string variables as shown here:

  1. String int32TypeName = typeof(Int32).ToString(); // will be "System.Int32"
  2. String stringTypeName = typeof(String).ToString(); // will be "System.String"
  3. String iDataReaderTypeName = typeof(IDataReader).AssemblyQualifiedName; // will be "System.Data.IDataReader, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
  4. String iDataRecordTypeName = typeof(IDataRecord).AssemblyQualifiedName; // will be "System.Data.IDataRecord, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

Later, these string values could be used as parameters of type descriptor creation.

The ideal solution would be to use the Type itself as the parameter of these methods. We will see how to achieve that soon.

Second, the parameters of the methods are rather redundant (see the name and lobName parameters that are usually the same value), contain several unspecified (null) parameter values, and the calls to create different type descriptors share most of their parameter values.

  1. returnRootElementTypeDescriptor.ChildTypeDescriptors.Create("FirstName", true,
  2.     "System.String", "FirstName", null, null, TypeDescriptorFlags.None, null);
  3. returnRootElementTypeDescriptor.ChildTypeDescriptors.Create("LastName", true,
  4.     "System.String", "LastName", null, null, TypeDescriptorFlags.None, null);
  5. returnRootElementTypeDescriptor.ChildTypeDescriptors.Create("Phone", true,
  6.     "System.String", "Phone", null, null, TypeDescriptorFlags.None, null);

It would be nice to create a base parameter set, without the need to specify default (null) values, and specify only the unique values on method calls.

Third, the calls to create type descriptors are sometimes rather complex and contains redundant parameter values again.

  1. customerIDParameter.CreateRootTypeDescriptor("CustomerId", true, "System.Int32", "CustomerId",
  2.     new IdentifierReference("CustomerId",
  3.     new EntityReference("AdventureWorks", "Customer", catalog), catalog),
  4.     null, TypeDescriptorFlags.None, null, catalog);

Wouldn’t it be great to get the entity and identifier references automatically from the entities and identifiers of the model already specified?

I’ve tried to achieve the above goals by applying a few extension methods and creating a custom type to hold type descriptor creation parameters.

The following two extension methods show how to get the IdentifierReference and the EntityReference by specifying the Identifier and the Entity:

  1. public static IdentifierReference GetReference(this Identifier identifier)
  2. {
  3.     // to protect method against possible direct call with null value
  4.     // should not happen when called like a "standard" extension method
  5.     if (identifier == null)
  6.     {
  7.         throw new ArgumentNullException("identifier");
  8.     }
  9.  
  10.     Entity entity = identifier.Entity;
  11.     EntityReference entityReference = entity.GetReference();
  12.     IdentifierReference result = new IdentifierReference(identifier.Name, entityReference, entity.GetCatalog());
  13.  
  14.     return result;
  15. }
  16.  
  17. public static EntityReference GetReference(this Entity entity)
  18. {
  19.     // to protect method against possible direct call with null value
  20.     // should not happen when called like a "standard" extension method
  21.     if (entity == null)
  22.     {
  23.         throw new ArgumentNullException("entity");
  24.     }
  25.  
  26.     // IMPORTANT! name and namespace parameters are in opposite order
  27.     // in the case of Entity.Create method and EntityReference constructor
  28.     EntityReference result = new EntityReference(entity.Namespace, entity.Name, entity.GetCatalog());
  29.  
  30.     return result;
  31. }

Just a side note. As you can read in the comments, name and namespace parameters are in opposite order in the case of Entity.Create method and EntityReference constructor. I find this design to be quite misleading.

Both methods above need a reference to the parent AdministrationMetadataCatalog. Instead of passing this as a parameter, I applied a Reflection call to get its value from the Entity that contains it as a non-public field. This extension method might be nicer as an extension property, but unfortunately there is not yet such thing in .NET.

  1. // unfortunately, there is no (yet) "extension property" in .NET
  2. // so we must create a "get" extension method instead
  3. public static AdministrationMetadataCatalog GetCatalog(this Entity entity)
  4. {
  5.     // to protect method against possible direct call with null value
  6.     // should not happen when called like a "standard" extension method
  7.     if (entity == null)
  8.     {
  9.         throw new ArgumentNullException("entity");
  10.     }
  11.  
  12.     AdministrationMetadataCatalog result = null;
  13.  
  14.     MethodCollection methods = entity.Methods;
  15.  
  16.     // a new MethodColletion is created on each Entity creation (see internal Entity constructor)
  17.     // so it shouldn't be null
  18.     if (methods != null)
  19.     {
  20.         Type methodCollectionType = typeof(MethodCollection);
  21.         FieldInfo fi_metadataCatalog = methodCollectionType.GetField("metadataCatalog", BindingFlags.NonPublic | BindingFlags.Instance);
  22.         result = (AdministrationMetadataCatalog)fi_metadataCatalog.GetValue(methods);
  23.     }
  24.  
  25.     return result;
  26. }

You can use these methods as illustrated here:

  1. Entity customerEntity = Entity.Create("Customer", "AdventureWorks", true,
  2.     new Version("1.0.0.0"), 1000, CacheUsage.Default, lobSystem, customerModel, catalog);
  3. EntityReference customerEntityRef = customerEntity.GetReference();
  4.  
  5. // create the identifier
  6. Identifier customerIdentifier = customerEntity.CreateIdentifier("CustomerID", true, typeof(Int32));
  7. IdentifierReference customerIdentifierRef = customerIdentifier.GetReference();

Next, I’ve created a class to hold type descriptor creation parameters. I’ve planned this class to support inheriting values from another instance of the class through its constructor. Only values explicitly set must be inherited, but default values must not. It requires the class to remember which values were specified explicitly. (Remark: in this version of code this behavior has not too much importance. I planned a merge functionality either that needs this feature in a possible later version.) The class is able to resolve name and lobName parameter values from each other so one should only specify one of them if they are the same.

  1. public class TypeDescriptorParams
  2. {
  3.     private String _name;
  4.     private bool _isNameSpecified = false;
  5.     public String Name { get
  6.     {
  7.         return _name;
  8.     }
  9.         set
  10.         {
  11.             _isNameSpecified = true;
  12.             _name = value;
  13.         }
  14.     }
  15.  
  16.     private bool _isCached;
  17.     private bool _isIsCachedSpecified = false;
  18.     public bool IsCached
  19.     {
  20.         get
  21.         {
  22.             return _isCached;
  23.         }
  24.         set
  25.         {
  26.             _isIsCachedSpecified = true;
  27.             _isCached = value;
  28.         }
  29.     }
  30.  
  31.     private Type _type;
  32.     private bool _isTypeSpecified = false;
  33.     public Type Type
  34.     {
  35.         get
  36.         {
  37.             return _type;
  38.         }
  39.         set
  40.         {
  41.             _isTypeSpecified = true;
  42.             _type = value;
  43.         }
  44.     }
  45.  
  46.     private String _lobName;
  47.     private bool _isLobNameSpecified = false;
  48.     public String LobName
  49.     {
  50.         get
  51.         {
  52.             return _lobName;
  53.         }
  54.         set
  55.         {
  56.             _isLobNameSpecified = true;
  57.             _lobName = value;
  58.         }
  59.     }
  60.  
  61.     private IdentifierReference _identifierReference;
  62.     private bool _isIdentifierReferenceSpecified = false;
  63.     public IdentifierReference IdentifierReference
  64.     {
  65.         get
  66.         {
  67.             return _identifierReference;
  68.         }
  69.         set
  70.         {
  71.             _isIdentifierReferenceSpecified = true;
  72.             _identifierReference = value;
  73.         }
  74.     }
  75.  
  76.     private FilterDescriptor _filterDescriptor;
  77.     private bool _isFilterDescriptorSpecified = false;
  78.     public FilterDescriptor FilterDescriptor
  79.     {
  80.         get
  81.         {
  82.             return _filterDescriptor;
  83.         }
  84.         set
  85.         {
  86.             _isFilterDescriptorSpecified = true;
  87.             _filterDescriptor = value;
  88.         }
  89.     }
  90.  
  91.     private TypeDescriptorFlags _flags;
  92.     private bool _isFlagsSpecified = false;
  93.     public TypeDescriptorFlags Flags
  94.     {
  95.         get
  96.         {
  97.             return _flags;
  98.         }
  99.         set
  100.         {
  101.             _isFlagsSpecified = true;
  102.             _flags = value;
  103.         }
  104.     }        
  105.  
  106.     private AssociationReference _associationReference;
  107.     private bool _isAssociationReferenceSpecified = false;
  108.     public AssociationReference AssociationReference
  109.     {
  110.         get
  111.         {
  112.             return _associationReference;
  113.         }
  114.         set
  115.         {
  116.             _isAssociationReferenceSpecified = true;
  117.             _associationReference = value;
  118.         }
  119.     }
  120.  
  121.     private AdministrationMetadataCatalog _metadataCatalog;
  122.     private bool _isMetadataCatalogSpecified = false;
  123.     public AdministrationMetadataCatalog MetadataCatalog
  124.     {
  125.         get
  126.         {
  127.             return _metadataCatalog;
  128.         }
  129.         set
  130.         {
  131.             _isMetadataCatalogSpecified = true;
  132.             _metadataCatalog = value;
  133.         }
  134.     }
  135.  
  136.     public TypeDescriptorParams()
  137.     {
  138.     }
  139.  
  140.     public TypeDescriptorParams(TypeDescriptorParams explicitParams)
  141.     {
  142.         if (explicitParams._isNameSpecified) this.Name = explicitParams.Name;
  143.         if (explicitParams._isIsCachedSpecified) this.IsCached = explicitParams.IsCached;
  144.         if (explicitParams._isTypeSpecified) this.Type = explicitParams.Type;
  145.         if (explicitParams._isLobNameSpecified) this.LobName = explicitParams.LobName;
  146.         if (explicitParams._isIdentifierReferenceSpecified) this.IdentifierReference = explicitParams.IdentifierReference;
  147.         if (explicitParams._isFilterDescriptorSpecified) this.FilterDescriptor = explicitParams.FilterDescriptor;
  148.         if (explicitParams._isFlagsSpecified) this.Flags = explicitParams.Flags;
  149.         if (explicitParams._isAssociationReferenceSpecified) this.AssociationReference = explicitParams.AssociationReference;
  150.         if (explicitParams._isMetadataCatalogSpecified) this.MetadataCatalog = explicitParams.MetadataCatalog;
  151.     }
  152.  
  153.     public void ResolveNames()
  154.     {
  155.         // names default to the other name type
  156.         if ((this._isNameSpecified) && (!this._isLobNameSpecified))
  157.         {
  158.             this.LobName = this.Name;
  159.         }
  160.  
  161.         if ((!this._isNameSpecified) && (this._isLobNameSpecified))
  162.         {
  163.             this.Name = this.LobName;
  164.         }
  165.     }
  166. }

You can see that we specify the Type and not the String-based type name of the descriptor. It will be resolved later to the name.

Having this class, I created the following extension methods to bridge my code to the standard BCS API calls:

  1. public static TypeDescriptor CreateChildTypeDescriptor(this TypeDescriptor typeDescriptor, TypeDescriptorParams typeDescrParams)
  2. {
  3.     // to protect method against possible direct call with null value
  4.     // should not happen when called like a "standard" extension method
  5.     if (typeDescriptor == null)
  6.     {
  7.         throw new ArgumentNullException("typeDescriptor");
  8.     }
  9.  
  10.     typeDescrParams.ResolveNames();
  11.  
  12.     TypeDescriptor result = typeDescriptor.ChildTypeDescriptors.Create(
  13.         typeDescrParams.Name,
  14.         typeDescrParams.IsCached,
  15.         ResolveForBcs(typeDescrParams.Type),
  16.         typeDescrParams.LobName,
  17.         typeDescrParams.IdentifierReference,
  18.         typeDescrParams.FilterDescriptor,
  19.         typeDescrParams.Flags,
  20.         typeDescrParams.AssociationReference);
  21.  
  22.     return result;
  23. }
  24.  
  25. public static TypeDescriptor CreateRootTypeDescriptor(this Parameter parameter, TypeDescriptorParams typeDescrParams)
  26. {
  27.     // to protect method against possible direct call with null value
  28.     // should not happen when called like a "standard" extension method
  29.     if (parameter == null)
  30.     {
  31.         throw new ArgumentNullException("parameter");
  32.     }
  33.  
  34.     typeDescrParams.ResolveNames();
  35.  
  36.     TypeDescriptor result = parameter.CreateRootTypeDescriptor(
  37.         typeDescrParams.Name,
  38.         typeDescrParams.IsCached,
  39.         ResolveForBcs(typeDescrParams.Type),
  40.         typeDescrParams.LobName,
  41.         typeDescrParams.IdentifierReference,
  42.         typeDescrParams.FilterDescriptor,
  43.         typeDescrParams.Flags,
  44.         typeDescrParams.AssociationReference,
  45.         typeDescrParams.MetadataCatalog);
  46.  
  47.     return result;
  48. }
  49.  
  50. public static Identifier CreateIdentifier(this Entity entity, String name, bool isCached, Type type)
  51. {
  52.     // to protect method against possible direct call with null value
  53.     // should not happen when called like a "standard" extension method
  54.     if (entity == null)
  55.     {
  56.         throw new ArgumentNullException("entity");
  57.     }
  58.  
  59.     Identifier result =
  60.         entity.Identifiers.Create(name, isCached, ResolveForBcs(type));
  61.  
  62.     return result;
  63. }

The following helper method is used by the above methods to resolve the Type to the type name BCS calls require. For CLR library classes we need only the short name of the type, otherwise the qualified name.

  1. private static string ResolveForBcs(Type type)
  2. {
  3.     String typeName = null;
  4.     
  5.     if (type != null)
  6.     {
  7.         typeName = (type.Module.ScopeName == "CommonLanguageRuntimeLibrary") ? type.ToString() : type.AssemblyQualifiedName;
  8.     }
  9.  
  10.     return typeName;
  11. }

Using these concepts, one can write more compact code, no need to specify null and common values. The calls to create type descriptors are easier to read and maintain.

  1. TypeDescriptorParams standardStringType =
  2.     new TypeDescriptorParams
  3.     {
  4.         IsCached = true,
  5.         Type = typeof(Int32),
  6.         Flags = TypeDescriptorFlags.None,
  7.     };
  8.  
  9. TypeDescriptorParams standardIntType =
  10.     new TypeDescriptorParams(standardStringType)
  11.     {
  12.         Type = typeof(Int32),
  13.     };
  14.  
  15. // create the TypeDescriptor for the CustomerID parameter
  16. CustomerIDParameter.CreateRootTypeDescriptor(new TypeDescriptorParams(standardIntType)
  17. {
  18.     Name = "CustomerID",
  19.     IdentifierReference = customerIdentifierRef,
  20.     MetadataCatalog = catalog
  21. });

Repetitive code blocks requires only specifying unique parameter values:

  1. returnElementTypeDescriptor.CreateChildTypeDescriptor(new TypeDescriptorParams(standardStringType)
  2. {
  3.     Name = "FirstName"
  4. });
  5.  
  6. returnElementTypeDescriptor.CreateChildTypeDescriptor(new TypeDescriptorParams(standardStringType)
  7. {
  8.     Name = "LastName"
  9. });
  10.  
  11. returnElementTypeDescriptor.CreateChildTypeDescriptor(new TypeDescriptorParams(standardStringType)
  12. {
  13.     Name = "Phone"
  14. });

This methods help you to write and alter the code easier, but there is still chance to have errors. To find these errors before activating your model, you can use the Validate method of your Entity, and check the returning ActivationError[] array if necessary.

  1. ActivationError[] errors = customerEntity.Validate();
  2.  
  3. result = (errors.Length == 0);
  4.  
  5. if (result)
  6. {
  7.     customerEntity.Activate();
  8.     Console.WriteLine("Model created");
  9. }
  10. else
  11. {
  12.     Console.WriteLine("Validation errors:");
  13.     Array.ForEach(errors,
  14.         error => Console.WriteLine(error));
  15. }

Of course, these examples only provide you some idea about what can be done to create a more maintainable code. There are a lot of things to do yet, for example, BCS connection properties are still string based, that I think not really safe as well:

  1. // set the connection properties
  2. lobSystemInstance.Properties.Add("AuthenticationMode", "PassThrough");
  3. lobSystemInstance.Properties.Add("DatabaseAccessProvider", "SqlServer");
  4. lobSystemInstance.Properties.Add("RdbConnection Data Source", "sp2010");
  5. lobSystemInstance.Properties.Add("RdbConnection Initial Catalog", "AdventureWorksLT");
  6. lobSystemInstance.Properties.Add("RdbConnection Integrated Security", "SSPI");
  7. lobSystemInstance.Properties.Add("RdbConnection Pooling", "true");

You can find the complete solution here, including code from my former posts about checking existence of a list from managed client object model and creating external list from code client side code.

Advertisements

2 Comments »

  1. This is great! I was just about gettinng ready to use Todd’s code in a few BCS we might need in next few months – I did find it error prone to make changes for different models – now I will look at your extension. great job both of you.

    As you are experts, do you know a way to migrate SP2007 blog entries to SP2010? SP2007 list “Posts” does not seem to have a way to save as template(with content) – any solution for that?

    Comment by EMT Genius — April 6, 2011 @ 09:30

  2. Hey there, I think your site might be having browser compatibility issues.

    When I look at your website in Chrome, it looks fine but when opening in Internet Explorer,
    it has some overlapping. I just wanted to give you a quick
    heads up! Other then that, superb blog!

    Comment by midwife san francisco — June 28, 2012 @ 14:40


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: