How to: Use the QueryManager class to query SharePoint 2010 Enterprise Search
Posted
Sunday, August 15, 2010 9:46 PM
by
CoreyRoth
Last week, I talked about the KeywordQuery class and how to use it in SharePoint 2010. I mentioned that a new way to query also existed using the QueryManager class. This class is extremely powerful and can allow us to issue federated queries to multiple locations at one time. For example, you could make one call and get results back simultaneously from Enterprise Search, FAST, and an OpenSearch provider like Bing. You can still use the KeywordQuery class but I think the best practice is likely to be the QueryManager class because of the added flexibility. Another thing I like about the QueryManager class is that the results are returned as XML instead of a ResutlsTableCollection. Now, in reality if you are just querying one source such as Local Search Results, QueryManager might add a little overhead since it turns around and calls the KeywordQuery class, but I think the the benefits that QueryManager brings are worth it.
Before we begin, let’s take a look at our Federated Locations in the Search Service Application. In a typical out-of-the-box SharePoint 2010 install, you will have five Federated Locations. We can query any of these locations using the QueryManager class. However, we have to know the proper name to pass to the QueryManager to specify the location. We’ll talk about that here in a bit. Here is what your federated locations might look like. In my case, I have an extra federated location that I created myself that queries search on DotNetMafia.com.
For our first example, we will just issue a simple query against the Local Search Results location. Our first couple of lines are just like the ones we used with the KeywordQuery claass. We need a reference to the Search Service Application. In my case, I have named my application Search Service Application. Change the name of the string to match the name of your service application.
// get the query and settings service proxy
SearchQueryAndSiteSettingsServiceProxy settingsProxy = SPFarm.Local.ServiceProxies.GetValue<SearchQueryAndSiteSettingsServiceProxy>();
// get the search service application proxy by name
SearchServiceApplicationProxy searchProxy = settingsProxy.ApplicationProxies.GetValue<SearchServiceApplicationProxy>("Search Service Application");
We now need to create an instance of the QueryManager object as well as an object to keep track of the locations we are searching (LocationList). The QueryManager is kind of an interesting object because it really is just a LocationList with a few extra properties. There are very few settings you can specify on it other than the query. Both of these just use default constructors, we pass in the proxy once we start creating new Location objects.
QueryManager queryManager = new QueryManager();
LocationList locationList = new LocationList();
Now, we want to create an instance of a Location object which takes the internal name of the location of the federated location as well as our SearchServiceApplicationProxy. The example in the SDK had a heap of code, that loops through all of the locations on the server. I found that it was kind of overkill and can simply be replaced with a single line of code to create the Location object.
Location localSearchLocation = new Location("LocalSearchIndex", searchProxy);
As you can see here, I specified LocalSearchIndex for the name of my Location. You might be wondering where I got that value. This is the internal name of the federated location (not to be confused with the display name). You can get this value by looking at the details of the location.
Here is a table with the internal names of all of the out-of-the-box federated locations for future reference.
Display Name | Internal Name |
Internet Search Results | InternetSearchResults |
Internet Search Suggestions | InternetSearchSuggestions |
Local Search Results | LocalSearchIndex |
Local People Search Results | LocalPeopleSearchIndex |
Local FAST Search Results | FASTSearch |
As you can see there is not a lot of consistency between the internal names so I thought this table would be useful. Remember, we can query FAST using the same API as we do Enterprise Search. We just specify FASTSearch as the location. We’ll talk about that more in a future post as well. Now, we need to add the location to the LocationList.
locationList.Add(localSearchLocation);
Once we have a location set, we can actually specify our query. We’ll stick with my usual example of querying for accounting documents.
queryManager.UserQuery = "accounting";
Now we just need to add our LocationList to our QueryManager and we’re ready to query. We also have to pass it to the IsTriggered method as well. I’m not fully sure of the reason behind this, but I think if you don’t set it, the location will be added but not executed.
queryManager.Add(locationList);
queryManager.IsTriggered(locationList);
Now we execute the query using the GetResults method. It returns an XmlDocument class. I am kind of surprised this isn’t an XDocument, but I guess it’s easy enough to get from one to the other.
XmlDocument xmlDocument = queryManager.GetResults(locationList);
If you have everything configured successfully, you should get some XML with results. Let’s take a look at what one of the results looks like. I make use of the File object’s WriteAll method to quickly dump the results into an XML file so I can look at it. That was one of my first blog posts there more than five years ago. :-) This way I can open it with Visual Studio and use the Format Document (Ctrl+K, Ctrl+D) command to make it readable. Here is what one of the results looks like.
<Result>
<id>2</id>
<workid>125</workid>
<rank>76221694</rank>
<title>Accounting Procedures 2009</title>
<author_multival>Craig Johnson</author_multival>
<author_multival>Windows User</author_multival>
<author>Craig Johnson;Windows User</author>
<size>22341</size>
<url>http://sp2010/ECM/Company Documents/Accounting Procedures 2009.docx</url>
<urlEncoded>http%3A%2F%2Fsp2010%2FECM%2FCompany%20Documents%2FAccounting%20Procedures%202009%2Edocx</urlEncoded>
<description></description>
<write>7/20/2010</write>
<sitename>http://sp2010/ECM/Company Documents</sitename>
<collapsingstatus>0</collapsingstatus>
<hithighlightedsummary>
This document details all <c0>accounting</c0> policies and procedures for fiscal year 2009.
</hithighlightedsummary>
<hithighlightedproperties>
<HHTitle>
<c0>Accounting</c0> Procedures 2009
</HHTitle>
<HHUrl>
http://sp2010/ECM/Company Documents/<c0>Accounting</c0> Procedures 2009.docx
</HHUrl>
</hithighlightedproperties>
<contentclass>STS_ListItem_DocumentLibrary</contentclass>
<isdocument>True</isdocument>
<picturethumbnailurl></picturethumbnailurl>
<serverredirectedurl>http://sp2010/ECM/_layouts/WordViewer.aspx?id=/ECM/Company%20Documents/Accounting%20Procedures%202009.docx&DefaultItemOpen=1</serverredirectedurl>
</Result>
This is all of the data you get back without specifying any custom managed properties. If you are familiar with querying Enterprise Search from MOSS 2007, you will know that there is a lot more data returned. I’ll point out some of the interesting elements. When a document has multiple authors, we see them all in author element delimited by a semicolor (;). Each author is also available separately in the author_multival element. Of course, we have the usual things such as a URL, the size, modification date (write), whether it is a document or not, and it’s content class. The sitename element is particularly interesting. It actually gives us the folder that the file exists in. You don’t know how much I wanted that feature in MOSS 2007. That is why I wrote the document link handler to help get the folder name of documents. If you use Excel Services or Office Web Apps, the serverredirectedurl will provide the path to open the document using Office Web Apps.
I would be a bad blogger if I didn’t tell you how to specify your own managed properties. Unfortunately, this works exactly like it did in SharePoint 2007. If you specify one managed property, you lose all of the defaults. You’ll see what I mean here in a minute. You specify your managed properties on the Location object using the RequestedProperties StringCollection. Just set the property on the Location before adding it to the LocationList.
localSearchLocation.RequestedProperties = new System.Collections.Specialized.StringCollection() { "DocumentType" };
Since I didn’t specify any other managed properties, the only thing I get back is the Id and the DocumentType managed properties. See below.
<Result>
<id>2</id>
<documenttype>Accounting</documenttype>
</Result>
Not very useful. So instead, you have to pass the values of all of the fields you want. I have found that most of the elements you see in the first XML document, you can pass to the RequestedProperties parameter but not all of them. For example, to get the value of the URL, you actually specify the manager property path, and it gives you both url and urlEncoded. Here is what the line might look to get all of your managed properties and the new one.
localSearchLocation.RequestedProperties = new System.Collections.Specialized.StringCollection()
{ "workid", "rank", "title", "author", "size", "write", "path", "sitename", "description",
"CollapsingStatus", "HitHighlightedSummary", "HitHighlightedProperties", "ContentClass",
"IsDocument", "PictureThumbnailURL", "PopularSocialTags", "PictureWidth", "PictureHeight",
"DatePictureTaken", "ServerRedirectedURL", "DocumentType" };
How did I know all of those properties? Well the URL thing I remember from working with the KeywordQuery class in the past. The rest, I just got from the CoreResutlsWebPart. You can also look at the Local Search Results federated location.
I was planning on showing the code to do multiple locations too, but this post is already getting long, so I think I will include it in the next post. It’s cool enough to deserve its own post. :-) I will show you two other properties that are important to know about. These properties allow us to do paging. On the Location object we can set the ItemsPerPage and StartItem property to set the number of items per page and what item to start on. For example, to start on item #21 and show 20 items, we would add the following before the Location is added to the LocationList.
localSearchLocation.ItemsPerPage = 20;
localSearchLocation.StartItem = 21;
When you get results back, the XML contains the total number of results as well as the number of results returned. In my case I only had two results in my set since there were only 22 items in the entire set.
<TotalResults>22</TotalResults>
<NumberOfResults>2</NumberOfResults>
</All_Results>
I assume these number are estimates in Enterprise Search, but I could be wrong. I need to test more. If you know for sure, please leave a comment.
One more thing I will mention is that exceptions are returned by the Location object. So if you don’t get results back, check Location.Exception to see what the details are. It won’t actually throw an exception. It will just return null.
I know some people (including myself) like to see all of the code together, so here it is. This is the complete example with our DocumentType managed property included with paging.
// get the query and settings service proxy
SearchQueryAndSiteSettingsServiceProxy settingsProxy = SPFarm.Local.ServiceProxies.GetValue<SearchQueryAndSiteSettingsServiceProxy>();
// get the search service application proxy by name
SearchServiceApplicationProxy searchProxy = settingsProxy.ApplicationProxies.GetValue<SearchServiceApplicationProxy>("Search Service Application");
// create QueryManager and LocationList objects
QueryManager queryManager = new QueryManager();
LocationList locationList = new LocationList();
// add the federated location we want
Location localSearchLocation = new Location("LocalSearchIndex", searchProxy);
// set the start page and page size
localSearchLocation.ItemsPerPage = 20;
localSearchLocation.StartItem = 21;
// add our managed properties
localSearchLocation.RequestedProperties = new System.Collections.Specialized.StringCollection()
{ "workid", "rank", "title", "author", "size", "write", "path", "sitename", "description",
"CollapsingStatus", "HitHighlightedSummary", "HitHighlightedProperties", "ContentClass",
"IsDocument", "PictureThumbnailURL", "PopularSocialTags", "PictureWidth", "PictureHeight",
"DatePictureTaken", "ServerRedirectedURL", "DocumentType" };
// add the Location to the LocationList
locationList.Add(localSearchLocation);
// set the query
queryManager.UserQuery = "accounting";
queryManager.Add(locationList);
queryManager.IsTriggered(locationList);
// make the call to search
XmlDocument xmlDocument = queryManager.GetResults(locationList);
// write out the results
System.IO.File.WriteAllText("results.xml", xmlDocument.InnerXml);
Console.WriteLine(xmlDocument.InnerXml);
Console.ReadLine();
The QueryManager class is very powerful for interacting with Enterprise Search. I hope you found this post useful. In my next post, we’ll query multiple locations at a time.