Thursday, July 10, 2008

CRM Properties and Values: More About "Equals"

As you can see from a previous post, I'm not real happy with the DynamicEntity class and what I consider an improper implementations of the Equals method on it. I attempted to fix this.
If you take a close look at the code I provided there was a second little trick in there I didn't touch on. I'm touching it now.

Assume this:
CrmBoolean leftSide = new CrmBoolean(true);
CrmBoolean rightSide = new CrmBoolean(true);
Assert.IsTrue(leftSide == rightSide);
Assert.IsTrue(leftSide.Equals(rightSide));

Can you guess what will happen with those assertions?! Yup, they fail.

GRRR! Why?!

Now, admittedly this is a little grayer of a subject. Conceptually speaking there is no way to tell that, from a business logic standpoint, that those should equal. Why do I say that?

Well, what if leftSide is actually a CrmBoolean from the account object and rightSide is a CrmBoolean from the contact record. Now, even though they are both true they don't carry the same meaning.

Ok, ok, I'll admit, that's pretty flimsy. I'm just trying to give MS the benefit of the doubt here on why they didn't do a data comparison here.

In the previous post the code has to compare these types of objects to see if they're equal. It does it by using reflection on all public properties. This code was originally part of a general CRM Utility class I'm working on but I refactored it into the object itself. But that's neither here nor there.

Let's assume for a moment (I love assumptions) that we actually want these properties to reflect a more realistic "Equals" operation.

We'll start by creating a new object that inherits from CrmBoolean:

public class CRMBoolean : Microsoft.Crm.Sdk.CrmBoolean { }


Just like before, we'll override the Equals method (and some related methods) so that we do a data comparison and not just a pointer comparison:



public class CRMBoolean
{
public CRMBoolean(bool value)
:
base(value)
{
}
public CRMBoolean()
{
}
public override
bool
Equals(object obj)
{
if (obj == null)
return false;
if
(obj.GetType() != typeof (CrmBoolean) && obj.GetType() != typeof
(CRMBoolean))
return false;
if (GetType() == typeof
(string))
return
this == obj;
PropertyInfo[] properties =
GetType().GetProperties(BindingFlags.Instance
BindingFlags.Public);
foreach
(PropertyInfo property in
properties)
{
object leftValue =
property.GetValue(this,
null);
object rightValue = property.GetValue(obj,
null);
if (leftValue
== null && rightValue != null)
return
false;
if (leftValue !=
null &&
!leftValue.Equals(rightValue))
return
false;
}
return
true;
}
public bool Equals(CRMBoolean
obj)
{
return
base.Equals(obj);
}
public override int
GetHashCode()
{
return
base.GetHashCode();
}
public static bool
operator ==(CRMBoolean
left,
CRMBoolean right)
{
return
Equals(left, right);
}
public
static bool operator ==(CRMBoolean
left,
CrmBoolean right)
{
return
Equals(left,
right);
}
public static bool operator !=(CRMBoolean
left,
CrmBoolean right)
{
return !(left == right);
}
public
static bool operator !=(CRMBoolean left,
CRMBoolean right)
{
return
!Equals(left, right);
}
}
However, here we have a slightly unfun task ahead of us. Namely, we have to override EVERY "CrmSomething" class in the library.

I'm not going to cover how to do this, but I'm intending to include these types of objects in the future library I'm working on (wow, two posts now mentioning this, I sure better produce something!). I'll likely generate them.

Once all the various "value" type objects in the CRM libraries are overridden with this "better" Equals method, then I'll no longer need to do that heavy-lifting comparison in the DynamicEntity's Equals method, which is always a bonus.

=-}

P.S. Still not caring too much about the formatting of the code. Sorry. Complain and I might fix it.

No comments: