Skip to main content

Extend IOrganizationService

If you're writing plugins, or interacting with the Power Platform through the .NET SDK then you will be familiar with the IOrganizationService interface. If not, here is a quick summary:

info

The IOrganizationService interface defines methods to perform common operations on system and custom tables and on the table definitions for your organization.

more...

The problem

IOrganizationService is relatively straight forward to use, but does have some apparent shortcomings when used at scale. I frequently find myself writing supplementary code to support common tasks that occure before and after usage of IOrganizationService methods.

Example 1 - Earlybound Entities

Take the following retrieve:

public static void DoSomething(IOrganizationService orgSvc, EntityReference contactRef) 
{
Entity contact = orgSvc.Retrieve(
Contact.EntityLogicalName,
ContactRef.Id,
new ColumnSet(true)
);

// do something with the contact record
}

This method retrieves an entity object representing a Contact record in Dataverse. However since the Retrieve member of IOrganizationService returns an Entity, we must either:

  1. Access the Contact record attributes by key (contact['EMailAddress1'])
  2. Cast the Contact record from Entity to Contact, then access attributes by property (contact.ToEntity<Contact>().EmailAddress1)

Option 2 is preferred and is best practice (in most cases).

This example is trivial, as casting a single record from Entity to Contact is not hard, time consuming, adds very little complexity, and maintains readable code.

However, we must cast using ToEntity<T>() each time we invoke Retrieve(), so why not simplify this.

public static void DoSomething(IOrganizationService orgSvc, EntityReference contactRef) 
{
Contact contact = RetrieveContact(orgSvc, contactRef, new ColumnSet(true));

// do something with the contact record
}

private static Contact RetrieveContact(IOrganizationService orgSvc, EntityReference contactRef, ColumnSet columnSet)
{
return orgSvc.Retrieve(
Contact.EntityLogicalName,
contactRef.Id,
columnSet
).ToEntity<Contact>();
}

Nice, so now we always have a Contact object if we use the RetrieveContact() method. This is great for Contacts, but a Dataverse ususally contains more tables than Contact. So rather than rewriting this method for each table, lets make it generic.

public static void DoSomething(IOrganizationService orgSvc, EntityReference contactRef) 
{
Contact contact = RetrieveRecord<Contact>(orgSvc, contactRef, new ColumnSet(true));

// do something with the contact record
}

private static T RetrieveRecord<T>(IOrganizationService orgSvc, EntityReference recordRef, ColumnSet columnSet)
where T : Entity, new()
{
return orgSvc.Retrieve(
new T().LogicalName,
recordRef.Id,
columnSet
).ToEntity<T>();
}

In a nutshell, our method (renamed to RetrieveRecord()) expects a generic type <T> with contraints that require <T>

  1. Extend the Entity class
  2. Implement a public parameterless constructor
note

To determine the table logical name we create an instance of the class <T> the access the LogicalName property. You could remove the new() constraint by replacing new T().LogicalName with recordRef.LogicalName. However, the original approach allows additional validation, such as if (recordRef.LogicalName != new T().LogicalName) throw new Exception("Table type mismatch)

In future I will add a tutorial doc on common validation approaches.

This function can now be called for any entity types. We can improve this function further by simplifying how we share and invoke this method. We can bundle this function into an extension method. Extension methods allow us to add a method to an existing type.

public static void DoSomething(IOrganizationService orgSvc, EntityReference contactRef) 
{
Contact contact = orgSvc.Retrieve<Contact>(contactRef, new ColumnSet(true));

// do something with the contact record
}

private static T Retrieve<T>(this IOrganizationService orgSvc, EntityReference recordRef, ColumnSet columnSet)
where T : Entity, new()
{
return orgSvc.Retrieve(
new T().LogicalName,
recordRef.Id,
columnSet
).ToEntity<T>();
}

By adding the this keyword before the IOrganizationService orgSvc parameter of our function we create an extension method on the interface IOrganizationService.

Again I have renamed the method from RetrieveRecrod to Retrieve, as we want this to be used in place of the native Retrieve function on IOrganizationService. Overloads allow us to use the same method name Retrieve as the method signatures are different.

We now have a very native looking method call when retrieving a record using the IOrganizationService interface, that is familiar to anyone with experience iwth the native method:

Entity entity = orgSvc.Retrieve(contactRef.LogicalName, contactRef.id, new ColumnSet(true));
Contact contact = orgSvc.Retrieve<Contact>(contactRef, , new ColumnSet(true));

This may seem like a load of work for something so trivial as casting an Entity, but you can apply this concept of IOrganizationService extension methods to more complicated methods.

Example 2 - Paging

public static List<T> RetrieveMultiple<T>(
this IOrganizationService service,
QueryExpression query)
where T : Entity
{

Argument.IsNotNull(query, nameof(query));

// Top count cannot be specified with paging
if (query.TopCount == null)
{
query.PageInfo = new PagingInfo();
query.PageInfo.Count = Constants.PageSize;
query.PageInfo.PageNumber = Constants.PageStartNumber;
query.PageInfo.PagingCookie = null;
}

var entities = new List<T>();

while (true)
{
EntityCollection ec = service.RetrieveMultiple(query);

if (ec?.Entities == null || ec.Entities.Count == 0) return entities;

entities.AddRange(ec.Entities.Select(e => e.ToEntity<T>()).ToList());

// There are more records so get the next page
if (ec.MoreRecords)
{
query.PageInfo.PageNumber++;
query.PageInfo.PagingCookie = ec.PagingCookie;

continue;
}

// There are no more records to retrieve, so return the results
return entities;
}
}

This extension method has the intention of replacing RetrieveMultiple, with the advantages of:

  1. Automatic paging, in a single, managable location
  2. Automatic casting to an earlybound entity type

What Next

The most obvious next step is to creat extension methods to wrao common functionality for other IOrganizationService methods. You could:

  1. Add argument validation
  2. Integrate try/catch logic
  3. Simplify building QueryExpressions

I have tried to achieve some of this with my NuGet package IOrganizationServiceExtensions. It's due for an update, but has plenty of examples of extension methods. The code is hosted in a public GitHub repository along with some other utilities I find helpful.

Summary

In a nutshell, we can consolidate complicated and repititve helper code that commonly wraps IOrganizationService method calls. By using extension methods we continue to use a pattern familiar to those who currently use IOrganizationService.