10

I have been attempting to resolve this issue for about two weeks now. So far, everything I try has a problem that makes it unworkable.

Problem
One of the current requirements is that I need to be able to transfer calendar events from multiple site collections into a single, amalgamated merged calendar stored on an entirely separate collection.

To that end, I have downloaded and installed Useful Sharepoint Designer Custom Workflow Activities from CodePlex, which adds extra workflow options for SPD. One of these allows you to copy a list item to another list at another URL. Ok, great. Easy enough to dump all the items we need into the alagamated calendar, just use the workflow to copy it whenever a new item is created or modified.

The problem I'm running into now is that the amalgamated calendar starts accruing duplicates, as every time an item is copied, it does not overwrite the original. To that end, I've created a sequential workflow in Visual Studio that just iterates through the events, and deletes old ones based on their original IDs. Running this manually works fine; I'm left with just the latest versions of events.

Since the copy process uses the System Account, the new item in the amalgamated site collection will not trigger any item creation workflows, to prevent workflow recursion. Alright, I understand that, and try to work around that.

I have tried adding the workflow as a timer job, in order to trigger this workflow on a regular basis. I have tried just creating a permanent whileActivity loop, that doesn't end unless it gets cancelled or triggers a fault. The other addition is a delayActivity, which pauses after iterating through the list, and then just dumps it back to the top of the loop. The idea is to start the workflow manually, and let it go. Unfortunately, the first time works fine, and when the workflow starts again, I get an error: Object reference not set to an instance of the object.

I have also tried using EFWorkflowTimer, to trigger workflows at specific times. I get the same error message as above: Object reference not set to an instance of the object.

This leads me to believe that there is something in my code that doesn't parse properly when disassociated from manual execution.

My Code:

SPWorkflow.CreateHistoryEvent(workflowProperties.Web, this.WorkflowInstanceId, 0, workflowProperties.Web.CurrentUser, new TimeSpan(), "", "Beginning Checking for Duplicates", "");
using (SPWeb web = workflowProperties.Web)
{
    SPList list = web.Lists[workflowProperties.ListId];

            SPWorkflow.CreateHistoryEvent(workflowProperties.Web, this.WorkflowInstanceId, 0, workflowProperties.Web.CurrentUser, new TimeSpan(), "", "Looping through events", "");
            List<SPListItem> itemsToDelete = new List<SPListItem>();

            foreach (SPListItem item in list.Items)
            {
                SPWorkflow.CreateHistoryEvent(workflowProperties.Web, this.WorkflowInstanceId, 0, workflowProperties.Web.CurrentUser, new TimeSpan(), "", "Current Item: " + item["ID"], "");

                foreach (SPListItem search in list.Items)
                {
                    if (item["OriginalID"].Equals(search["OriginalID"]) && !item["ID"].Equals(search["ID"]))
                    {
                        SPWorkflow.CreateHistoryEvent(workflowProperties.Web, this.WorkflowInstanceId, 0, workflowProperties.Web.CurrentUser, new TimeSpan(), "", "Duplicate found.  Description: " + item["Description"], "");

                        if ((DateTime)item["Created"] > (DateTime)search["Created"])
                        {
                            SPWorkflow.CreateHistoryEvent(workflowProperties.Web, this.WorkflowInstanceId, 0, workflowProperties.Web.CurrentUser, new TimeSpan(), "", "Deleting Item: " + search["ID"], "");
                            if (!itemsToDelete.Contains(search))
                                itemsToDelete.Add(search);
                        }
                        else
                        {
                            SPWorkflow.CreateHistoryEvent(workflowProperties.Web, this.WorkflowInstanceId, 0, workflowProperties.Web.CurrentUser, new TimeSpan(), "", "Deleting Item: " + item["ID"], "");
                            if (!itemsToDelete.Contains(item))
                                itemsToDelete.Add(item);
                        }
                    }
                }
            }

            foreach (SPListItem currentItem in itemsToDelete)
            {
                currentItem.Delete();
            }

            SPWorkflow.CreateHistoryEvent(workflowProperties.Web, this.WorkflowInstanceId, 0, workflowProperties.Web.CurrentUser, new TimeSpan(), "", "Check for duplicates complete", "");
        }

When I check my workflow history, I see:

7/19/2012 11:19 AM  System Account  Creating and Invoking Duplicate Workflow    
7/19/2012 11:19 AM  System Account  Beginning Checking for Duplicates   
7/19/2012 11:19 AM  System Account  There was an issue checking duplicates. 
7/19/2012 11:19 AM  System Account  Object reference not set to an instance of an object.

So it seems to be erroring on:

using (SPWeb web = workflowProperties.Web)

To narrow it down, I added a history event right after creating the SPWeb object, and it doesn't hit that. So it's something to do with the top line, which doesn't make sense; the implication is that workflowProperties is uninitialized, but if that were so, it couldn't log the history events, as that's exactly what it's using.

Does anyone have any ideas what I'm doing wrong?

5
  • 3
    Have you considered expanding upon the Useful Sharepoint Designer Custom Workflow Activities action you are using to prevent duplicate items from being created? Following the logic from your snippet, you could add an updateListItem() method that would be called if a list item with the OriginalID exists.
    – LeviS
    Commented Jul 25, 2012 at 15:31
  • The workflow option I'm choosing has an overwrite option; it doesn't work, for some reason.
    – fbueckert
    Commented Jul 25, 2012 at 15:47
  • 1
    Taking a look at the docs, the overwrite option works only on Documents/Files in libraries. I assume this is because lists do not have a default, unique field to compare on. Whereas as the action can compare file names in a library to do the overwrite. If you can develop a custom workflow I expect you could download the source of the project and add in the ability to overwrite a list item for you special case.
    – LeviS
    Commented Jul 25, 2012 at 16:10
  • I agree with LeviS you can track the ID of the calendar item you want to update and update it using the current item. SP Designer is nice, but this seems like a job for VS workflows.
    – Mike
    Commented Jul 27, 2012 at 18:00
  • @Mike This IS a VS workflow. :P
    – fbueckert
    Commented Jul 27, 2012 at 18:33

3 Answers 3

7
+50

Why are you trying to cram this into a workflow? IMO, workflow wasn't designed to be run as a "scheduled" task. You're trying to hammer in a nail with a screwdriver.

You should use a custom timer job, or even easier, just create a console application:

  1. In visual studio, File -> New -> Project
  2. Choose Windows -> Console application
  3. Add a reference to Microsoft.SharePoint
  4. Make sure you target .NET 3.5 (version 4.0 will NOT work with the SharePoint API!!)
  5. Paste in the code from your workflow
  6. Set it up in task scheduler on the box to run your console application

Example code:

static void Main(string[] args)
{
    using (SPSite siteCollection = new SPSite("http://Server_Name"))
    {
        using (SPWeb web = siteCollection.OpenWeb("mysubsite"))
        {
            // maybe store ListId in your console app settings?
            SPList list = web.Lists["My Calendar"];

            // snipped for illustration
            // the rest of your logic to delete items goes here...
        }
    }

    Console.Write("Press ENTER to continue");
    Console.ReadLine();
}

Bonus:

  • Use the console app settings to store things like Site Url, web URL, and list name/id. Then you can change it easier! Right click on your project -> Properties -> Settings tab (scope all of them to application.. not user)
  • Use log4net and log to a share so you can view the logs in case something goes wrong

Of course.. all this is moot if you checked for duplicates in your consolidated list. In your original workflow, create a step to see if the item exists using ListItemID and do an "update" instead of a "create.

5
  • I don't know of any other way to automatically make changes to a list; my understanding of Sharepoint is still very spotty, so the assumption I should just create a console application is going to require some examples for me to understand it.
    – fbueckert
    Commented Jul 26, 2012 at 19:37
  • If I just paste my code into a console application and use a scheduled task to run it, it won't work; where is it supposed to pull the web and list references from? About the only helpful part is the last bit, where I'll have to extend the workflow tools to do what I want.
    – fbueckert
    Commented Jul 26, 2012 at 19:46
  • 1
    Check out this question for the basic structure of accessing a SharePoint site and list from a console application. It also covers the common pitfalls of console apps & SharePoint.
    – LeviS
    Commented Jul 26, 2012 at 20:25
  • @fbueckert My bad.. I wasn't sure how much you knew about console applications. I've edited my answer to include a link and an example. The example includes opening a SPSite, SPWeb, and SPList.
    – Kit Menke
    Commented Jul 26, 2012 at 21:20
  • I agree that a custom timer job is better suited based on the requirements in the question. The whole application then has context of both calendars, and will be more reliable.
    – Russell
    Commented Jul 30, 2012 at 1:23
5

Once you remove multiple duplicate entries from your amalgamated calendar, you could do this using a SPD workflow that runs on the amalgamated calendar each time an item is added.

Below is the workflow map of an example I tried: Screen shot of example workflow

Step 1 First we set a variable to the ID of a list item that has the same 'OriginalID' as the current item. Be sure to set the variable to have a data type of number, otherwise we will not be able to use it in the expression in Step 2.

If no duplicates exist this will return the ItemID of our current item. If a duplicate does exist it will return the ItemID of the old item. If multiple duplicates exist it will return the ItemID of the first duplicate item it finds.

Step 2 Compare the value we just stored in the variable with the ID of the current item. If the ID in the variable is less than the current item's id it proceeds to step three.

Step 3 Delete the old entry from the list.

The one issue I see with this is if you are using only the ItemID for the OriginalID field in the amalgamated list. Because you are bringing in items from multiple lists, you could run into the situation where a new item has the same OriginalID as item from another list. To get around this, you could add another column called "SourceList" and use that along with OriginalID to find duplicates.

3
  • 2
    +1 since I think this solves the actual root problem
    – Kit Menke
    Commented Jul 26, 2012 at 18:32
  • I can create a workflow to compare and eliminate duplicates; that's not hard. The hard part is that when the original item is created, it gets copied over to the amalgamated list. And since it gets copied over by System Account, I can't trigger OnItemCreate workflows on it. I do not believe that list workflows can delete items outside of their site collection, or trigger workflows outside of their site collection. If I could get either of those working, there'd be no problem. Otherwise, this would be a great way to go about it.
    – fbueckert
    Commented Jul 26, 2012 at 19:24
  • 1
    Ahh... the lovely System Account issue. Then I'd suggest following Kit's advice below. Or extend the Useful SharePoint Designer Custom Workflow Activities to prevent duplicates from ever getting created.
    – LeviS
    Commented Jul 26, 2012 at 19:39
2

I also think that using of workflow isn't the best solution to implement behavior described by you. The workflows should be used if you have some interaction with user or external service. Timer jobs works for you if you just need to perform some tasks at scheduled time. You can move all code needed to timer job and it will be run by SharePoint Timer Service automatically. Here is MSDN article where you can find some information and examples about timer jobs.

If you sill want to use workflow I think that the cause of getting null reference exception is in line:

using (SPWeb web = workflowProperties.Web)

You don't need to dispose Web property of SPWorkflowActivationProperties object. Disposing of this property is performing automatically by SharePoint itself.

3
  • That's exactly the line I had mentioned as causing the issue. This does not help me solve the underlying issue at all.
    – fbueckert
    Commented Jul 30, 2012 at 15:11
  • Anyway you need to remove this using statement. It can produce some errors potentially. It will be easy to solve your problem if you provide exception's stack trace. Commented Jul 30, 2012 at 16:54
  • Workflow is not designed to be used in this case, Timer Job is right approach to perform different actions based on timer. Commented Jul 31, 2012 at 7:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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