Sunday, May 3, 2009

CRM 4.0 Using attribute mapping in code


This is a quick and dirty way of getting CRM attribute mapping. So what exactly can you do with it? One option would be to use it in order to retrieve parent context information when you use CrmService to create new entities. For example, when you create a new contact you might want to get the account information that would normally map to contact when used from the CRM UI.

One of the challenges of using SDK is to mimic some of the functionalities that exist in the UI e.g. required field and attribute mapping when a child entity is created in the context of a parent entity. This type of mapping does not exist out of the box and requires you to write custom code.

The following example is a generic way to extract mapping information from CRM. So what are the pros and cons of using this technique?
The biggest advantage is that you’re able to you CRM customizations which make the configuration process worth while.
The disadvantages are that the default CRM mapping (attributemap entity) does not hold the type of the attributes e.g picklist or lookup and the information returned form the CRM also contains inner mappings of picklist and lookups e.g. parentcustomeridname and parentcustomriddsc.

So how can we overcome these disadvantages?
The attribute type can easily be retrieved using the metadata service RetrieveAttributeRequest. The problem is that this call to metadataservice is very slow so you also need to cache the data in order to use it efficiently.
The inner mapping can be ignored if you exclude picklist and lookup attributes that end with name of dsc.

The code does not cache the mapping information and does not show you how to create a new contact with parent mapping but presents a poc of how to retrieve the mapping information for further use.

The following is what the program prints to the console:

paymenttermscode --> paymenttermscode - Type: Picklist
telephone1 --> telephone1 - Type: String
accountid --> parentcustomerid - Type: Customer
name --> parentcustomeridname - Type: String
deletionstatecode --> parentcustomeriddsc - Type: Integer
address1_addresstypecode --> address1_addresstypecode - Type: Picklist
address1_city --> address1_city - Type: String
address1_country --> address1_country - Type: String
address1_county --> address1_county - Type: String
address1_line1 --> address1_line1 - Type: String

Here is the code, simply create a new console application and dump the class inside the program.cs file


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Sdk.Query;
using Microsoft.Crm.SdkTypeProxy.Metadata;
using Microsoft.Crm.Sdk.Metadata;

namespace EntityMapping
{
class Program
{
static void Main(string[] args)
{
List<AttributeMapping> AcctContMap = GetAttributeMapping("account", "contact");

foreach(AttributeMapping attributeMap in AcctContMap)
{
Console.WriteLine
(
String.Format
(
"{0} --> {1} - Type: {2}",
attributeMap.FromEntityName,
attributeMap.ToEntityName,
Enum.GetName(typeof(AttributeType),attributeMap.AttributeType)
)
);
}
}

public class AttributeMapping
{
public String FromEntityName;
public String ToEntityName;
public AttributeType AttributeType;

public AttributeMapping(String fromEntityName,
String toEntityName,
AttributeType typeofAttribute)
{
this.FromEntityName = fromEntityName;
this.ToEntityName = toEntityName;
this.AttributeType = typeofAttribute;
}
}

private static List<AttributeMapping> GetAttributeMapping(String fromEntity, String toEntity)
{
DynamicEntity entityMap = GetEntityMapping(fromEntity, toEntity);
List<BusinessEntity> startMap = RetieveAttributeMappingByEntityMap(entityMap);
List<AttributeMapping> endMap = new List<AttributeMapping>(startMap.Count);

foreach (DynamicEntity attributeMap in startMap)
{
String targetAttribute = attributeMap.Properties["targetattributename"].ToString();
String sourceAttribute = attributeMap.Properties["sourceattributename"].ToString();
endMap.Add(
new AttributeMapping(sourceAttribute,targetAttribute,
GetAttributeType(toEntity,targetAttribute))
);
}

return endMap;
}

private static List<BusinessEntity> RetieveAttributeMappingByEntityMap(DynamicEntity entityMap)
{
Guid entityMapId = ((Key)entityMap.Properties["entitymapid"]).Value;
QueryExpression attributeQuery = GetAttributeQuery(entityMapId, new AllColumns());
return RetrieveMultiple(attributeQuery);
}

private static AttributeType GetAttributeType(string entityLogicalName, string attributeLogicalName)
{
RetrieveAttributeRequest attributeRequest = new RetrieveAttributeRequest();
attributeRequest.EntityLogicalName = entityLogicalName;
attributeRequest.LogicalName = attributeLogicalName;
RetrieveAttributeResponse attributeResponse =
(RetrieveAttributeResponse)GetMetaService("http://moss:5555/","MicrosoftCRM").Execute(attributeRequest);
return attributeResponse.AttributeMetadata.AttributeType.Value;
}

private static MetadataService GetMetaService(string serverUrl, string orgName)
{
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = orgName;

MetadataService service = new MetadataService();
service.Url = String.Format("{0}mscrmservices/2007/metadataservice.asmx", serverUrl);
service.PreAuthenticate = false;
service.UnsafeAuthenticatedConnectionSharing = true;
service.CrmAuthenticationTokenValue = token;
service.UseDefaultCredentials = true;

return service;
}

private static List<BusinessEntity> RetrieveMultiple(QueryExpression query)
{
RetrieveMultipleRequest request = new RetrieveMultipleRequest();
request.Query = query;
request.ReturnDynamicEntities = true;
RetrieveMultipleResponse response =
(RetrieveMultipleResponse)GetCrmService(
"http://moss:5555/","MicrosoftCRM").Execute(request);
return response.BusinessEntityCollection.BusinessEntities;
}

private static CrmService GetCrmService(string serverUrl, string orgName)
{
CrmAuthenticationToken token = new CrmAuthenticationToken();
token.AuthenticationType = 0;
token.OrganizationName = orgName;

CrmService service = new CrmService();
service.Url = String.Format("{0}mscrmservices/2007/crmservice.asmx",serverUrl);
service.PreAuthenticate = false;
service.UnsafeAuthenticatedConnectionSharing = true;
service.CrmAuthenticationTokenValue = token;
service.UseDefaultCredentials = true;

return service;
}

private static DynamicEntity GetEntityMapping(String fromEntity, String toEntity)
{
QueryExpression query = new QueryExpression(EntityName.entitymap.ToString());
query.ColumnSet.AddColumn("entitymapid");
query.Criteria.AddCondition(
new ConditionExpression("sourceentityname",ConditionOperator.Equal,
new object[]{fromEntity})
);
query.Criteria.AddCondition(
new ConditionExpression("targetentityname",ConditionOperator.Equal,
new object[]{toEntity})
);

List<BusinessEntity> entityMapping = RetrieveMultiple(query);
return entityMapping[0] as DynamicEntity;
}

private static QueryExpression GetAttributeQuery(Guid entityMapId, ColumnSetBase columnSet)
{
QueryExpression query = new QueryExpression(EntityName.attributemap.ToString());
query.ColumnSet = columnSet;
query.Criteria.AddCondition(
new ConditionExpression("entitymapid",ConditionOperator.Equal,entityMapId.ToString())
);
return query;
}
}
}

1 comment:

Birendra Gaihre said...

Is there anyother way to filter out inner attributes rather than checking for ending in dsc? What if the user creates attribute ending in dsc?