2

I am trying to make an activity that migrates an address from a lead to a contact. We do not use the default Address1 and Address2 in our CRM deployment (not my decision) so although the Qualification process does copy the address entered in the lead to the contact, it does so using the Address1 fields. I am using the code below, and everything seems to be working (no errors registering, no errors running the workflow that uses this activity). There's just one problem... nothing happens. Although there are no errors, no address gets created. I am running as the CRM admin so this shouldn't be a permissions thing, however even if it was shouldn't that generate a security exception? Any ideas why this is not working?

public class MigrateLeadAddressToContactActivity : CodeActivity
{
    [Input("Contact input")]
    [ReferenceTarget("contact")]
    public InArgument<EntityReference> InContact { get; set; }

    protected override void Execute(CodeActivityContext executionContext)
    {
        // Get the tracing service                         
        var tracingService = executionContext.GetExtension<ITracingService>();

        if (InContact == null)
        {
            const string errorMessage = "Contact was not set for Address Migration Activity";
            tracingService.Trace(errorMessage);
            throw new InvalidOperationException(errorMessage);
        }

        // Get the context service.                         
        var context = executionContext.GetExtension<IWorkflowContext>();
        var serviceFactory = executionContext.GetExtension<IOrganizationServiceFactory>();

        // Use the context service to create an instance of CrmService.             
        var service = serviceFactory.CreateOrganizationService(context.UserId);

        //Retrieve the contact id
        var contactId = this.InContact.Get(executionContext).Id;

        // Get The Lead if it exists
        var query = new QueryByAttribute
                    {
                        ColumnSet = new ColumnSet(
                            new[]
                            {
                                "address1_line1",
                                "address1_line2",
                                "address1_line3",
                                "address1_city",
                                "address1_stateorprovince",
                                "address1_postalcode",
                                "address1_country",
                            }
                        ),
                        EntityName = "lead"
                    };

        // The query will retrieve all leads whose associated contact has the desired ContactId
        query.AddAttributeValue("customerid", contactId); 

        // Execute the retrieval.
        var results = service.RetrieveMultiple(query);

        var theLead = results.Entities.FirstOrDefault();
        if (null == theLead)
        {
            tracingService.Trace("Activity exiting... Contact not sourced from Lead.");
            return;
        }

        var newAddress = new Entity("customeraddress");
        newAddress.Attributes["name"] = "business";
        newAddress.Attributes["objecttypecode"] = "contact";
        newAddress.Attributes["addresstypecode"] = 200000;
        newAddress.Attributes["parentid"] = new CrmEntityReference("contact", contactId);
        newAddress.Attributes["line1"] = theLead.Attributes["address1_line1"];
        newAddress.Attributes["line2"] = theLead.Attributes["address1_line2"];
        newAddress.Attributes["line3"] = theLead.Attributes["address1_line3"];
        newAddress.Attributes["city"] = theLead.Attributes["address1_city"];
        newAddress.Attributes["stateorprovince"] = theLead.Attributes["address1_stateorprovince"];
        newAddress.Attributes["postalcode"] = theLead.Attributes["address1_postalcode"];
        newAddress.Attributes["country"] = theLead.Attributes["address1_country"];

        service.Create(newAddress);
        tracingService.Trace("Address Migrated from Contact to Lead.");
    }
Konrad Viltersten
  • 36,151
  • 76
  • 250
  • 438
Bitfiddler
  • 3,942
  • 7
  • 36
  • 51
  • Is it possible for you to log a LOT of information? Push out all parts of the code to see what you're getting, it might be some sort of uncaught error somewhere. – Rickard N Mar 05 '13 at 19:21

6 Answers6

0

Everything looks right to me. I'd try throwing an exception if no lead is found, just to verify that your query is working correctly. If you don't get an exception, verify that you don't have any plugins on the create of the customer Address that could be causing it not to be created.

Daryl
  • 18,592
  • 9
  • 78
  • 145
0

Assuming there are indeed no exceptions thrown, the only other exit for that piece of code is here

if (null == theLead)
{
    tracingService.Trace("Activity exiting... Contact not sourced from Lead.");
    return;
}

So it would be fair to assume that theLead is null at this point and the workflow ends gracefully.

To test this, throw an exception instead. Assuming the exception is throwm, you can investigate why it is null. Presumably, the value you are filtering on is not valid or not what you expect - or the field is empty.

glosrob
  • 6,631
  • 4
  • 43
  • 73
  • It appears that the qualification process is not setting the customerId on the Lead, which according to the documentation is: "Unique identifier for the account or contact associated with the lead." CRM strikes again... guess I will have to work around this somehow. Thanks for you help glosrob (and Daryl). – Bitfiddler Mar 05 '13 at 22:39
0

Try tracing out the returned GUID from Service.Create and see what that contains.

Something like:

Guid addressId = service.Create(newAddress);
tracingService.Trace(string.format("Address ID Result: {0}", addressId));
  • Question: Where do I find the ouput of a Trace? I have not found a good answer to this. I have tried a number of things but have not been able to find where these trace messages end up. – Bitfiddler Mar 05 '13 at 22:37
  • The trace is given in the exception message. It is very useful but also not entirely intuitive :) Throw an exception on purpose and have a look at the returned error message. Should see your trace in there. – glosrob Mar 05 '13 at 22:52
  • I see. Thank you for the explanation. I never would have guessed that "Trace" in MS speak means "Additional info for Exception". What a strange world CRM is shaping up to be. Interesting that even books written specifically for CRM 2011 published by Microsoft press don't talk about this... grrr. – Bitfiddler Mar 06 '13 at 00:19
0

You seem to be trying to create an Address record directly, bypassing the two already created for the Contact record by default (it always creates these two records, whether or not you use them and whether or not you want the "shadow" fields from them to be visible in the Contact form). You say nothing happens - is that verified by looking in the SQL tables or at the "More Addresses" link in the Contact record?

One thing you seem to have omitted from the create is specifying the address number (and ideally doing this by first finding the largest number on all addresses already associated to the Contact and incrementing by 1). This would need to be at least 3 in all cases.

If you look at the Address entity Associated View definition you will see that it explicitly filters to include addresses where address number > 2 in order to provide the illusion that 1 and 2 are "part of" the Contact and the "more" are separate records, whereas in fact all addresses including 1 and 2 are rows in the address entity table in their own right.

So I suspect that if the value is left as null, the address simply won't show up in the GUI as it does not meet the filter criteria. It may or may not also fail to create as it relies on that field quite heavily internally so I would treat it as "system required" and make sure to include it in the column collection.

Alternative approach:

If you are never using the fields for Add_1 or Add_2 on the form, you could still use the Address record with address number 2 to store your data from the Lead, making it an update of this existing (empty) record rather than a create.

Modify the Address Associated view to change the filter so it will include Add_2 in the view, by making it filter for address number > 1 instead of 2.

This will appear to users like there is one address there already and they can add more with normal functionality for address_3, 4 etc (which they might consider to be 2, 3 etc). Address 1 exists but is empty and can be ignored.

AdamV
  • 1,467
  • 8
  • 11
  • Hi Adam, thanks for the response. To your first question, yes I have verified that the records are in fact not getting created in the database. – Bitfiddler Mar 06 '13 at 16:25
  • I will try adding the address number and see if that helps. Others have posted on other forums giving examples of creating addresses without using the address number so I didn't realize that was even a useful field. – Bitfiddler Mar 06 '13 at 16:27
  • As far as the alternative approach, I can't do that because the existing system has a bunch of workflows and plugins which assume the address 1&2 are ignored. I am not tasked with re-writing existing/working functionality so I have to work around this. – Bitfiddler Mar 06 '13 at 16:29
  • It would be interesting to see how they "assume the address 1&2 are ignored". If they do this by using the address number then that is clearly relevant, if they simply return collections of address records linked to the Contact rather than using the address fields on the Contact entity then that should still work (not forgetting that the fields "on" the Contact record are in fact just two more address records). It sounds to some extent like someone has over engineered things to make CRM work in a way it could already do much more simply by itself, and now you are having to work round this. – AdamV Mar 06 '13 at 20:49
0

Ah! I see now that you are doing the query backwards! (or rather you don't need a query at all)

The customer ID field is only used when the user specifies that this is a Lead for an existing customer, the intention being to create an Opportunity linked to that customer.

If you Qualify a Lead and create a Contact (and/or Account and/or Opportunity) the field which connects them together is on the newly created record and called "originatingleadid" in all three cases for all three entities. Notice this supports qualifying, unqualifying and requalifying a lead several times over and every record created by doing so knows where it came from (think "who's your daddy?" and you won't forget this!).

The Lead would only be able to remember the last of these if the relationship were the other way round.

So to fix the query part of the code, replace the query and just get the originatingleadid explicitly from the lookup field on the Contact you are dealing with.

My other answer still stands as a better way to handle this process overall using update rather than create, but this answer should get you on the way to using the correct lead record in the first place.

AdamV
  • 1,467
  • 8
  • 11
0

If this code is written for CRM 2011 (and it looks like it is), then you need to set the addresstypecode value not with an integer, but with an OptionSetValue object:

newAddress.Attributes["addresstypecode"] = 200000; 

becomes

newAddress.Attributes["addresstypecode"] = new OptionSetValue(200000);

I would actually expect this to throw an exception. I'm not sure why it isn't.