Debugging LINQ-to-DASL Queries

When your LINQ-to-DASL queries do not return the results you expect, how do you determine where the problem is?  The issue could be that the query simply doesn't do what you expect.  For example, you could be querying the wrong DASL properties and therefore Outlook returns no (or unexpected) items.  (This is easy to do, as DASL property names can be difficult to identify and map to the equivalent Outlook object model property.)  It could also be that the DASL query syntax itself is incorrect or the LINQ-to-DASL provider did not manage the query results property.

Fortunately, the LINQ-to-DASL provider offers a way to gain a little insight into what it is actually doing.  You can attach a logger to the provider that will be passed the exact DASL string passed to Outlook when the query is executed.

Let's say we want to find all appointments that have been modified in the last week.  That is, where the LastModifiedTime is within the last seven days.  First, we'll define a custom class on which to perform the query.  (We do this because the built-in Appointment query class does not have LastModifiedTime.)

 public class MyAppointment : Appointment
{
    [OutlookItemProperty("DAV:getlastmodified")]
    public DateTime LastModifiedTime { get { return Item.LastModificationTime; } }
}

Next, we'll create a method that generates the query.

 private IEnumerable<Outlook.AppointmentItem> CreateQueryWithoutDebuggingInformation(Outlook.Items items, DateTime oldestDate)
{
    var query = from item in items.AsQueryable<MyAppointment>()
                where item.LastModifiedTime > oldestDate.ToUniversalTime()
                select item.Item;

    return query;
}

The method takes the items collection on which the query is to be performed and the date we wish to filter by.  Note that there is nothing special about this LINQ-to-DASL query at this point.

Finally, we'll write the method to execute the query and show the results.

 private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    Outlook.Folder folder = (Outlook.Folder)Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);
    DateTime oldestDate = DateTime.Now.AddDays(-7);

    var query = CreateQueryWithoutDebuggingInformation(folder.Items, oldestDate);

    foreach (var appt in query)
    {
        if (MessageBox.Show(appt.Subject, "Appointment Found!", MessageBoxButtons.OKCancel) == DialogResult.Cancel)
        {
            break;
        }
    }
}

The method simply retrieves the Calendar folder, calls the query creation method, and then iterates over the results.  If the query doesn't return any items, or returns items that you do not expect, what do we do next?  The answer is to modify our query by attaching a logger so that we can see the generated DASL.

First, we'll create a simple logger that writes to the debugger output window.

 internal class DebuggerWriter : TextWriter
{
    public override Encoding Encoding
    {
        get { throw new NotImplementedException(); }
    }

    public override void WriteLine(string value)
    {
        Debug.WriteLine(value);
    }
}

The logger can be any TextWriter-derived class.  Note that the Encoding property must be overridden, but need not actually be implemented.

Next, we'll create a new query generation method.

 private IEnumerable<Outlook.AppointmentItem> CreateQueryWithDebuggingInformation(Outlook.Items items, DateTime oldestDate)
{
    var source = new ItemsSource<MyAppointment>(items)
    {
        Log = new DebuggerWriter()
    };

    var query = from item in source
                where item.LastModifiedTime > oldestDate.ToUniversalTime()
                select item.Item;

    return query;
}

Notice that the query itself (the from, where, and select) is virtually identical to the previous query.  The only difference is that we explicitly create a ItemsSource.  (This is what AsQueryable() did implicitly in the previous query.)  We then attach an instance of our logger.

Finally, we update our query execution method.

 private void ThisAddIn_Startup(object sender, System.EventArgs e)
{
    Outlook.Folder folder = (Outlook.Folder)Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderCalendar);
    DateTime oldestDate = DateTime.Now.AddDays(-7);

    //var query = CreateQueryWithoutDebuggingInformation(folder.Items, oldestDate);
    var query = CreateQueryWithDebuggingInformation(folder.Items, oldestDate);

    foreach (var appt in query)
    {
        if (MessageBox.Show(appt.Subject, "Appointment Found!", MessageBoxButtons.OKCancel) == DialogResult.Cancel)
        {
            break;
        }
    }
}

The only change here was to call the new query generation method.  Now when we run the code in the debugger, we'll see the following in our output window:

DebugOutput

You can see the exact DASL filter string sent to Outlook when the query was executed.  You can use this to verify that the DASL property names, syntax, and values all look correct.  You can also compare this filter string to the string built using the advanced filtration dialog in Outlook.