Filter Outlook Items by Date with LINQ to DASL

I received an email over the weekend asking why the following LINQ to DASL query threw an exception:

Outlook.Folder folder = (Outlook.Folder)Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);

var appointments =

from item in folder.Items.AsQueryable<Appointment>()

where item.Categories.Contains("Personal Appointments") && item.Item.Start.Date >= DateTime.Now - new TimeSpan(30, 0, 0, 0)

select item.Item;

foreach (var appointment in appointments)

{

MessageBox.Show(appointment.Start.ToString());

}

The query looks simple enough—return all personal appointments for the last 30 days—but when the foreach loop executes a MissingPropertyAttributeException is thrown stating "The property Date on class DateTime does not have an attached OutlookItemUserPropertyAttribute". The problem here is that (as the exception indicates) the Appointment.Item.Start.Date property does not have an OutlookItemProperty or OutlookItemUserProperty attached. These attributes are used by LINQ to DASL to map properties defined on .NET classes to DASL properties defined by Outlook. Why doesn't this attribute exist? Appointment.Item is of type Microsoft.Office.Interop.Outlook.AppointmentItem. This type is part of the Outlook object model and defined by the Outlook PIA. Unfortunately, since we have no control over the Office PIAs, we can't markup the types with our LINQ to DASL attributes. This means that we can't directly query properties on Outlook items. Instead, we query properties on a proxy class (i.e. Appointment) that we do have control over.

But wait a minute…the Appointment class doesn't have a Start property! Yes, unfortunately we weren't able to map every known DASL property to its Outlook item equivalent for this initial release (only so many hours in the day and all that). We did, however, provide a way for you to add such properties yourself.

internal class MyAppointment : Appointment

{

[OutlookItemProperty("urn:schemas:calendar:dtstart")]

public DateTime Start { get { return Item.Start; } }

[OutlookItemProperty("urn:schemas:calendar:dtend")]

public DateTime End { get { return Item.End; } }

}

The MyAppointment class above derives from the existing Appointment class and adds two new properties, Start and End. These properties simply defer to the inner Item's Start and End properties. Each property has an OutlookItemPropertyAttribute attached that maps the property to its corresponding DASL property, which can be found using the handy SQL tab of Outlook's custom filter dialog. Next, the query can be revised as follows:

Outlook.Folder folder = (Outlook.Folder)Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);

var appointments =

from item in folder.Items.AsQueryable<MyAppointment>()

where item.Categories.Contains("Personal Appointments") && item.Start >= DateTime.Now - new TimeSpan(30, 0, 0, 0)

select item.Item;

foreach (var appointment in appointments)

{

MessageBox.Show(appointment.Start.ToString());

}

Note that the AsQueryable<T>() extension method now uses the new MyAppointment type and that its Start property is used instead of Item.Start.Date. Run the query again and Outlook should return a collection of appointments instead of an exception (presuming you have any appointments which match the query).