Although the join syntax in LINQ to
SharePoint is very powerful, in some situations you won’t be able to
retrieve the data that you need using this syntax. Some operations that
you require are not permitted on the LINQ to SharePoint provider,
because they are considered inefficient.
Performing an In-Memory Subquery
Here’s an example of an in-memory subquery: Suppose
that our sample application requires a list of asset tags and locations
codes for a particular contract, but only where the locations are also
being used to store assets that are subject to another contract. If you
were writing this query using SQL, it would be relatively
straightforward—something along the lines of this:
SELECT AssetTag, LocationCode
FROM AssetNotes as n
INNER JOIN OnHireAssets as a
ON A.AssetId=n.AssetId
INNER JOIN HireContracts as c
ON c.ContractId=a.ContractId
WHERE n.LocationCode in (
SELECT LocationCode
FROM AssetNotes as n1
INNER JOIN OnHireAssets as a1
ON a1.AssetId=n1.AssetId
INNER JOIN HireContracts as c1
On c1.ContractId=a1.ContractId
Where c1.ContractId=CONT-002'
)
AND c.ContractId='CONT-001'
OK—maybe this is not that straightforward with all
the joins, but you get the picture. You could use a subquery to filter
the results to suit your requirements.
Unsurprisingly, given its similarity to SQL, LINQ
syntax also supports a similar operation. Let’s use our sample
application to try it out. As usual, add a new button, label it Sub Query and add the following code:
private void button10_Click(object sender, EventArgs e)
{
using(HireSampleDataContext dxRead = new HireSampleDataContext(SiteUrl.Text))
{
StringBuilder sb = new StringBuilder();
//since we-re using subqueries, more than one CAML query
//will be generated.
//add a root element to ensure that the logged output is valid XML
sb.Append("<Queries>");
using (StringWriter logWriter = new StringWriter(sb))
{
//log the generated CAML query to a StringWriter
dxRead.Log = logWriter;
dxRead.ObjectTrackingEnabled = false;
var subquery=from note2 in dxRead.AssetNotes
where note2.AssetReference.ContractReference.ContractId
== "CONT-002"
select note2.LocationCode;
var results = from note in dxRead.AssetNotes
where note.AssetReference.ContractReference.ContractId
== "CONT-001"
&& subquery.Contains(note.LocationCode)
select new
{
note.AssetReference.AssetTag,
note.LocationCode
};
dataGridView1.DataSource = results.ToList();
}
sb.Append("</Queries>");
//create a temporary file for the generated CAML
string fileName = Path.Combine(Path.GetTempPath(), "tmpCaml.xml");
XmlDocument doc = new XmlDocument();
doc.LoadXml(sb.ToString());
doc.Save(fileName);
//point the browser control to the temporary generated CAML file
webBrowser1.Navigate(fileName);
}
}
In this code sample, we’ve defined the subquery, and
then used it within the main query. I’ve split up the queries for the
sake of clarity; LINQ syntax allows you to combine them within a single
query if required.
When
you run this query using the sample application, an exception will be
thrown, because the LINQ to SharePoint parser can’t convert the
statement into CAML, because the CAML syntax doesn’t support
subqueries. However, it is still possible to execute this query by
making a small modification. Modify the subquery declaration to render
the results to a List, as follows:
var subquery=(from note2 in dxRead.AssetNotes
where note2.AssetReference.ContractReference.ContractId
== "CONT-002"
select note2.LocationCode).ToList();
This time, clicking the Sub Query button will return
the expected result set, and an examination of the generated CAML
queries will reveal that two queries were generated. The first query
corresponds to the subquery and the second corresponds to the main
query without the inclusion of the subquery.
So how does this work? Using ToList
in the definition of the subquery forces the query to be executed
immediately, returning the results as a generic list. The generic List
object implements IEnumerable<T>
and can therefore be used within a LINQ expression. The main LINQ query
then performs the subquery using LINQ to Objects as opposed to LINQ to
SharePoint, yielding the expected results. In effect, adding ToList
to a query allows you to process the results using the full power of
LINQ to Objects. However, as discussed earlier, this approach has
drawbacks, and efficiency must be given serious thought before you
adopt this technique.