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:
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.
Comments
Anonymous
December 19, 2008
The LINQ-to-DASL provider of the Office Interop API Extensions provides a very limited set of mappingsAnonymous
April 08, 2009
Hi Phillip. Some times ago you send me an email explaining in details the debugger. I lost it. Now I use this post. How could I know in debug mode the variable value used in comparation? I am trying to get the Birthday.Month100 + Birthday.Day (excluding the year) to get the list of the contacts that commemorate birthday today. I am using : [OutlookItemProperty("urn:schemas:contacts:bday")] public int BDAY { get { return Item.Birthday.Month * 100 + Item.Birthday.Day ; } } and querying using: var source = new ItemsSource<MyContact>(contactFolder.Items); source.Log = new DebuggerWriter(); var allnivs = (from item in source where item.BDAY == 408 / april 8 */ select item) as IEnumerable<ContactItem>; And I get none. (yes, we have some birthdays today) any ideas? thx in advanceAnonymous
April 08, 2009
Hi Deraldo, The problem you’re having is related to the way you’ve set up the custom contact item property: [OutlookItemProperty("urn:schemas:contacts:bday")] public int BDAY { get { return Item.Birthday.Month * 100 + Item.Birthday.Day ; } } Custom item properties are intended to be thin, pass-through wrappers around the underlying Outlook item property. They exist primarily so that we have someplace to put the OutlookItemPropertyAttribute which maps the item property to its equivalent DASL property. Any logic within the property getter will not be translated into the DASL; only the logic within the actual query expression will be translated (i.e. the where clause of your query). The IContactItem.Birthday property is a standard DateTime, so we can query it as-is with LINQ to DASL. The following example shows how this can be done for both the current day (in comments) and for any arbitrary day. internal class DebuggerWriter : TextWriter { public override Encoding Encoding { get { throw new NotImplementedException(); } } public override void WriteLine(string value) { Debug.WriteLine(value); } } internal partial class MyContact : Contact { [OutlookItemProperty("urn:schemas:contacts:bday")] public DateTime Birthday { get { return Item.Birthday; } } } public partial class ThisAddIn { private void ThisAddIn_Startup(object sender, System.EventArgs e) { Outlook.Folder folder = (Outlook.Folder) Application.Session.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderContacts); var source = new ItemsSource<MyContact>(folder.Items); source.Log = new DebuggerWriter(); //DateTime date = DateTime.Today; DateTime date = new DateTime(DateTime.Today.Year, 4, 8); var results = from item in source where item.Birthday == date.ToUniversalTime() select item.Item; foreach (var result in results) { MessageBox.Show(String.Format("{0} has a birthday on {1}!", result.FullName, date)); } } private void ThisAddIn_Shutdown(object sender, System.EventArgs e) { } #region VSTO generated code /// <summary> /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// </summary> private void InternalStartup() { this.Startup += new System.EventHandler(ThisAddIn_Startup); this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown); } #endregion }