Monthly Archives: May 2011

Comparing SalesForce with MicroSoft Dynamics xRM

MicroSoft Dynamics (xRM)

SalesForce.com (SF)

Design * offers more options during entity creation* to make changes to a field, it has to be deleted then recreated and if there are forms/views with that field, such dependencies have to be deleted first * easier to create entities (objects)* fields can be changed/deleted and all the related forms are automatically updated

* Master-Detail relationships include cascaded deletes and detail roll-ups (sum, count, average, etc) but M-D relationships can’t be chained, which results in having to implement them as Lookups and lose the benefit of summarization offered by Master-Relationships

Data Manipulation * smarter data import tool with lookup of foreign keys by ID or description* doesn’t have the UpSert function (update existing records and insert new ones) that SalesForce has

* uses SQL Server

* smart data import (lookup of foreign keys)* requires external tool and has the little quirk of having to use UpSert instead of Insert

* the UpSert function updates existing records and insert new ones in one run

* the SF query language (SOQL) has limitations

Infrastructure * can be hosted on premises* no Governor limits if hosted in house

* on premises installation/configuration takes a long time

* there is no option to host SF on premises* subject to Apex Governor Limits because SF is a multi-tenant platform

* automatic upgrades twice a year, each release with a lot of new features

Bugs * error messages popup often although requiring only a retry from the same screen without closing/reopening * no bugs found
Speed * slow, not very responsive * very responsive
Usability * the user interface is familiar to MS Office users but it is very busy

* can be embedded in Outlook

* integrated with Sharepoint

* very basic/light-weight mobile web app (will likely require 3rd party for mobile access)

* offers a much cleaner/simpler interface* all users like it

* Outlook and Sharepoint integration requires a lot more effort

* SF is mobile ready

Customization /Programming * .net:  Visual Studio, C#, VB* seems very flexible * requires unit tests to publish triggers and classes* default filter screens can be customized only to a certain degree and beyond the basics, it requires coding

* requires creation of report types, then reports that use them

* Apex is very similar to Java and C#

* subject to Apex Governor Limits

* limited deployments to Production in a given period as per the contracted level of service

Workflows * offers more types of process steps and seems more cohesive than SF * there are 2 types:  approval process (triggered by the user) and workflow (triggered when a record changes in a specific way)
Forward thinking considerations * MS is one of the world’s largest R&D spenders * clearly the leader in CRM

* SF spends 8% of revenue in R&D while (2008 data) and 5x as much in sales and marketing

* SF buys many other companies and integrate their proven technologies into the platform

* customer base is very large

* the company leader is a visionary

Cost * measured roughly 1/3 to 1/2 of SF’s cost in 1, 3 and 10 years for a high number of users* can be paid monthly * entry price is cheaper (Group edition with 5 users)* annual upfront payment

The above is based on my experience with a proof of concept application and online research. Feel free to comment and add your corrections or a different point of view.

Pertinent links:

– MS Dynamics 2011 vs salesforce.com
Developer frustration
MicroSoft Dynamics CRM vs SalesForce – How do you choose?
Microsoft Dynamics CRM vs. Salesforce.com vs. Microsoft Office Business Contact Manager
– Comparing Microsoft Dynamics CRM To Salesforce
MicroSoft Dynamics CRM vs SalesForce (MS partner)
– Evaluating Software Vendors (from a MS shop)
– Microsoft Dynamics CRM: Much More Than Meets the Eye – Part 2 and Part 3
– Salesforce vs. Microsoft CRM: Relationship Management Applications
– Why We Chose to Work with Salesforce.com

Advertisements

How to disable/enable all validation rules for data loading

While working on a recruiting application, I found a solution for being able to load data into a SalesForce application without being blocked by validation rules.

Validation rules are usually intended to be applied only when a user creates a record and not when data is being imported from an external database. In this recruiting application, candidate records go several stages in a sequence (1-lead, 2-phone, 3-applicant, 4-interview, 5-contract negotiation, etc.) and this validation rule prevented the import process from loading candidate records in a stage higher than lead.

So the solution was to create a Custom Setting of type Hierarchy with a flag/checkbox in it that disables validation rules for a given user or profile. That is, all the validation rules will include this flag and only apply when the value of this flag is enabled.

To implement it:

1) click Setup, then on the left side, click App Setup/Develop/Custom Settings.

2) click New and create your settings as hierarchy/public

3) now create a custom field of type Checkbox:  click New, select Checkbox, click Next, type the name of the field as “Disable Validation Rules”, default to Unchecked, click Next, then click Save.

4) in the Custom Setting Definition/Your App Settings screen, click Manage to start configuring the setting you just created.

5) click the New button above “Default Organization Level Value”, leave the checkbox “Disable Validation Rules” unchecked and then click Save.

6) click “Back to List”, click the New button at the bottom, select Profile or User then choose the user id or profile that will perform the data loading, then click “Disable Validation Rules” and click Save.

7) now edit the validation rule appending the expression (highlighted below)
&& NOT( $Setup.Your_App_Settings__c.Disable_Validation_Rules__c )

  8 ) click Save

9) now the validation rule will only apply if the setting checkbox
Disable Validation Rules is unchecked for your profile/user

10) you can now load data freely and then, later, re-enable all
validation rules for your profile/user by changing the custom
setting.

11) you can use this way of implementing Custom Settings on
triggers too, just use the syntax below:

 Your_App_Settings__c s = 
 Your_App_Settings__c.getInstance( UserInfo.GetUserID() ); //or Profile
 if( s.Disable_Validation_Rules__c ) return; // skip trigger...

Pertinent Articles:
– Accessing Custom Settings
Let’s make some magic…

Upserting into SalesForce using .net and SOAP API

I’ve been experimenting with the classes created by Darren Terrell in the article “SalesForce integration with .net web services SOAP API” and found that there is a limit of 200 rows for upserts, so I’ve changed the Upsert() method as below to divide and submit the upserts in batches of 200 items at a time.

public UpsertResult[] Upsert(string externalID, sObject[] items)
{
   SetupService();
   // send in batches of 200 updates
   int iIndex = 0;
   List objResults = new List(items.Count());
   while (iIndex < items.Count())
   {
      sObject[] obj200Items = items.SubArray(iIndex, 200);
      objResults.AddRange( salesforceService.upsert(externalID, obj200Items) );
      iIndex += 200;
   }
   return objResults.ToArray();
 }

The SubArray method is defined as an extension below:

public static class ArrayExtensions
{
 /// <summary>
 /// Extracts a subarray of a given number of items (count) from an array starting from a given position (startIndex)
 /// </summary>
 /// <typeparam name="T"></typeparam>
 /// <param name="arr">Array from which to extract a subarray</param>
 /// <param name="startIndex">Position from where to obtain the array items</param>
 /// <param name="count">Number of items</param>
 /// <returns></returns>
 public static T[] SubArray(this T[] arr, int startIndex, int count)
 {
    var sub = new T[count];
    int iActualCount = count;
    if( arr.Count() - startIndex < count )
       iActualCount = arr.Count() - startIndex;
    Array.Copy(arr, startIndex, sub, 0, iActualCount);
    return sub;
 }
}

jQuery Visual Cheat Sheet

Very useful!

http://woorkup.com/wp-content/uploads/2011/05/jQuery-1.6-Visual-Cheat-Sheet.pdf

SalesForce integration with .Net

Darren Terrell has a series of very good articles about SalesForce and .Net integration:

SalesForce integration with .net web services SOAP API
How to make .net query/insert/upsert/update/delete SalesForce objects.

SalesForce Integration with .net custom web services
How to make .net consume a web service created in SalesForce.

SalesForce Integration with .net callouts
How to make SalesForce consume a SOAP/REST web service created in .net.

Types of document storage in SalesForce

Types of document storage in SalesForce:

Feature/ Type CRM Content Files tab Documents tab Notes and Attachments related list Google Docs tab
Can associate with a record Yes, through content types/workspaces /lookup fields No No Yes, mandatory No
File size limit ? 500 Mb/user 100 Mb/file 5 Mb/file 5 Mb/file, 10 Mb/email 1 Gb/file upgradeable
Can be imported through Data Loader Yes No Yes Yes No
Externally accessible through link Allowed Allowed Allowed for images only No Yes
Can see files from other tabs No Yes No No No
Tracks when file is viewed Yes No No No No
Search inside the file Yes Yes Yes No No
View file in web page Yes Yes No No Yes
Mobile access Yes ? No No ?
Can create content packages Yes Yes No No No
Can follow/ be notified of file updates Yes Yes No No No
Private section Only if file is on personal workspace Yes Yes No No
Relation to Chatter Yes Yes No ? No

Pertinent discussions:

What’s the actual difference between the Documents, Content and Files tab?
What is the difference between a Chatter file and a Salesforce CRM Content file?
Viewing and Editing Google Docs, Notes, and Attachments
What is the CRM Content file size limit?
SF App Limits Cheatsheet

Differences between Files, Salesforce CRM Content, Salesforce Knowledge, Documents, and Attachments

Please let me know if you find any inaccuracies in the table above.

Getting name of previous owner of account

1) Create a lookup field “Previous Owner” in Account as LookUp(User)

2) Create a trigger like this:

trigger OnAccountOwnerChanged on Account (before update) {
    for( Account a : Trigger.new ) {
        Account AcctBeforeUpdate = Trigger.oldMap.get( a.Id );

        if( AcctBeforeUpdate.OwnerId != a.OwnerId
                && ((String) a.OwnerId).startsWith( '005' ) )
            a.Previous_Owner__c = AcctBeforeUpdate.OwnerId;
    }
}

3) Use the “Previous Owner” in your reports and formulas. It will let you reference the user name since it is a lookup field (eg.: Previous_Owner__r.LastName + ‘, ‘ + Previous_Owner__r.FirstName).

4) The part that says

&& ((String) a.OwnerId).startsWith( '005' ) 

is for when the owner changes from one user to a queue. It will ignore queues as owners when setting the previous owner..

5) Create a test class like this:

public with sharing class TestTriggerOnAccountOwnerChanged {
    public static TestMethod void TestEverything() {
        // get default profile
        Profile objP = [ SELECT Id FROM Profile WHERE Name='Standard User' ]; 

        // create 2 users
        User objU1 = new User( Alias = 'TTOAOC1', Email='TTOAOC1@testorg.com' 
                        , CommunityNickName = 'TTOAOC1', EmailEncodingKey='UTF-8'
                        , UserName='TestingTTOAOC1@testorg.com', LastName = 'TTOAOC1'
                        , LocaleSidKey='en_US', ProfileId = objP.Id
                        , TimeZoneSidKey='America/Los_Angeles', LanguageLocaleKey='en_US' );
        insert objU1;
        User objU2 = new User( Alias = 'TTOAOC2', Email='TTOAOC2@testorg.com' 
                        , CommunityNickName = 'TTOAOC2', EmailEncodingKey='UTF-8'
                        , UserName='TestingTTOAOC2@testorg.com', LastName = 'TTOAOC2'
                        , LocaleSidKey='en_US', ProfileId = objP.Id
                        , TimeZoneSidKey='America/Los_Angeles', LanguageLocaleKey='en_US' );
        insert objU2;

        // create account
        Account AccountToTest;
        system.runAs( objU1 ) {
            AccountToTest = new Account( Name = 'TestingAccountOwnerChange' );
            insert AccountToTest;
        }

        // change owner of account
        system.runAs( objU2 ) {
            AccountToTest.OwnerId = objU2.Id;
            update AccountToTest;
        }

        // test the previous owner field
        AccountToTest = [ SELECT Id, Previous_Owner__C FROM Account WHERE Id = :AccountToTest.id ];
        system.assertEquals( objU1.Id, AccountToTest.Previous_Owner__c, 'Previous Owner Test : ' + AccountToTest.Previous_Owner__c );
    }
}

SalesForce blogs