InfiniTec - Henning Krauses Blog

Don't adjust your mind - it's reality that is malfunctioning

FindItems Helper method for the Exchange Managed API

I’ve seen quite a few samples for using the FindItems method of the EWS Managed API, either on StackOverflow or in the Exchange development forum. One of the most common error I see is that people disable paging by specifying creating an ItemView like this:

var view = new ItemView(int.MaxValue);

Don’t do that! A query using this ItemView will put a severe burden on the Exchange server if the folder being queried contains many items. The pageSize parameter is there for a reason.

Instead of querying all items at once, one should do a proper paging: Query, say, 512 items and process them. Then, query the next 512 items. Thanks to iterators in C#, the process of retrieving the items can be completely separated away from the processing.

If you are not familiar with C# iterators, have a look at this article. In essence, the yield operator allows for deferred execution, something you might heard from with regards to LINQ.

Here is a helper method which uses this technique to iterate over the contents of an Exchange folder, requesting 512 items per batch and returning them to the caller. When used properly, this method uses very little resources on the Exchange server as well as on the client. Additionally, it allows for querying the body of an item. In this case, instead of executing a FindItems request, it executes a FindItems request and a LoadPropertiesForItems request per batch.

Due to the nature of the method, you should not put the items returned from the methods in a List, as this would consume a lot of memory on the client. Instead process the items one by one.

public static class ExchangeServiceExtension
{
    public static IEnumerable<Item> FindItems(this ExchangeService service, WellKnownFolderName folderName, PropertySet propertySet, SearchFilter searchFilter, ItemTraversal traversal = ItemTraversal.Shallow, params KeyValuePair<PropertyDefinition, SortDirection>[] orderBy)
    {
        return FindItems(service, new FolderId(folderName), propertySet, searchFilter, traversal, orderBy);
    }

    public static IEnumerable<Item> FindItems(this ExchangeService service, FolderId folderId, PropertySet propertySet, SearchFilter searchFilter = null, ItemTraversal traversal = ItemTraversal.Shallow, params KeyValuePair<PropertyDefinition, SortDirection>[] orderBy)
    {
        if (service == null) throw new ArgumentNullException("service");
        if (folderId == null) throw new ArgumentNullException("folderId");
        if (propertySet == null) throw new ArgumentNullException("propertySet");

        PropertySet propertySetToQuery;

        // If the body or unique body is requested, the FindItems method cannot be used to query the 
        // propertyset. Instead a GetItem operation is required. The propertyset IdOnly is used in this case
        // for the FindItems operation.
        if (propertySet.Contains(ItemSchema.Body) || propertySet.Contains(ItemSchema.UniqueBody))
        {
            propertySetToQuery = PropertySet.IdOnly;
        }
        else
        {
            propertySetToQuery = propertySet;
        }

        var itemView = new ItemView(512) { PropertySet = propertySetToQuery, Traversal = traversal };
        
        if (orderBy != null)
        {
            foreach (var order in orderBy)
            {
                itemView.OrderBy.Add(order.Key, order.Value);
            }
        }

        bool hasMoreItems;
        do
        {
            var result = service.FindItems(folderId, searchFilter, itemView);
            if (propertySetToQuery == PropertySet.IdOnly)
            {
                // Load the properties, including the body using a GetItem request.
                service.LoadPropertiesForItems(result, propertySet);
            }
            foreach (var item in result)
            {
                yield return item;
            }
            hasMoreItems = result.MoreAvailable;
            itemView.Offset += 512;
        } while (hasMoreItems);
    }
}

As you can see, this method uses several .NET 4.0 features: Optional parameters and extension methods. So, if you are still using .NET 2.0, you’ll need to modify the code a little bit.

To use these methods, insert this code into your solution. In the class where you want to use the methods, add a using to the namespace containing the ExchangeServiceExtension. Both methods will then appear in the code completion window on instances of the ExchangeService class:

CodeCompletion

In the following example, the method is used to iterate over all items in the Inbox folder and printing the items to the console:

var items = service.FindItems(WellKnownFolderName.Inbox, new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject));

foreach (var item in items)
{
    Console.Out.WriteLine("Subject = {0}", item.Subject);
}

Since the last parameters are optional, they can be omitted. This example adds a sort clause to the call:

var items = service.FindItems(WellKnownFolderName.Inbox, new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject), 
    orderBy: new KeyValuePair<PropertyDefinition, SortDirection>(ItemSchema.Subject, SortDirection.Ascending));

Of course, a SearchFilter and an ItemTraversal can also be specified:

var items = service.FindItems(WellKnownFolderName.Inbox, new PropertySet(BasePropertySet.IdOnly, ItemSchema.Subject), 
    new SearchFilter.ContainsSubstring(ItemSchema.Subject, "test"), ItemTraversal.Associated, 
    new KeyValuePair<PropertyDefinition, SortDirection>(ItemSchema.Subject, SortDirection.Ascending));

Posted by Henning Krause on Thursday, September 8, 2011 11:15 PM, last modified on Thursday, September 8, 2011 11:15 PM
Permalink | Post RSSRSS comment feed

 +Pingbacks and trackbacks (1)