2. Querying Using LINQ
The CAML join syntax is not the easiest to write or
read. Let me complicate the picture a little bit further. What if you
want it to not just query data but also to query the data and update
the queried objects? While updating the queried objects, you want to
take care of things such as concurrency. Not only that, most CAML
queries are embedded in your code as strings. What if you want it to do
compile time checking; in other words, strongly type code to represent
your SharePoint site collection? All these things are not possible to
do with pure CAML. But with SharePoint 2010, you have the ability of
using LINQ to SharePoint, to address all these problems. Thus,
SharePoint supports yet another way of interacting with its data, and
that is LINQ to SharePoint.
For those of you who have worked with LINQ to SQL,
LINQ to SharePoint is very similar. Just like LINQ to SQL, LINQ to
SharePoint ships with a command-line utility called SPMetal that allows
you to create a data context to interact with your SharePoint site. You
can create such a data context on your sample data site using this
command:
"%14%\bin\spmetal" /web:http://sp2010/sampledata /code:sampledata.cs
Note that 14 is an environment variable I have
created that points me to the SharePoint root.
Executing the preceding command will create the data
context in a file called "sampledata.cs". Go ahead and create a
SharePoint console application and add this newly created file, and
thus add your data context into your SharePoint console application.
Also go ahead and add a reference to Microsoft.SharePoint.LINQ.dll. You
can find Microsoft.SharePoint.LINQ.dll in the %14%\ISAPI folder.
With the data context ready, I'm going to show you
three examples of using LINQ with SharePoint. I will demonstrate
querying using a simple LINQ query, changing data using LINQ, and
performing joins using LINQ.
First, let's see the example of a simple LINQ query. You can see the code for a simple LINQ query in Listing 4.
Example 4. A Simple LINQ Query
private static void SimpleLINQQuery() { Trace("Simple LINQ Query", true); using (SampledataDataContext context = new SampledataDataContext(siteUrl)) { var artistsMichael = from artist in context.Artists where artist.Title.Contains("Michael") select artist;
foreach (var artist in artistsMichael) { Trace(artist.Title); } } }
|
As you can see from the listing, I create an
instance of my SampleDataContext. I always do so in a using block
because it'll automatically dispose the data context for me. With the
data context ready, I can then execute a LINQ query as shown here:
var artistsMichael = from artist in context.Artists
where artist.Title.Contains("Michael")
select artist;
Executing the LINQ query then allows me to work with my queried objects in a very easy-to-read fashion, as shown here:
foreach (var artist in artistsMichael)
{
Trace(artist.Title);
}
Note that the trace methods are simply helper
methods I have written in the associated code. This code is much easier
to read and deal with than the CAML code. It is strongly typed, thus my
errors are caught during compile time and not at runtime, and I can
code more effectively using intellisense. The biggest disadvantage of
LINQ, however, is that after your data context has been generated,
changing the schema of the target site can potentially invalidate this
generated data context.
Later on, I will show the best of both worlds, in
which you have the resiliency of CAML -based code and the ease of
writing of LINQ-based code.
Now let's say you have this data queried, how would
you update the changes to this data back into SharePoint? When it comes
to updating data, LINQ to SharePoint and LINQ to SQL, both work in a
similar fashion.
The data context for both LINQ to SharePoint and LINQ to SQL
builds upon that concept. There is, however, a method on the data
context called RefreshData, which allows you to explicitly specify that
you wish to overwrite you're in-memory objects with the latest
representations from the original data source.
Excluding the case of calling RefreshData, usually all disconnected update models would appear to work as follows:
Disconnect from the underlying data source
Submit the changes back to the data source.
Things get a little tricky at number 4 because while
you were executing steps 2 and 3, someone else could have changed the
data that you had queried. Thus in step 4 you need to perform
concurrency checks before your data was written into the server.
Similar to LINQ to SQL, step 4 is encapsulated in the SubmitChanges
method of the DataContext. Also, there are various options you can
specify to SubmitChanges to control the concurrency behavior. You can
also use System.Transactions to control the transactional behavior on
such updates.
But this is where LINQ to SharePoint and LINQ to SQL
slightly bifurcate. Specifically, there are two things you need to be
careful of when you call SubmitChanges in LINQ to SharePoint.
Try not to treat SharePoint like a
high-volume transactional database. As mentioned previously, the
SharePoint content database in SharePoint 2010 has undergone
significant improvements from SharePoint 2007. This means that the
database is less prone to issues such as locks and transaction
isolation levels being elevated. However, it is probably still more
prone to such issues than a highly optimized and customized SQL
database.
Do not use
transactions that span more than one content database.
System.Transactions will work as long as you stick with a single
content database because to prevent serializable isolation levels in
transactions, SharePoint stores all its internal connection strings
with "Enlist=False". This means databases are explicitly precluded from
enlisting in distributed transactions a rather good idea. But it
prevents cross-content database transactions from succeeding.
Now updating data using LINQ to SharePoint is
extremely simple. You simply change the queried objects and then call
the SubmitChanges method. THATS IT! No really, it is that simple. Thus
in Listing 4, if you add the following two lines, you would change the data in the SharePoint list:
artist.Title = "Some new title";
dataContext.SubmitChanges() ;
Adding new items is slightly more involved but not difficult, either. You can see an example of adding a new artist in Listing 5.
Example 5. Adding a New Artist Using LINQ to SharePoint
private static void ChangeDataUsingLINQ() { Trace("Update Data Using LINQ", true); using (SampledataDataContext context = new SampledataDataContext(siteUrl)) { ArtistsItem duranduranArtist = new ArtistsItem() { Country="USA", Title="Duran Duran" }; context.Artists.InsertOnSubmit(duranduranArtist); context.SubmitChanges(); Trace("Item Added"); } }
|
As you can see from Listing 5,
I create a new instance of the ArtistsItem. I then use the
InsertOnSubmit method to mark the newly created artists item to be
persisted in the next SubmitChanges call. This causes my newly created
artist to get added into the SharePoint list. Go ahead and execute the
code shown in Listing 5 and verify that Duran Duran is actually added to the list.
Next, let's look at an example of performing joins
using the LINQ to SharePoint. This is where LINQ to SharePoint truly
shines! Go ahead and author a method as shown in Listing 6.
Example 6. Performing Joins Using LINQ to SharePoint
private static void JoinUsingLINQ() { Trace("LINQ Query with a Join", true); using (SampledataDataContext context = new SampledataDataContext(siteUrl)) { var songs = from song in context.Songs where song.Artist.Title.Contains("Michael") select song;
// context.Log = Console.Out;
foreach (var song in songs) { Trace(song.Title); } } }
|
As you can see, performing a join using LINQ to
SharePoint is a matter of simply dereferencing the appropriate object
in the generated object model, as I am doing with song.Artist.Title.
Because I was using look up columns, SPMetal.exe had enough information
to create the object model to reflect the structure of my site,
including all relationships defined by lookup columns. Executing the
code shown in Listing 7 will show you an output as shown in Figure 5.
As you can see, all songs that contain the word "Michael" in the artist name match in this query.
Earlier I mentioned that writing CAML queries by
hand can be a little cumbersome. That problem is solved using LINQ to
SharePoint. But LINQ to SharePoint introduces its own additional
challenge of restricting the schema changes of the site after the
DataContext has been generated. Is there a best of both worlds? Yes!
From Listing 7, uncomment the following line:
// context.Log = Console.Out;
Execute the code one more time. You should see an output shown in Figure 6.
The LINQ query that LINQ to SharePoint was executing
for you has been written out for you on the console. This query after
cleanup is shown in Listing 8.
Example 8. Automatically Generated CAML Query by the Data Context
<View> <Query> <Where> <And> <BeginsWith> <FieldRef Name="ContentTypeId" /> <Value Type="ContentTypeId">0x0100</Value> </BeginsWith>
<Contains> <FieldRef Name="ArtistTitle" /> <Value Type="Lookup">Michael</Value> </Contains> </And> </Where> </Query> <ViewFields> <FieldRef Name="Artist" LookupId="TRUE" /> <FieldRef Name="ID" /> <FieldRef Name="owshiddenversion" /> <FieldRef Name="FileDirRef" /> <FieldRef Name="Title"/> </ViewFields> <ProjectedFields> <Field Name="ArtistTitle" Type="Lookup" List="Artist" ShowField="Title" /> </ProjectedFields> <Joins> <Join Type="LEFT" ListAlias="Artist"> <!—List Name: Artists—> <Eq> <FieldRef Name="Artist" RefType="ID" /> <FieldRef List="Artist" Name="ID" /> </Eq> </Join> </Joins> <RowLimit Paged="TRUE">2147483647</RowLimit> </View>
|
You're executing nothing but just a simple CAML
query with joins. You could take the same query and get the same
results if you wished. But then, of course, you will be restricted to
doing updating using the regular SharePoint object model.
So you have an option here: you can either
lean toward developer productivity and restrict schema changes, or you
can have a very flexible system with resilient code, but have the
developer work a bit harder, while borrowing such queries generated
LINQ. As an architect, it is good to have choices!