Active Mapping Syncronization

In Dynamics CRM a mapping in a relationship is just a way to speed up the process of creating sub entities. But what if you would like have a little bit more out of it. Say that you would like to have them cascading information down, so that when an address is updated on an account that same address is updated on all of the child contacts as well.
Note here that this is a one way update. Parent entity will update child entities and not the other way around.

Let’s start with what we already have in Dynamics CRM. So take the Account to contact relationship and have a look.

First picture here it’s the relationship with the relationship name (1).
Relationship name

Then mapping of fields, and these are the fields to keep updated.Relationship mappings

So we need a place to keep record of what needs to be updated, so create a new entity. I named it “Active Mapping”, with two string fields,

  • Name (or relationship name)
  • Primary entity

Active Mapping Form

When that’s done the rest is just code. No more information need to get this to work. So let’s get to it. I created a plugin using the developer toolkit but removed all the features that control when the plugin is fired. So I have a

LocalPluginContext localContext = new LocalPluginContext(serviceProvider);

But not much more.
So when the plugin has fired I want to start with getting all the “Active Mapping” entities that I created above matching my “primary enity”.

private static List<string> GetActiveMappings(LocalPluginContext localContext)
{
    QueryExpression activeMappingsQuery = new QueryExpression("mer_activemapping")
    {
        ColumnSet = new ColumnSet("mer_name"),
    };
    activeMappingsQuery.Criteria.AddCondition("mer_primaryentity", ConditionOperator.Equal, localContext.PluginExecutionContext.PrimaryEntityName);

    EntityCollection activemappings = localContext.OrganizationService.RetrieveMultiple(activeMappingsQuery);
    List<string> returnList = new List<string>();
    foreach (Entity activeMapping in activemappings.Entities)
    {
        if (activeMapping.Attributes.Contains("mer_name"))
            returnList.Add(activeMapping["mer_name"].ToString());
    }

    return returnList;
}

Now this will return a list of relationship names that are relevant to update.
With that list of names I need to get the details of this list of relationships:

private static List<OneToManyRelationshipMetadata> GetRelationshipDetails(LocalPluginContext localContext, List<string> relationshipNames)
{
    RetrieveEntityRequest retrieveEntityRequest = new RetrieveEntityRequest()
    {
        EntityFilters = EntityFilters.Relationships,
        RetrieveAsIfPublished = true,
        LogicalName = localContext.PluginExecutionContext.PrimaryEntityName,
    };

    RetrieveEntityResponse retrieveEntityResp = (RetrieveEntityResponse)localContext.OrganizationService.Execute(retrieveEntityRequest);
    List<OneToManyRelationshipMetadata> returnList = new List<OneToManyRelationshipMetadata>();

    foreach (OneToManyRelationshipMetadata relationshipMetadata in retrieveEntityResp.EntityMetadata.OneToManyRelationships)
    {
        foreach (string relName in relationshipNames)
        {
            if (relationshipMetadata.SchemaName.Equals(relName))
            {
                returnList.Add(relationshipMetadata);
            }
        }
    }

    return returnList;
}

When I have the actual relationships I need to get the attribute mappings list, so for each of the items in the returned list in the previous method I do:

private static List<InternalAttributeMap> GetChildAttributeMappings(LocalPluginContext localContext, string relatedentity)
{

    QueryExpression relationshipMappings = new QueryExpression("attributemap");
    relationshipMappings.ColumnSet.AddColumns("sourceattributename", "targetattributename");
    relationshipMappings.Criteria.AddCondition("parentattributemapid", ConditionOperator.Null);

    LinkEntity entityMap = new LinkEntity("attributemap", "entitymap", "entitymapid", "entitymapid", JoinOperator.Inner);
    entityMap.LinkCriteria.AddCondition("sourceentityname", ConditionOperator.Equal, localContext.PluginExecutionContext.PrimaryEntityName);
    entityMap.LinkCriteria.AddCondition("targetentityname", ConditionOperator.Equal, relatedentity);
    relationshipMappings.LinkEntities.Add(entityMap);

    EntityCollection attributeMaps = localContext.OrganizationService.RetrieveMultiple(relationshipMappings);
    List<InternalAttributeMap> attributeMappings = new List<InternalAttributeMap>();
    foreach (Entity attributeMap in attributeMaps.Entities)
    {
        attributeMappings.Add(new InternalAttributeMap(attributeMap["sourceattributename"].ToString(), attributeMap["targetattributename"].ToString()));
    }

    return attributeMappings;
}

I put all of them in a helper class to make life easier for me:

internal class InternalAttributeMap
{
    public InternalAttributeMap(string sourceattributename, string targetattributename)
    {
        this.SourceAttributeName = sourceattributename;
        this.TargetAttributeName = targetattributename;
    }

    public string SourceAttributeName { get; set; }
    public string TargetAttributeName { get; set; }
}

Note here that only source entity and target entity is needed, that means that CRM actually don’t have an attributes mapping per relationship. But have a common for each source-target pair. This means that if you have multiple relationships from one entity to another, and you change the attribute mapping for one of them, you have changed the attribute mapping for the other as well.
It’s easy to try. Change one of the account to opportunity mapping on account and then check the other.
Anyway, I have to remove the particular mapping that comprises the relationship, I don’t even want to try to change that field.

private static void RemoveSourceValue(List<InternalAttributeMap> list, string sourceAttributeName)
{
    foreach (InternalAttributeMap attribute in list)
    {
        if (attribute.SourceAttributeName.Equals(sourceAttributeName))
        {
            list.Remove(attribute);
        }
    }
}

Update the child entities:

private static void UpdateChildEntities(LocalPluginContext localContext, List<InternalAttributeMap> attributeMappings, string entity, string relationshipattribute)
{
    ColumnSet contactAttributes = new ColumnSet();
    ColumnSet campaignResonseAttributes = new ColumnSet();
    foreach (InternalAttributeMap attribute in attributeMappings)
    {
        contactAttributes.AddColumn(attribute.SourceAttributeName);
        campaignResonseAttributes.AddColumn(attribute.TargetAttributeName);
    }

    Entity primaryEntity = localContext.OrganizationService.Retrieve(localContext.PluginExecutionContext.PrimaryEntityName, localContext.PluginExecutionContext.PrimaryEntityId, contactAttributes);

    QueryExpression retreiveChildEntitiesQuery = new QueryExpression(entity);
    retreiveChildEntitiesQuery.Criteria.AddCondition(relationshipattribute, ConditionOperator.Equal, localContext.PluginExecutionContext.PrimaryEntityId);

    EntityCollection childEntitiesCollection = localContext.OrganizationService.RetrieveMultiple(retreiveChildEntitiesQuery);
    foreach (Entity childEntity in childEntitiesCollection.Entities)
    {
        foreach (InternalAttributeMap attribute in attributeMappings)
        {
            if (primaryEntity.Attributes.Contains(attribute.SourceAttributeName))
                childEntity[attribute.TargetAttributeName] = primaryEntity[attribute.SourceAttributeName];
            else
                childEntity[attribute.TargetAttributeName] = null;
        }
        localContext.OrganizationService.Update(childEntity);
    }
}

So when you have all the pieces just add them together:

public void Execute(IServiceProvider serviceProvider)
{
    // Construct the Local plug-in context.
    LocalPluginContext localContext = new LocalPluginContext(serviceProvider);

    List<string> relationshipsToUpdate = GetActiveMappings(localContext);

    List<OneToManyRelationshipMetadata> metadataInfos = GetRelationshipDetails(localContext, relationshipsToUpdate);
    foreach (OneToManyRelationshipMetadata relationship in metadataInfos)
    {
        List<InternalAttributeMap> attributeMappingsList = GetChildAttributeMappings(localContext, relationship.ReferencingEntity);
        RemoveSourceValue(attributeMappingsList, relationship.ReferencedAttribute);
        UpdateChildEntities(localContext, attributeMappingsList, relationship.ReferencingEntity, relationship.ReferencingAttribute);
    }
}

So this is still missing just one little thing. Just because users add a row to my custom entity “active mapping” there is no event to trigger the plugin. You can definitely do this also as a plugin to that entity itself and then create and remove events to the primary entities that exits, but this is where this solution ends. So for each primary entity that exists in the “active mapping” list you have to create an event yourself. But I think you can live with that.

Posted in Customizations, MS CRM, Plugin | Tagged , , | 1 Comment

Set a good Trace Log folder

When I started using the developer toolkit I noticed that I got a lot of errors in the event log of my server after I deployed the first plugin.

What I found was that the default CRM trace log file is not a good one, you have to change it on every installation to remove this error. It’s not hard it just annoying. The default is:

c:\crmdrop\logs

This is how you do it.

Open Power Shell and type:

Add-PSSnapin Microsoft.Crm.PowerShell
$setting = Get-CrmSetting TraceSettings
$setting.Directory="C:\Program Files\Microsoft Dynamics CRM\Trace"
Set-CrmSetting $setting
Get-CrmSetting TraceSettings

But note that this does’n actually change the trace log folder, this is just to remove the erros from the event log

Posted in CRM 2011, MS CRM | Tagged , | 2 Comments

Multi organization deploy: part 3 binding it together

Now for the third and final part of this multi organization tool I will bind all together and make it useful. First a quick look at the problem that I wanted to solve in the first place.

I wanted to have a tool that could connect to the same server and access all of the organizations and deploy solutions to them. However I wanted to have a bit control over the process so that I can choose which organizations to deploy to.

  • Lock users out of organization
  • Apply solution to specific organization
  • Be able to do things after solution is applied (migrations and others…)
  • Activate users again on organization

The more I though about it the more I realized that I have a tool that supports a lot of this, the plugin registration tool. So I decided that I might as well just tweak that and I will get a great tool.

There was a bit of cleaning away all the things that I did not need for this, so everything that actually has anything with registering a plugin had to go. However if you are doing this, this is not a required step, I just like to remove things I don’t need. Next up is the fun part, hijacking the tool.

The plugin registration tool is based on a simple UserControl, so start up with creating one. Then add just one constructor like this:

public ApplySolution(CrmOrganization org, MainForm mainForm)
{
    if (org == null)
    {
        throw new ArgumentNullException("org");
    }
    else if (org.Connection == null)
    {
        throw new ArgumentNullException("org.Connection");
    }

    InitializeComponent();

    this.m_org = org;
    this.m_con = org.Connection;
    this.m_mainForm = mainForm;
}

This it the look that I have chosen:

Apply Solution Plugin RegistrationIt’s simple and descriptive. You add the code from the first and the second blog entry and connect it to the buttons.

Now open OrganizationsForm in the plugin registration and change all references to “OrganizationControl” with the user control that you just created. In my case I named it “ApplySolution”. Search and replace it a great way for this (Ctrl + H in Visual Studio).

Apply Solution Plugin Registration in action

You click the one “disable active users”, now you are alone in your organization.
Browse your solution file and you can choose not to Activate Processes and Publish All Customizations when you import your file.
When all is done, activate the same users again.

Posted in CRM 2011, MS CRM, Tool | Tagged , , | Leave a comment

Multi organization deploy: part 2 import solution

Last entry was about how to disable and enable your users when you are about to deploy solutions. Now this time it’s about how to import solution to your organization.
Let’s start with what requirements I want to solve:

  • Import solution
  • Activate process
  • Publish solution

So I checked the SDK and took some code from there. This how it turned out:

private void import(string ManagedSolutionLocation, bool activateWorkflows, bool publishCustomizations)
{

    byte[] fileBytes = File.ReadAllBytes(ManagedSolutionLocation);

    // Monitor import success
    ImportSolutionRequest impSolReqWithMonitoring = new ImportSolutionRequest()
    {
        CustomizationFile = fileBytes,
        ImportJobId = Guid.NewGuid(),
        PublishWorkflows = activateWorkflows,
    };

    m_org.OrganizationService.Execute(impSolReqWithMonitoring);
    AddLineToOutput(string.Format("Imported Solution with Monitoring from {0}", ManagedSolutionLocation));

    ImportJob job = (ImportJob)m_org.OrganizationService.Retrieve(ImportJob.EntityLogicalName, impSolReqWithMonitoring.ImportJobId, new ColumnSet("data", "solutionname"));

    System.Xml.XmlDocument doc = new System.Xml.XmlDocument();
    doc.LoadXml(job.Data);

    String ImportedSolutionName = doc.SelectSingleNode("//solutionManifest/UniqueName").InnerText;
    String SolutionImportResult = doc.SelectSingleNode("//solutionManifest/result/@result").Value;

    AddLineToOutput("Report from the ImportJob data");
    AddLineToOutput(string.Format("Solution Unique name: {0}", ImportedSolutionName));
    AddLineToOutput(string.Format("Solution Import Result: {0}", SolutionImportResult));

    // This code displays the results for Global Option sets installed as part of a solution.

    System.Xml.XmlNodeList optionSets = doc.SelectNodes("//optionSets/optionSet");
    foreach (System.Xml.XmlNode node in optionSets)
    {
        string OptionSetName = node.Attributes["LocalizedName"].Value;
        string result = node.FirstChild.Attributes["result"].Value;

        if (result == "success")
        {
            AddLineToOutput(String.Format("{0} result: {1}", OptionSetName, result));

            if (publishCustomizations)
            {
                m_org.OrganizationService.Execute(new PublishAllXmlRequest());
            }
        }
        else
        {
            string errorCode = node.FirstChild.Attributes["errorcode"].Value;
            string errorText = node.FirstChild.Attributes["errortext"].Value;

            AddLineToOutput(string.Format("{0} result: {1} Code: {2} Description: {3}", OptionSetName, result, errorCode, errorText));
        }
    }
}

Now for the next time I will have to get all the pieces together.
Perhaps in a familiar tool?

Posted in MS CRM | Tagged , , | 1 Comment

Multi organization deploy: part 1 disable users

So I am starting a new miniseries to describe how I solved a problem. I was facing the problem of deploying the same solution to different organizations.

Problem: One of my customers wanted to separate the data in different organizations they weren’t happy about having the possibility to share data between different parts of their organizations so we took the choice of separating the data in different CRM organizations. However they still wanted the same customizations to be deployed in all of the organizations so we suddenly were stuck with A LOT of organizations and needed to be able to deploy to them faster than standard CRM allows us to.

Of course I thought that this “tool” that we were supposed to use had to be great and not only solve our immediate problem but some other things that bothered me about the deployment process in CRM.

This is bout that first problem, how do you shut users out when you want to deploy?

Scenario: you are about to deploy new features in CRM and that unfortunately involves some migration of data. While you are migrating you don’t want the users to change the data that you are yourself are changing.

I came up with the solution that you can disable all of the users (except you) and then reactivating all of the users that have been changed today by you.

If users are disabled they are not allowed into CRM, so that’s what we decided to do.

QueryExpression expr = new QueryExpression(SystemUser.EntityLogicalName);
//Active
expr.Criteria.AddCondition("isdisabled", ConditionOperator.Equal, false);
//Not Support User
expr.Criteria.AddCondition("accessmode", ConditionOperator.NotEqual, 3);
//Not me
expr.Criteria.AddCondition("systemuserid", ConditionOperator.NotEqualUserId);

EntityCollection activeUsersResponse = m_org.OrganizationService.RetrieveMultiple(expr);
SetStateRequest deactivateUserRequest = new SetStateRequest()
{
    EntityMoniker = new EntityReference(SystemUser.EntityLogicalName, Guid.Empty),
    State = new OptionSetValue(1),
    Status = new OptionSetValue(-1),
};

foreach (SystemUser activeUser in activeUsersResponse.Entities)
{
    deactivateUserRequest.EntityMoniker.Id = activeUser.SystemUserId.Value;
    m_org.OrganizationService.Execute(deactivateUserRequest);
}

It’s remarkably small piece of code. Then when we are ready let’s reactivate those users again.

QueryExpression expr = new QueryExpression(SystemUser.EntityLogicalName);
//Not Active
expr.Criteria.AddCondition("isdisabled", ConditionOperator.Equal, true);
//Not SYSTEM User
expr.Criteria.AddCondition("fullname", ConditionOperator.NotEqual, "SYSTEM");
//Today
expr.Criteria.AddCondition("modifiedon", ConditionOperator.Today);
//modified by me
expr.Criteria.AddCondition("modifiedby", ConditionOperator.EqualUserId);

EntityCollection deactiveUsersResponse = m_org.OrganizationService.RetrieveMultiple(expr);
SetStateRequest activateUserRequest = new SetStateRequest()
{
    EntityMoniker = new EntityReference(SystemUser.EntityLogicalName, Guid.Empty),
    State = new OptionSetValue(0),
    Status = new OptionSetValue(-1),
};

foreach (SystemUser deactiveUser in deactiveUsersResponse.Entities)
{
    activateUserRequest.EntityMoniker.Id = deactiveUser.SystemUserId.Value;
    m_org.OrganizationService.Execute(activateUserRequest);
}

That’s it.

Next time I will actually have to import a solution, that’s a little bit trickier, stay tuned.

Posted in MS CRM | Tagged , , , | 2 Comments

Back to basics: Creating an entity

Today I am going back to the basics, writing about creating a new entity.

First off, when do you want to create a new entity? Well sometimes the choice is easy sometimes it is not. Here are some of the things I think of before I go and create a new entity.

Does it fit in an existing entity?

What is the con’s of having it in an already existing entity? Most of the time it is possible to get the data into another entity but the restrictions might be too much. I.e. when you have details to a header of any sort (quote and quotedetail), you can create fields for the details in the header but then you can only create a predefined childs. For example you create a fields address 1, address 2 and address 3 yourself but then there are no way to create a fourth address for the users.

Do you need to control security?

In CRM 2011 you CAN have field level security but if you have a bunch of sensitive data that you might want to add to Account, going for a new entity might be easier to manage.

When I have decided to go for a new entity there is a new set of questions to be answered, however this is more related to what data you need.

What should your name be? It sounds easy but I have found that it’s not so easy and I find myself recreating entities just to change their names.

Do you need a new activity type (1)? Then the rest is already set for you

Primary field: Never choose an external ID or anything like that even thou you know that always has text, name is pretty good

Ownership (2): I struggle here sometimes. If this is a config entity that everyone needs to read, then it’s an easy choice but the reality is never easy. I usually chose “User/Team” when in doubt.

Last but not least, do you really need to have:

  • Notes and attachments
  • Activities
  • Connections
  • Queues

If not or in doubt remove it, I always do.

Posted in CRM 2011, Customizations | Tagged , , | Leave a comment

CRM 2011 Demo Builder

I might be a little bit late here but there is a new Demo Builder for CRM 2011:

http://demobuilder.cloudapp.net/

This is quite a neat tool if you want a big demo fast. However the demo isn’t built in 15 minutes. I will walk you through the steps.

First a few advises, I started with trying this on my Windows 8 machine and there are a couple of things that does not work so great with windows 8. The click once application (we are coming to that) need Internet Explorer 9 to work so Windows XP (or earlier) is not to think of here.

So I started with a blank Windows 7 machine and installed .Net 4 Framework Extended.

Now it’s time to visit the demo builder.

Of course you want to “Build a Demo” and start to install the Click once program:

Select that you want to build a demo using CRM Online, Office 365 and Windows Azure (which is the only choice).

Now it’s time to choose a name, region, language and you are set:

Tip don’t do this behind a firewall I had some problems doing this:

Choosing region will get you data that corresponds to that region, i.e. the Live ID generated will have names that suite you.

Now you can relax for a while and let the program do its magic:

This took me at least one hour.

Wait for your email:

Finally we can use the demo but what did we get?

First off let’s take a look at the Office 365 prortal to get that out of the way.

Go go: https://portal.microsoftonline.com/ and sign in with you new identity, I got Mark Harrington, if you don’t have Office, you can go ahead and install it from here. We move to CRM while that is in progress

In CRM we have a couple of solutions:

  • Inside View
  • Bing Maps
  • Microsoft xRM Partner Portal
  • Activity Feeds
  • and Document Management Configured

Dashboards with data in every chart (wow):

Inside View (one of my personal favorites):

SharePoint Site:

Azure Portal with a CRM connection:

Posted in CRM 2011, MS CRM | Tagged , , , , | Leave a comment