Introduction
LINQ is a .NET-based framework for executing queries on a variety of data types. All that such queryable data types have in common is an interface—be it IEnumerable or the newest IQueryable, with their generic variations IEnumerable<T> and IQueryable<T>. If you dig a little bit deeper, you’ll find out that LINQ only demands the availability of an enumerator object on the collection object being queried. Not all potentially queryable collections, though, are enumerable, at least according to the contract of the IEnumerable interface. This is where IQueryable fits in. In brief, IQueryable represents an alternative query interface for those objects that do not natively implement an enumerator. In this way, all queryable data types are normalized and can be queried using a common language.
As a result, each flavor of LINQ (including LINQ-to-XML and LINQ-to-SQL) is essentially an object model that implements and exposes the IQueryable interface thus mapping its underlying data to the expected programming interface of LINQ. So for LINQ-to-XML you also have a bunch of additional classes such as XDocument. For LINQ-to-DataSets you have a bunch of extension methods onto the DataTable class. Goal of these extensions is exposing a LINQ-queryable interface. For LINQ-to-SQL, the companion object model is centered around the DataContext class.
The DataContext class supports a fair number of features including object caching, object identity and, main topic of this article, object tracking.
Object Tracking in Brief
In LINQ-to-SQL, you work against a sort of domain model. To the application’s eyes, this object model represents the entire database. Note that, in this context, the word “database” generically refers to an “archive”, rather than to an instance of a commercial DBMS. The entry point in this domain model is the DataContext class. Any query or update operations you need to accomplish on the data must pass through the DataContext class—or, more precisely, through the domain specific context class that Visual Studio 2008 generates for you.
Note:
Creating the scaffolding for a domain data context is a task that you typically accomplish through the O/RM designer tool in Visual Studio 2008. However, really nothing would prevent you from manually writing all required class. All that matters, in the end, is that you have available in your project a bunch of classes with certain characteristics.
The entry point in the LINQ-to-SQL’s domain model is a class that inherits from DataContext. Obviously, this class inherits some behavior and properties from the parent class. One of the most interesting and powerful properties is ObjectTrackingEnabled. Here’s how the property is defined in the DataContext class:
The property instructs the framework to track the original value and object identity for the data context. The property’s default value is true meaning that tracking is on unless you explicitly disable it. You can no longer change the value of ObjectTrackingEnabled once some data has been retrieved and cached internally. You’ll get an invalid operation exception if you do so. In addition, if you disable tracking the set modifier of the property also sets another related property—DeferredLoadingEnabled—to false.
Property DeferredLoadingEnabled controls whether loading of child record sets happens on demand. For performance reasons, LINQ-to-SQL doesn’t physically load orders from SQL Server for a given customer until your code hits a line where orders are required. Disabling lazy loading means that no child relationships on the object will be honored implicitly by the framework.
Tracking and Deferred Loaders
When tracking is enabled, any objects stored in a data context are bound to the context for eternity. Note that this form of implicit binding between objects and their original context lasts even when the context is disposed of. In fact, both the get accessor and set modifier of the ObjectTrackingEnabled property check the disposed state of the context before proceeding. Here’s a short snippet that illustrates the point. The snippet is an excerpt of what you get for the property from .NET Reflector.
An entity object (say, a Customer) that is bound to a particular instance of a DataContext, can’t be reused outside the parent data context, unless object tracking is disabled upfront for that context. By setting ObjectTrackingEnabled to false, you instruct the framework not to track value changes and the identity of all entity objects.
Object tracking allows for batch updates and simplifies the set up of complex queries, but it comes at a cost. When object tracking is enabled, every object holds some internal references to deferred loaders in the original context. Deferred loaders are internal objects in charge of resolving data out of detected table relationships. For example, when in the SQL Server database a foreign key relationship is detected between, say, Customers and Orders, the auto-generated domain model features an Orders entity set property on the Customer entity class. In this way, you can easily access all orders for a given customer. The list of orders is not resolved until your code attempts to read it through. Filling this data on demand is the main purpose of deferred loaders. It goes without saying that for this model to work each Customer instance needs to maintain an internal reference to the loader object.
Now, the key question. Why should I care about object tracking and their connection to deferred loaders? All in all, as a developer you get free and transparent tracking and lazy loading and may not even need to know about many details. You don’t need to know about tracking and loaders until you attempt to cache LINQ-to-SQL managed data for your own purposes.
Taking Control of Data Caching
LINQ-to-SQL runs its own queries and holds data in memory. But where exactly? Quite obviously, data is held on the tier where LINQ-to-SQL runs. Furthermore, data belongs to the instance of the DataContext class which retrieved it. Can you move this data across tiers or, more simply, across layers? As long as the data is maintained by the data context, you can’t work on those objects in total freedom. You can do some work on those objects, but you can’t move them around outside the boundaries of the data context.
Here’s a common scenario. You have an ASP.NET application and your data tier uses LINQ-to-SQL to grab some data from the database. You might want to cache this data to avoid running the query over and over again for each and every request. You likely have some code, as shown below:
To avoid running the query for every request, you may decide to cache data. By “data” here I mean the result set of the query. So you end up with code like below:
You basically add a caching layer around your LINQ-to-SQL code. The key thing that is going on here is the call to the ToList method in InitListOfCustomers. The ToList method forces the LINQ-to-SQL runtime to physically fetch the data and the resulting collection of entity objects is further saved to the ASP.NET cache. The data context is employed within the scope of a using statement and is disposed of in a few moments past retrieving the data. The ASP.NET page can safely and successfully work on its data from the cache. By adding a cache duration, you can control whether data is stale and proceed with a new query if required. For the new query, you’ll use a fresh instance of the data context. All seem to work just right. Is there any problem with this?
Updating Disconnected Data
Cached data remains available even once the data context has been disposed of. What if you modify this data in memory and then want to save changes back to the database? To submit changes to the SQL Server table, you still need a data context object. You have a few options: caching the original data context, implementing the data context as a singleton, use a fresh instance of the data context class.
The DataContext class is not designed for being thread safe. So any use of it that configures usage from within different threads is not recommended. The DataContext class is designed to be a short-lived, lightweight object that you use and dispose of as soon as possible. Yes, it is much like an ADO.NET database connection object. Using a fresh instance is definitely the way to go. However, when it comes to using a fresh instance of the DataContext class to save changes back, there are a few things to consider.
The newly created context object is initially empty and you don’t want to run a query against the database to fill it out. Quite the reverse; this is just what you want to avoid. You need to attach existing and cached entities to a newly created data context. Each entity object maintains a list of references to its own deferred loaders, if any. These loaders are internal properties of the original data context—likely, a dismissed object. To avoid null reference exceptions, LINQ-to-SQL forces you not to attach objects to a data context that were originally bound to another data context. The following code, for example, will just throw when the Attach method is invoked.
How can you avoid that and still save changes back to the database? Option #1 is disabling the state tracking on entities. On the original data context that you use to retrieve data to cache, you set ObjectTrackingEnabled to false. In doing so, you keep the DataContext lean and mean but lose the possibility of detecting conflicts during updates.
In alternative, when object tracking is enabled, you have to attach unbound entities. How can you “disconnect” an entity from its original data context, even when the data context object is gone? You clone the entity, as shown below:
A clone is an object of the same type and containing the same values as the entity; it only lacks references to internal loaders and, more in general, to pieces of information specific of the data context.
Summary
The DataContext class is not designed to survive across layers and tiers. You should think of it as a sort of database connection object—to grab as late as possible and release as soon as possible. Entities queried through a given context remain bound to that context for eternity—when object tracking is enabled. You can’t attach an external entity object to a data context different from the one which created the entity. You can clone the entity, though, and get an object that is identical to the entity except for context dependencies.
Top Articles in this category
Introducing LINQ – Part 1
Introducing LINQ is the first part of a series of articles on Language Integrated Query (LINQ). This series will cover the core essentials of LINQ and its use with other technologies like ASP.NET, Win Forms and WPF.
Polymorphism and Encapsulation
Polymorphism and encapsulation are two big words in OO development, and are also a fundamental concept of software development. This article will demystify these concepts by showing you some real world examples.
Writing an ActiveX control in C#
An ActiveX control is an object that supports a customizable programmatic interface. Using the methods, events and properties exposed by the control, web developers can automate their web pages to give the functionality which is equivalent to that of a desktop application.
Introducing LINQ – Part 2
In the first part of this series I introduced you to the new language enhancements in C# 3.0, in this part we will look at querying relational data.
Introducing LINQ – Part 3
In Part 2 we took a look at LINQ to SQL, how to generate an entity, and also how to query that entity. In this part we will look a little more at what entities are, as well as taking a closer look at the key types we can use and their application.
|
|
Please login to rate or to leave a comment.