Take the 2-minute tour ×
SharePoint Stack Exchange is a question and answer site for SharePoint enthusiasts. It's 100% free, no registration required.

I've got some code I need to run in an ItemUpdated event receiver method, but only if the value in specific column changed during the update.

My initial thought is to also implement some code to grab the BEFORE value int the ItemUpdating event and stash it in a member variable.

My worry about that is that I can't find a reliable source to reassure me that the same instance of my SPItemEventReceiver object will be used for both the updating and updated events, and that instance won't be reused in the context of a separate update.

Note:
- The list I am attaching this to does NOT have version history turned on.

So the question is:
What is a good technique that can be implemented such that I can check whether a specific column in a list changed in the ItemUpdated handler?

share|improve this question
add comment

3 Answers

up vote 13 down vote accepted

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:

  1. Never use BeforeProperties. Use properties.ListItem.
  2. The properties.ListItem will act as BeforeProperties in an '-ing' event, and as AfterProperties in an '-ed' event.
  3. 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.

share|improve this answer
    
You make a good point. My code could just as easily be in ItemUpdating. I think I'll go that way since it seems to simplify things. –  JohnFx Jun 16 '11 at 14:41
    
Oh, and one way to simplify the code (don't have edit rights yet) to tease out the null conditions is to use Convert.ToString() since it converts nulls to empty strings. Convert.ToString(properties.ListItem["column"]) != Convert.ToString(properties.AfterProperties["column"])) It makes the conditional a lot less hairy. –  JohnFx Jun 16 '11 at 14:44
    
Good tip! That definitely simplifies things. –  Janne Louw Jun 16 '11 at 15:24
    
Good work - thanks for the solution. –  Bernd Rickenberg May 9 '12 at 12:54
add comment

You could add a Hidden field (e.g. NeedsUpdate) to your list that you use as a flag. You set it to true during ItemUpdating and then check it and set it back to false in ItemUpdated.

share|improve this answer
add comment

Have a read up on BeforeProperties and AfterProperties.

Here's a blog post my colleague wrote.

And here's a more explicit example that illustrates what you're tring to achieve.

Here's the code on that page, in case it vanishes over time

public override void  ItemUpdating(SPItemEventProperties properties)
{
     if (properties.BeforeProperties["column"] != properties.AfterProperties["column"])
    {
        properties.Cancel = true;
        properties.ErrorMessage = "This column cannot be changed";
    }
}
share|improve this answer
    
I think the blog post gets me there, but the code sample won't work. I am attaching this to a list (not a doc library) so BeforeProperties won't be set. –  JohnFx Jun 15 '11 at 22:46
    
ItemUpdated should have both BeforeProperties and AfertProperties set in a List, though. I think Alex's code sample might need tweaking, so that you test the string values of each column (so BeforeProperties[...].toString()) –  James Love Jun 15 '11 at 22:55
    
If you can execute your additional code during ItemUpdating, then this is the place to do it. From your question, I understood you needed to wait until ItemUpdated to run that code. At that point you will not have BeforeProperties to compare to. –  Laurie Jun 15 '11 at 23:04
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.