Querying in TFS is easy. You run a query specifying a filter; you walk through returned items performing certain actions on each returned item. Now after a few runs you start asking yourself: why is it so slow? Here are some non-obvious implementation details that should answer this question.
A work item returned by a query only has fields specified in the SELECT clause. Accessing a field not from that list results in a roundtrip to the server to obtain that field for this work item (and for 49 other items from that query; we batch reading operations). If your query asks for System.Id, but also obtain Title, Assigned To and other fields, the object model makes a lot of unnecessary database calls behind the scene.
Accessing collection of links, file attachments, or historical revisions opens the work item. Opening obtains all work item's data in a single roundtrip to the server. It loads ALL fields in ALL revisions; file attachments and links. At the same time accessing fields like Rev, RelatedLinkCount, HyperlinkCount, AttachedFileCount, and ExternalLinkCount does not require opening the item, so it is better to use these fields to check item's number of revisions or file attachments.
Calling WorkItemStore.GetWorkItem returns you an opened work item with all data. If your program queries work items to access their historical revisions, file attachments, or links, it is better to create a query returning only System.Id field, and call WorkItemStore.GetWorkItem for each returned work item.
Here's the summary: