Thursday, July 10, 2008

CRM DynamicEntity Limitations - Part 1

DynamicEntities in CRM are either a love or hate relationship. Either you love the flexibility of using them within code (no need to do WebService proxy updates as configuration changes are made in CRM) or you hate them for their lack of usability.

What do I mean by their lack of usability? Namely this:

Microsoft.Crm.Sdk.DynamicEntity leftSide = new
Microsoft.Crm.Sdk.DynamicEntity("account");

Microsoft.Crm.Sdk.DynamicEntity rightSide = new
Microsoft.Crm.Sdk.DynamicEntity("account");

leftSide.Properties.Add(new StringProperty("name", "Fabrikam"));

rightSide.Properties.Add(new StringProperty("name", "Fabrikam"));

Assert.IsTrue(leftSide == rightSide); // this will fail every time

Assert.IsTrue(leftSide.Equals(rightSide)); // this will fail every time
too

I find that incredibly annoying. I love the TDD style to coding and not having these two objects do a data style comparison kills me. I have rarely run into a need to do a reference comparison and if so, I'll explicitly call a ReferenceEquals. To me. "Equals" is always a data-comparison. I mean, if you do "test" == "test" you'll get True, right?

Now, I could always create routines that will compare DynamicEntity classes (data wise) during my unit testing, but then every time I have some comparison of objects I have to create a new matcher. I tend to use NMock and NUnit which means I've got two different libraries to work with and about 3-4 different types of matching interfaces I have to implement. Why would I want to do that!?

Now, I could go off on MS for this sorely lacking detail but I'll refrain. That is better left for a well thought-out and tested post going over ALL the pain points! =-}

Here's my solution (which will hopefully make it up on CodePlex at some point as part of a general "Better CRM Library" project.

I first take and create my own DynamicEntity class that inherits from MS's DynamicEntity:

public class DynamicEntity : Microsoft.Crm.Sdk.DynamicEntity {}

Now let's get to some overriding! Now, I wouldn't say this code has been thoroughly unit tested or integration tested, so use at your own risk. It is only meant to point you in the right direction for your own implementations:


public class DynamicEntity : Microsoft.Crm.Sdk.DynamicEntity
{
public
DynamicEntity(string name)
: base(name)
{
}
public
DynamicEntity()
{
}
public override bool Equals(object obj)
{
if
(!(obj is Microsoft.Crm.Sdk.DynamicEntity obj is
DynamicEntity))
{
return false;
}
var rightSide = (DynamicEntity)
obj;
if (Name != rightSide.Name)
return false;
foreach (Property
property in Properties)
{
if
(!rightSide.Properties.Contains(property.Name))
return false;
if
(!PropertiesEqual(Properties[property.Name],
rightSide[property.Name]))
{
return false;
}
}
return
true;
}
public static bool operator ==(DynamicEntity
left,
DynamicEntity right)
{
if (Equals(left, null) &&
Equals(right, null))
{
return true;
}
if (Equals(left,
null))
{
return false;
}
return left.Equals(right);
}
public
static bool operator !=(DynamicEntity left,
DynamicEntity
right)
{
return !(left == right);
}
public bool Equals(DynamicEntity
obj)
{
return Equals(this, obj);
}
public override int
GetHashCode()
{
int runningTotal = 0;
runningTotal +=
Name.GetHashCode();
foreach (Property p in Properties)
runningTotal +=
p.GetHashCode();
return runningTotal;
}
public static bool
PropertiesEqual(object leftSide,
object rightSide)
{
if (leftSide ==
null && rightSide == null)
return true;
if (leftSide ==
null)
return false;
if (leftSide.GetType() !=
rightSide.GetType())
return false;
if (leftSide.GetType() == typeof
(string))
return leftSide == rightSide;
PropertyInfo[] properties =
leftSide.GetType().GetProperties(BindingFlags.Instance
BindingFlags.Public);
foreach (PropertyInfo property in
properties)
{
object leftValue = property.GetValue(leftSide,
null);
object rightValue = property.GetValue(rightSide, null);
if
(leftValue == null && rightValue != null)
return false;
if
(leftValue != null && !leftValue.Equals(rightValue))
return
false;
}
return true;
}
}

I haven't included the unit tests around this and I'm sure they don't have full code coverage anyway. But I want to get this out there.

This will hopefully be only one part of a whole series of these types of posts where I continue to expand and show how to handle some of the frustrating limitations of the CRM API. Let's just hope I've got the bench-time to get all them out.

=-}

P.S. Forgive me for the poor formatting of the code.

1 comment:

Vienna Advantage said...

Thanks – nice one. I have just converted all our routines to CRM 4.0
Buy CRM Software California