LINQ provides an efficient, transactional way to
update data. Either all modifications made to a given data context are
applied or all are rolled back. The update process also supports
checking for concurrent updates, allowing for long-running processes to
create and hold references to disconnected entity objects without the
overhead of maintaining references to heavyweight objects such as SPWeb and SPSite.
1. Disconnecting Entities
To see disconnected entities in action, let’s add another button to our sample application. Label the button Update and Disconnect, and add the following code:
//Define a member variable to store the query
IQueryable<AssetNote> _basicQuery;
private void button11_Click(object sender, EventArgs e)
{
using(HireSampleDataContext dxWrite = new HireSampleDataContext(SiteUrl.Text))
{
StringBuilder sb = new StringBuilder();
sb.Append("<Queries>");
using (StringWriter logWriter = new StringWriter(sb))
{
dxWrite.Log = logWriter;
//Since we're updating data, object tracking must be enabled
dxWrite.ObjectTrackingEnabled = true;
_basicQuery = from n in dxWrite.AssetNotes
select n;
foreach (var n in _basicQuery)
{
//Edit each location code
n.LocationCode += "-Edit";
}
//disconnect from the datacontext
//remove the logger to prevent problems when the reference
//is missing on reconnect
dxWrite.Log = null;
dxWrite.Dispose();
dataGridView1.DataSource = _basicQuery.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);
}
}
Notice
a few important things in this code. First, a member variable is
required to store the updated entities between function calls. Second,
it’s important that you disconnect the DataContext object properly. If a logger is attached to the DataContext, the logger must be detached before disposing of the DataContext; otherwise, an error will occur when the entities attempt to reconnect, since the DataContext entity has an internal reference to the logger. This issue may be resolved in the final version of the code.
2. Reconnecting Entities
Now that we have created some code to update items
and disconnect the updated entities, we need some code to reconnect the
entities and save the changes. Before we do that, we need to consider
error handling for our sample application.
Since we have the facility to create disconnected
updates, it’s possible that another user will change an item that we
have disconnected before our update has been applied. Indeed, even
if our update was not performed in a disconnected manner and was made
in near real-time, the web-based nature of the SharePoint platform
means that it is still likely that data will have changed before the
update completes.
LINQ to SharePoint provides functionality to deal with this type of error via the ChangeConflicts property of the DataContextChangeConflicts property in action in our sample application, we need to make a few changes to the user interface: object. So that we can see the
In the form designer, un-dock the DataGridView from the SplitContainer and add a second SplitContainer in the right pane.
Set the Orientation of this new SplitContainer to Horizontal.
In the top pane of the new SplitContainer, place the original DataGridView control, and again set the Dock property to Fill.
Drag a new, second DataGridView onto the bottom pane of the new SplitContainer.
Again, set the Dock property of this new DataGridView to Fill.
Once you’ve made these changes, add a new button labeled Reconnect and Save. Your updated form should look like this:
Double-click the Reconnect and Save button and in the code file add the following:
private void button12_Click(object sender, EventArgs e)
{
using(HireSampleDataContext dxWrite = new HireSampleDataContext(SiteUrl.Text))
{
StringBuilder sb = new StringBuilder();
sb.Append("<Queries>");
using (StringWriter logWriter = new StringWriter(sb))
{
dxWrite.Log = logWriter;
//Since we're updating data, object tracking must be enabled
dxWrite.ObjectTrackingEnabled = true;
foreach (var n in _basicQuery)
{
dxWrite.AssetNotes.Attach(n);
}
//always define a catch for ChangeConflictException
try
{
dxWrite.SubmitChanges();
}
catch (ChangeConflictException ex)
{
//bind any conflicts to a data grid so that we can see them
dataGridView2.DataSource = dxWrite.ChangeConflicts.ToList();
}
dataGridView1.DataSource = _basicQuery.ToList();
}
sb.Append("</Queries>");
string fileName = Path.Combine(Path.GetTempPath(), "tmpCaml.xml");
XmlDocument doc = new XmlDocument();
doc.LoadXml(sb.ToString());
doc.Save(fileName);
webBrowser1.Navigate(fileName);
}
}
Notice the try/catch block around the SubmitChanges statement. You should always define a handler for the ChangeConflictException
since, by its very nature, the exception can occur any time an item is
updated. In this sample code, the exception is handled by displaying
the resulting conflicts in our user interface.
We can use the two new buttons that we added to
simulate a disconnected update. First, click the Update and Disconnect
button to make updates to a set of records. Using the SharePoint user
interface, verify that the updates have not yet been applied to the
list. Then go back to the sample application and click Reconnect and
Save. This time, checking the SharePoint user interface will confirm
that the updates have been applied as expected.