Random Musings

June 27, 2009

what is the difference between RetrieveRequest and RetrieveMultipleRequest

Filed under: Dynamics CRM — haditeo @ 10:43 am
Tags: ,

RetrieveRequest is used to retrieve one and only one record based on the unique EntityId while RetrieveMultipleRequest can be used to retrieve one or many records based on the selection criteria.

You can use RetrieveMultipleRequest to retrieve a particular record based on EntityId, usually i create a helper method by using RetrieveMultipleRequest to retrieve particular records based on selection criteria

Here is the example of using RetrieveRequest. Our scenario is retrieving a particular account based on accountid attribute

public DynamicEntity RetrieveByUsingRetrieveRequest(CrmService service, Guid accountId)
{
    TargetRetrieveAccount target = new TargetRetrieveAccount();
    target.EntityId = accountId;

    RetrieveRequest request = new RetrieveRequest();
    request.Target = target;
    
    // retrieve all attributes of the particular account
    request.ColumnSet = new AllColumns();
    
    // tell the CRM SDK to return the result as DynamicEntity type of object
    request.ReturnDynamicEntities = true;
    RetrieveResponse response = (RetrieveResponse) service.Execute(request);
    
    DynamicEntity de = (DynamicEntity)response.BusinessEntity;
    
    return de;
}

and here is the example of using RetrieveMultipleRequest

public DynamicEntity RetrieveByUsingRetrieveMultipleRequest(CrmService service, Guid accountId)
{
    QueryExpression query = new QueryExpression();
    query.EntityName = "account";
    
    ConditionExpression ceAccount = new ConditionExpression();
    ceAccount.AttributeName = "accountid";
    ceAccount.Operator = ConditionOperator.Equal;
    ceAccount.Values = new object[] {accountId};
    
    FilterExpression feAccount = new FilterExpression();
    feAccount.AddCondition(ceAccount);
    feAccount.FilterOperator = LogicalOperator.And;
    
    query.Criteria.AddFilter(feAccount);

    // retrieve all attributes of the particular account
    query.ColumnSet = new AllColumns();

    RetrieveMultipleRequest request = new RetrieveMultipleRequest();
    request.Query = query;
    
    // tell the CRM SDK to return the result as DynamicEntity type of object
    request.ReturnDynamicEntities = true;

    RetrieveMultipleResponse response = (RetrieveMultipleResponse) service.Execute(request);

    DynamicEntity de = (DynamicEntity)response.BusinessEntityCollection.BusinessEntities[0];

    return de;
}

Note :

  • ReturnDynamicEntities property can be used to configure the CRM SDK either to return the result as Dynamic Entity or Proxy Class
  • The disadvantage of using RetrieveRequest is the query can only be implemented by querying the entity-id. Usually our goal is to obtain the particular entity-id by querying certain criteria.

    Let me elaborate further. Suppose you like to know which accounts residing in Singapore, you can query like this SELECT * FROM account where city=’Singapore’ and it will return the list of accounts complete with the account id

    It’s not useful to use RetrieveRequest, if we know ahead all accounts, means SELECT * FROM account where accountid = ‘3F2504E0-4F89-11D3-9A0C-0305E82C3301’

    Usually our targets are the Unique Identifiers.

June 25, 2009

How to: Add the CrmService Web Reference

Filed under: Dynamics CRM — haditeo @ 9:29 pm
Tags: ,

I seldom use CRM Service proxy Web Reference and i almost forgot that there is a difference in invoking the web service between CRM 3.0 and CRM 4.0

Here is the CRM 3.0 way:

http://<your server name>/mscrmservices/2006/crmservice.asmx

and here is the CRM 4.0 way:

http://<servername[:port]>/mscrmservices/2007/crmservice.asmx?WSDL&uniquename=organizationName

No wonder when i invoke using below code, i still can obtain the CrmService, but i cannot find any proxy entities there

http://<your server name>/mscrmservices/2007/crmservice.asmx

After i using the correct way, i can refer to the CRM entities through intellisense as usual
class_account

intellisense

I forgot that in the CRM 4.0, there is a multitenancy concept, so we can create multiple organization within one Dynamics CRM deployment, so to refer to the particular organization CRM entities, we can customize the organization name below

http://<servername[:port]>/mscrmservices/2007/crmservice.asmx?WSDL&uniquename=organizationName

June 19, 2009

Reflection in C#

Filed under: Dynamics CRM — haditeo @ 10:00 pm
Tags: , ,

These are the code before it’s refactored

         private Opportunity ToOpportunity(DynamicEntity de)
         {
             try
             {
                 Opportunity opportunity = new Opportunity();

                 foreach (string attribute in opportunity.AttributesList)
                 {
                     if (!de.Properties.Contains("opportunityid"))
                         throw new PropertiesNotExistException("The Dynamic Entity type Opportunity does not have id property");

                     opportunity.Id = new KeyProperty("opportunityid", ((Key)de["opportunityid"]));

                     switch (attribute)
                     {
                         case "customerid":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetPotentialClient(((Customer)de[attribute]).Value);
                             break;
                         case "new_contactpersonid":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetContactPerson(((Lookup)de[attribute]).Value);
                             break;
                         case "new_clientcategory":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetClientCategory(((Picklist)de[attribute]).Value);
                             break;
                         case "name":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetPotentialProjectTitle(de[attribute].ToString());
                             break;
                         case "new_potentialprojecttitlechinese":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetPotentialProjectTitleInChinese(de[attribute].ToString());
                             break;
                         case "new_accountmanager":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetAccountManager(de[attribute].ToString());
                             break;
                         case "new_kba":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetKba(((Picklist)de[attribute]).Value);
                             break;
                         case "new_servicetype":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetServiceType(((Picklist)de[attribute]).Value);
                             break;
                         case "new_developmenttype":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetProjectDevelopmentType(((Picklist)de[attribute]).Value);
                             break;
                         case "new_regionopportunityid":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetRegion(((Lookup)de[attribute]).Value);
                             break;
                         case "new_location":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetLocation(de[attribute].ToString());
                             break;
                         case "new_countryopportunityid":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetCountry(((Lookup)de[attribute]).Value);
                             break;
                         case "description":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetDescription(de[attribute].ToString());
                             break;
                         case "new_source":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetSource(((Picklist)de[attribute]).Value);
                             break;
                         case "new_estimatedprojectcost":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetEstimatedProjectCost(((CrmMoney)de[attribute]).Value);
                             break;
                         case "estimatedvalue":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetEstimatedProjectFees(((CrmMoney)de[attribute]).Value);
                             break;
                         case "new_estimatedfeespercentage":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetEstimatedFeesPercentage(((CrmDecimal)de[attribute]).Value);
                             break;
                         case "new_estimatedprojectstartdate":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetEstimatedProjectStartDate(((CrmDateTime)de[attribute]).UserTime);
                             break;
                         case "new_estimatedprojectenddate":
                             if (de.Properties.Contains(attribute))
                                 opportunity.SetEstimatedProjectEndDate(((CrmDateTime)de[attribute]).UserTime);
                             break;
                     }
                 }
                 return opportunity;
             }
             catch (SoapException soapExc)
             {
                 throw soapExc;
             }
             catch (Exception exc)
             {
                 throw exc;
             }

         }

and these are the code after it’s refactored

        public Opportunity ToOpportunity(DynamicEntity de)
        {
            try
            {
                Assembly sdkDll = Assembly.LoadFrom("microsoft.crm.sdk.dll");

                Opportunity newOpportunity = new Opportunity();

                foreach(PropertyInfo pi in  typeof(Opportunity).GetProperties())
                {
                    string crmFieldMapping = ((CrmFieldMapping)(pi.GetCustomAttributes(typeof(CrmFieldMapping), false))[0]).GetCrmFieldMapping();
                    string crmTypeProperty = ((CrmTypeProperty)(pi.GetCustomAttributes(typeof(CrmTypeProperty), false))[0]).GetCrmTypeProperty();

                    if (de.Properties.Contains(crmFieldMapping))
                    {
                        Type currentCrmTypeProperty = sdkDll.GetType("Microsoft.Crm.Sdk." + crmTypeProperty);

                        object obj = Activator.CreateInstance(currentCrmTypeProperty, crmFieldMapping, de[crmFieldMapping]);

                        pi.SetValue(newOpportunity, obj, null);
                    }
                }

                return newOpportunity;
            }
            catch (SoapException soapExc)
            {
                throw soapExc;
            }
            catch (Exception exc)
            {
                throw exc;
            }

        }

Currently i’m closely applying Domain Driven Development by creating domain objects first. The previous source code is created without using reflection. Yesterday, i was wondering and got the idea of decorating the domain object with custom attributes as metadata, so that i can easily refer to it when reflecting the object at runtime.

These are the sample of the custom attribute :

    public class Opportunity
    {
        private KeyProperty opportunityId;
        [CrmTypeProperty("KeyProperty")]
        [CrmFieldMapping("opportunityid")]
        public KeyProperty Id
        {
            get { return opportunityId; }
            set { opportunityId = value; }
        }

        private StringProperty _projectTitle;
        [CrmTypeProperty("StringProperty")]
        [CrmFieldMapping("name")]
        public StringProperty PotentialProjectTitle
        {
            get { return _projectTitle; }
            set { _projectTitle = value; }
        }
   }

What are the advantages of using reflection ? Now, let’s say i am adding another property at the domain object, i just need to update the domain object and i don’t need to change the code at the data layer.

June 15, 2009

Open thread token failed error message

Filed under: Dynamics CRM — haditeo @ 9:57 pm
Tags: ,

While i was using ExtractCrmAuthenticationToken method, i have encountered this error message “Open thread token failed with hr = 1008” while running the solution. The compilation is working well.

OpenThreadToken

Why the error message is appeared ?
1) I placed the .ASPX custom solution outside of Dynamics CRM website.
2) In order to extract CRM authentication token, the custom solution must be placed under the Dynamics CRM website so that it can extract the authentication token which is utilized by CRM in the web.config . Here is the web.config definition taken from Dynamics CRM solution

<httpModules>
    <add name ="MapOrg" type="Microsoft.Crm.MapOrgEngine, Microsoft.Crm, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name ="CrmAuthentication" type="Microsoft.Crm.Authentication.AuthenticationEngine, Microsoft.Crm, Version=5.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</httpModules>

The solution is :
1) Place the custom ASPX solution under the Dynamics CRM website in order to utilize the ExtractCrmAuthenticationToken method, so that this method can use the CrmAuthentication module.

June 11, 2009

TDD with Dynamics CRM 4.0 Plugin

Filed under: Dynamics CRM — haditeo @ 4:24 pm
Tags: , ,

Download source code here

I would like to introduce how to enforce TDD for developing plugin in Dynamic CRM. Why enforcing TDD ? I find it very time consuming to manually enter the data from the Dynamics CRM UI, just to trigger the plugin to run and generate the IPluginExecutionContext.

Usually i hook the debugger on the plugin codes right inside the IPluginExecutionContext method and debug line by line, then if some bugs are detected, then i need to
1) Recompile
2) Re-register again through plugin registration tool
3) IISReset
4) Manually enter the data again
5) Debug the codes
These cycles are repeated numerously, which are time consuming

The key is how do we “mock” IPluginExecutionContext ?

Take a look at the definition of the Execute method which accepts IPluginExecutionContext type of object :

public void Execute(IPluginExecutionContext context)
{
    ContextStage = context.Stage;

    List = QueryActiveContact(context.CreateCrmService(true));
}

In this method, i would like to
1) Mock the context.Stage property to return some fake value
2) Run a query against Dynamics CRM SDK by mocking the CrmService returned by context.CreateCRMService() method

Here i am using RhinoMock as my mocking framework:

[Test]
public void TestInstantiate()
{
    TestIPluginExecutionContext test = new TestIPluginExecutionContext();           

    MockRepository mocks = new MockRepository();
    IPluginExecutionContext context = mocks.StrictMock<IPluginExecutionContext>();

    With.Mocks(mocks).Expecting(delegate
    {
        Expect.Call(context.Stage).Return(11122);
        Expect.Call(context.CreateCrmService(true)).Return(new MockCrmService());
    })
    .Verify(delegate
    {
        test.Execute(context);
    });

    Assert.Greater(test.ContextStage, 0, "context stage is 0");
    Assert.IsNotEmpty(test.List, "list is empty");
}

The key here is i am using Expect.Call() method, to tell mocking framework to return ‘11122’ integer value when context.Stage is invoked . If the context.CreateCrmService() method is invoked, please return a concrete MockCrmService object. I have created a fake MockCrmService object, to be injected when the context.CreateCrmService() method is called.

I have noticed that the Dynamics CRM plugin has confirmed to TDD, by making the parameter of the method of Execute accepting an Interface type of IPluginExecutionContext. This is very useful, since during the implementation we can mock / fake the CRM Service by impersonating any CRM user. The advantage of this is to test whether any exception is occured when we impersonate a CRM user to access a record.

Here is the concrete MockCrmService object

public class MockCrmService : ICrmService
{
    public CrmService CreateCrmService()
    {
        NetworkCredential nc = new NetworkCredential();
        nc.Domain = Settings.Default.Domain;
        nc.UserName = Settings.Default.UserName;
        nc.Password = Settings.Default.Password;

        CrmAuthenticationToken token = new CrmAuthenticationToken();
        token.AuthenticationType = Convert.ToInt32(Settings.Default.AuthenticationType); ;
        token.OrganizationName = Settings.Default.OrgName;

        CrmService crmService = new CrmService();
        crmService.CrmAuthenticationTokenValue = token;
        crmService.Credentials = nc;
        crmService.Url = Settings.Default.CrmUrl;

        return crmService;
    }

    #region ICrmService Members

    public Guid Create(BusinessEntity entity)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public void Delete(string entityName, Guid id)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public object Execute(object request)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public string Fetch(string fetchXml)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public BusinessEntity Retrieve(string entityName, Guid id, Microsoft.Crm.Sdk.Query.ColumnSetBase columnSet)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public BusinessEntityCollection RetrieveMultiple(Microsoft.Crm.Sdk.Query.QueryBase query)
    {
        return CreateCrmService().RetrieveMultiple(query);
    }

    public void Update(BusinessEntity entity)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    #endregion

    #region IDisposable Members

    public void Dispose()
    {
        throw new Exception("The method or operation is not implemented.");
    }

    #endregion
}

Since we are interested in using RetrieveMultiple method, i inserted codes below to instantiate a new CrmService object

    public BusinessEntityCollection RetrieveMultiple(Microsoft.Crm.Sdk.Query.QueryBase query)
    {
        return CreateCrmService().RetrieveMultiple(query);
    }

For the full source code, please refer to the download link in the beginning of the post.
What are the advantages of using TDD in developing plugin ?
1) We can “mock” the IPluginExecutionContext to supply correct data to test our internal business logic.
2) We can “mock” the IPluginExecutionContext to supply incorrect data to test whether the exception raised by our business logic are correct
3) We can combine this with NUnit to run our test cases automatically, without needing manual intervention. Since we build small codes and test small codes, it’s quite easy to detect bugs since we have NUnit to alert us in the event of bugs raising.
4) We don’t need to publish the plugin just to test the plugin, since publishing is time consuming and involving IISReset to refresh the assembly.
5) We can utilize NUnit to put assertion, to detect whether the object values or states are correct, rather than manually put the debugger when the plugin is running.

June 10, 2009

Remove the linkage between two entities in many to many relationships

Filed under: Dynamics CRM — haditeo @ 6:05 pm
Tags: ,

To disassociate the relationship between two entities which are linked via many to many relationship, these code snippet can be used :

                DisassociateEntitiesRequest disassociateRequest = new DisassociateEntitiesRequest();
                disassociateRequest.Moniker1 = new Moniker();
                disassociateRequest.Moniker1.Id = entityId;
                disassociateRequest.Moniker1.Name = entityName;

                disassociateRequest.Moniker2 = new Moniker();
                disassociateRequest.Moniker2.Id = entityId2;
                disassociateRequest.Moniker2.Name = entityName2;
                disassociateRequest.RelationshipName = relationshipName;

                DisassociateEntitiesResponse associateResponse = (DisassociateEntitiesResponse)crmService.Execute(disassociateRequest);

June 6, 2009

AssociateEntityRequest – Used to link two entities in many to many relationships

Filed under: Dynamics CRM — haditeo @ 9:08 pm
Tags: ,

In order to link two different entities in the many to many relationship, both entities should have been created first, so that their GUID are exist

Use below codes to associate both entities

Moniker moniker1 = new Moniker();
moniker1.Name = "entity1Name";
moniker1.Id = new Guid("11111111-2222-3333-4444-5555555555555");
Moniker moniker2 = new Moniker();
moniker2.Name = "entity2Name";
moniker2.Id = new Guid("66666666-7777-8888-9999-0000000000000");

AssociateEntitiesRequest request = new AssociateEntitiesRequest();
request.Moniker1 = moniker1;
request.Moniker2 = moniker2;
request.RelationshipName = "relationshipName";

service.Execute(request); 

June 5, 2009

Paging records using Dynamics CRM SDK QueryExpression

Filed under: Dynamics CRM — haditeo @ 9:55 pm
Tags: ,

Inside the QueryExpression object, we can set the PageInfo property to limit the number of records returned by the QueryExpression.

Just setup a PagingInfo object and set the PageInfo property of the QueryExpression to the PagingInfo object

PagingInfo page = new PagingInfo();
page.Count = 10; // means that we would like to return 10 records per batch
page.PageNumber = 2; // means that we would like to retrieve the 2nd batch which is record number 11 until 20

QueryExpression query = new QueryExpression;
query.PageInfo = page;

June 4, 2009

Query only ‘enabled’ users

Filed under: Uncategorized — haditeo @ 10:08 pm
Tags: ,

In order to search for all system users who are not disabled, these ConditionExpression needs to be used

// Create the ConditionExpression.
ConditionExpression statusCodeConditionExpression = new ConditionExpression();

// Set the condition for the retrieval
statusCodeConditionExpression.AttributeName = "isdisabled";
statusCodeConditionExpression.Operator = ConditionOperator.Equal;
statusCodeConditionExpression.Values = new object[] { false };

May 30, 2009

TDD on Dynamics CRM Workflow

Filed under: Dynamics CRM — haditeo @ 8:23 pm
Tags: , ,

In the Dynamics CRM 4 custom workflow activity, we can create an instance of CRMService by instantiating a IWorkflowContext object

IWorkflowContext context = ((IContextService)executionContext.GetService(typeof(IContextService))).Context;
ICrmService crmService = context.CreateCrmService();

How to unit test a Dynamics CRM 4 workflow ? since the CrmService object needs to be created with an interface type of ICrmService

Notice here carefully that we need to implement several methods, but in my scenario, i need to use the execute method.

    class MyCrmService :ICrmService
    {

        private CrmService crmService;

        public MyCrmService()
        {
            NetworkCredential nc = new NetworkCredential();
            nc.Domain = "Domain";
            nc.UserName = "administrator";
            nc.Password = "pwd";

            // Set up the CRM Service.
            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.AuthenticationType = 0;
            token.OrganizationName = "xyz";

            CrmService service = new CrmService();
            service.Url = "http://localhost/mscrmservices/2007/crmservice.asmx";
            service.CrmAuthenticationTokenValue = token;
            service.Credentials = nc;
            this.crmService = service;        
        }


        #region ICrmService Members

        public Guid Create(BusinessEntity entity)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void Delete(string entityName, Guid id)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public object Execute(object request)
        {
            return crmService.Execute((Request)request);
        }

        public string Fetch(string fetchXml)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public BusinessEntity Retrieve(string entityName, Guid id, ColumnSetBase columnSet)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public BusinessEntityCollection RetrieveMultiple(QueryBase query)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        public void Update(BusinessEntity entity)
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        #endregion
    [TestFixture]
    public class Class1
    {
        private MyCrmService crmSvc;
        private MyMetadataService metadataSvc;
        private SendEmailActivity activity;

        [SetUp]
        public void Init()
        {
            this.crmSvc = new MyCrmService();

            this.metadataSvc = new MyMetadataService();

            this.activity = new SendEmailActivity();

        }
     }

The advantages of using the Interface, during the runtime, we can switch a method which requires ICrmService with the actual CrmService itself

Next Page »

Blog at WordPress.com.