In the preceding few examples, we used LINQ
to Objects to illustrate the query syntax of LINQ—in fact, the syntax
is pretty similar regardless of which provider you’re using. For
example, to retrieve a list of records from a table using LINQ to SQL,
you’d write something like this:
NorthwindDataContext context = new NorthwindDataContext());
var customers = from c in context.Customers
where c.Name.Contains("n")
select c;
Or to get a list of nodes from an XML document using LINQ to XML, you’d use something like this:
XDocument loaded = XDocument.Load(@"C:\users.xml");
var q = from u in loaded.Descendants("user")
where u.Name.Contains("n")
select u;
As
you’ll see, the only major difference is in the way that the “table” is
referenced. When we used LINQ to Objects, the “table” was simply the
collection variable, whereas when using LINQ to SQL the “table” is
actually a database table. To make this less confusing, this “table”
object is commonly referred to as a gateway object,
since technically it doesn’t necessarily represent a table—the way it’s
used in the SQL-like syntax makes it seem like a table.
Microsoft.SharePoint.Linq.DataContext
The gateway object for LINQ to SharePoint is Microsoft.SharePoint.Linq.DataContext. The DataContext
object exposes a number of methods and properties, of which the
following are most significant for the purposes of this discussion:
GetList(T) This method returns a generic EntityList object that represents that specified list. Here’s an example:
EntityList<Customer> customers=dataContext.GetList<Customer>("Customers");
Refresh This method can be used to refresh one or more entities with the latest data from the content database. It has three overloads:
Refresh(RefreshMode, IEnumerable) Refreshes the collection of entities that are defined by the IEnumerable parameter
Refresh (RefreshMode, Object) Refreshes a single entity as defined by the object parameter
Refresh(RefreshMode, Object[]) Refreshes an array of entities as defined by the object array parameter
Each of the overloads for the Refresh method accepts a RefreshMode parameter. This enumeration is used to specify how concurrency conflicts should be handled. Here are the possible values:
KeepChanges Accept every user’s changes with prejudice for the current user
KeepCurrentValue Rolls back all other users’ changes
OverwriteCurrentValues Gives absolute prejudice to the database version
These descriptions are pretty vague, and I’ll cover
how LINQ to SharePoint handles concurrency conflicts in more detail a
bit later. For now, it’s enough to know that the Refresh methods of the DataContext object require one of these three values.
RegisterList This method enables continued reading and writing to an EntityList object after it’s been renamed or moved. Since the EntityList object is effectively a snapshot of a list at time of creation, if the list is subsequently moved or renamed, the EntityList object will become invalid. The RegisterListEntityList object to the new destination rather than having you dispose and re-create the EntityList object. This method has two overloads: method allows you to re-point the
RegisterList<T>(String, String) Used if the list is renamed within the same site
RegisterList<T>(string newListName, string newWebUrl, string oldListName) Used if the list is moved to another web site
SubmitChanges() As with the Refresh method, this method has three overloads that are used to specify how concurrency conflicts are handled:
SubmitChanges() Persists changes to the content database. If a concurrency conflict occurs, a ChangeConflictException will be thrown and the ChangeConflicts property will be populated with one or more ChangeConflict objects. This method assumes a failure mode of FailOnFirstConflict. When items are updated using this method, the version number is automatically incremented if versioning is enabled.
SubmitChanges(ConflictMode) Used when you don’t want to use the default conflict handling behavior. Two possible values can be used for ConflictMode: ContinueOnConflict,
which attempts all changes, throws an exception if any concurrency
errors occurred, and then rolls back all changes; or the default of FailOnFirstConflict,
which throws an error when the first concurrency error occurs, and then
rolls back all changes made up to that point. When items are updated
using this method, the version number is automatically incremented if
versioning is enabled.
SubmitChanges(ConflictMode, bool systemUpdate) Used for the same reason as the preceding overload, with one significant difference: if the systemUpdate flag is set to true, updates are performed using the SystemUpdate() method rather than the Update() method. This is significant because updating an item using SystemUpdate does not increment the version number. Setting the systemUpdate flag to false would have the same effect as using the preceding overload with the same ConflictMode.
In addition to these methods are a number of properties that are worth covering briefly:
ObjectTrackingEnabled This Boolean property is used to determine whether the DataContext object should track changes for each entity that’s associated with it. If this value is set to false, calling SubmitChanges will have no effect. This property exists for performance reasons; by setting the value to true, the DataContext
object has a lot of extra work to do to track any changes to its
associated objects. As a rule of thumb, you should set this value to false unless you specifically need to add, edit, update, or delete data. By default, object tracking is enabled.
ChangeConflicts This ChangeConflictCollection property, as its type suggests, is a read-only collection of ObjectChangeConflict objects. Each ObjectChangeConflict
object represents a set of one or more discrepancies between the
in-memory version of a list item and the current persisted version. The
ObjectChangeConflict object has a MemberConflicts collection that contains a collection of MemberChangeConflict objects, with each object representing the discrepancy in a single field. It’s possible to have an ObjectChangeConflict object without any associated MemberChangeConflict objects. In this case, the ObjectChangeConflict object exists because the original item has been deleted. This condition can be verified by checking the IsDeleted property of the ObjectChangeConflict object.
Tip
Depending
on the profile of your application, you may find that you use LINQ most
often for querying data as opposed to making changes. Furthermore,
you’ll generally be making changes to a very small dataset, whereas you
may be querying much larger datasets. In this situation, maintaining a
separate DataContext object for updates is usually the most efficient approach. In effect, you’ll have one DataContext
with object-tracking enabled that you can use for updates, and another
with object-tracking disabled that you can use for queries.
Figure 1
shows the relationship between the conflict resolution objects.
I’ve covered the main functional elements
of the gateway class for LINQ to SharePoint, and a few other objects
are significant as well, such as EntityList, EntityRef, and EntitySet.
I won’t cover these in detail since their operation is practically
identical to similar objects used on other variants of LINQ; their
usage will become apparent in the code samples that follow. The DataContext
object has special significance since it provides the gateway to using
standard LINQ syntax, although, as you’ll see when we look at SPMetal,
generally the DataContext object
isn’t used directly; instead, common practice is to create a number of
derived classes that represent each list in the data structure.