The blog post James's colleague wrote is wrong. If you follow the link he uses you'll see the table for lists he uses is different from the one he references.
I use these rules of thumb not to have to look at that post all the time:
- Never use BeforeProperties. Use properties.ListItem.
- The properties.ListItem will act as BeforeProperties in an '-ing' event, and as AfterProperties in an '-ed' event.
- You cannot get information about what was before the event in the '-ed' EventReceiver, only in the '-ing'.
So if you want to test if a specific column was changed you have to use the ItemUpdating instead of the ItemUpdated eventreceiver. Is there a reason why do you want to use the ItemUpdated receiver specifically? Do you absolutely need to do something Asynchronous?
If so, indeed the best way to do it is to add a hidden bit column, or an int column if you want to cheaply store changed flags for several columns. Test and set in the ItemUpdating, then read in the ItemUpdated.
Ok, so inside the ItemUpdating, you might use the following code:
if (properties.ListItem["column"] != properties.AfterProperties["column"])
{
}
This won't work though, because these collections return generic objects, which you will have to convert to something comparable. Usually strings will do just fine.
if (properties.ListItem["column"].ToString() != properties.AfterProperties["column"].ToString())
{
}
This may seem fine, but if the tested column wasn't filled in it's not part of the AfterProperties collection, and trying .ToString() on a null will result in a runtime error. So first we need to check for that. Of course, if both the ListItem and the AfterProperties lack the column, it hasn't changed either.
if ((properties.ListItem["column"] != null
&& properties.AfterProperties["column"] != null) ?
properties.ListItem["column"].ToString() != properties.AfterProperties["column"].ToString()
: !(properties.ListItem["column"] == null && properties.AfterProperties["column"] == null))
{
}
This becomes a bit unreadable and unwieldy, so I made a nice little function out of it.
public static bool AreEqualStringObjects(object obj1, object obj2)
{
if (obj1 == null) return obj2 == null;
if (obj2 == null) return false;
return obj1.ToString() == obj2.ToString();
}
With this we can make an easy-to-read set of ItemEventReceivers:
public override void ItemUpdating(SPItemEventProperties properties)
{
SPItemEventProperties afterProp = properties.AfterProperties;
int bitstrChanged = 0;
if (!AreEqualStringObjects(properties.ListItem["column"], afterProp["column"])
{
bitstrChanged += 1;
}
if (!AreEqualStringObjects(properties.ListItem["column2"], afterProp["column2"])
{
bitstrChanged += 2;
}
if (!AreEqualStringObjects(properties.ListItem["column3"], afterProp["column3"])
{
bitstrChanged += 4;
}
AfterProp["hiddencol"] = bitstrChanged;
}
public override void ItemUpdated(SPItemEventProperties properties)
{
int bitstrChanged = Convert.ToInt32(properties.ListItem["hiddencol"].ToString());
if (bitstrChanged & 1) { // column1 changed }
if (bitstrChanged & 2) { // column2 changed }
if (bitstrChanged & 4) { // column3 changed }
}
If you want to change columns in the ItemUpdated, remember to set EventFiringEnabled before you do your SystemUpdate() or Update(), so that the eventreceiver isn't triggered again.
If someone sees anything that could be more efficient please comment, but I'm pretty sure this works at least. Hope this helps.