Understanding the DataContext
In an earlier post I showed how LINQ developers can connect to a database and write a simple query. This post steps behind the scenes of a LINQ to SQL application and describes the classes auto-generated by the Object Relational Designer (aka LINQ to SQL designer). The focus will be on two key items:
- The Customer and Order entity class
- The DataContext itself
The Customer and Order Entity Classes
In the previous post we dragged the Customers class from the Northwnd database onto the LINQ Object Relational Designer. A number of things happened when we did this:
- A class called Customer was created. This class maps directly to the Customers table in the Northwnd database.
- The DataContext was modified. I will describe how it was modified later in this post.
When a developer accesses an instance of the Customer class, it is almost as if they have direct access to the Customer table in the database. Figure 1 shows the fields of the Customer table as they appear in the Object Relational Designer.
Figure 1: The customer table has 12 fields.
Here is a portion of the corresponding class generated by the designer, and stored, by default, in DataClasses1.designer.cs. As you can see, one field is generated in this class for each field in the Customer table.
[Table(Name="dbo.Customers")]
public partial class Customer : INotifyPropertyChanging, INotifyPropertyChanged
{
private static PropertyChangingEventArgs emptyChangingEventArgs = new PropertyChangingEventArgs(String.Empty);
private string _CustomerID;
private string _CompanyName;
private string _ContactName;
private string _ContactTitle;
private string _Address;
private string _City;
private string _Region;
private string _PostalCode;
private string _Country;
private string _Phone;
private string _Fax;
… Code omitted here
}
The Table attribute above the Customer class informs the compiler that this class is linked to a table in the database. The attribute takes a parameter called Name, which explicitly identifies the table as dbo.Customers.
Note: If you are interested, you can learn a little more about the mapping between this class and Customers table by right clicking on the DataClasses1.dbml file in the Solution Explorer. Select Open with from the pop up dialog, and then select XML editor. You will see an XML file that shows exactly how LINQ to SQL maps the fields in an entity class to the fields in a database table. Also notice the field of the DataContext called mappingSource.
The declarations for the fields in the Customer class are very simple, and hence not terribly interesting. The Orders table, however, has more interesting fields. Double click on DataClasses1.dbml to switch back to the Object Relational Designer. Drag the Orders table onto the design surface. Switch back to DataClasses1.designer.cs and search for the newly generated Order class. In it you will see the following declarations:
private int _OrderID;
private string _CustomerID;
private System.Nullable<int> _EmployeeID;
private System.Nullable<System.DateTime> _OrderDate;
etc …
These variables map to the fields of the Orders table, and specify their type, and whether or not they are nullable. This kind of detailed information makes it possible for the IDE to give you reliable type checking and IntelliSense on the fields of the tables in your database.
The entity classes also contain an auto-generated property associated with each field. Here, for instance, is the property associated with the CustomerID field:
[Column(Storage="_CustomerID", DbType="NChar(5)")]
public string CustomerID
{
get
{
return this._CustomerID;
}
set
{
if ((this._CustomerID != value))
{
if (this._Customer.HasLoadedOrAssignedValue)
{
throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
}
this.OnCustomerIDChanging(value);
this.SendPropertyChanging();
this._CustomerID = value;
this.SendPropertyChanged("CustomerID");
this.OnCustomerIDChanged();
}
}
}
The Column attribute specifies exactly how the compiler should bind this field to a row in the database. The getter for this property is very simple. The setter is a bit more interesting since it fires a few events that track the status of the field when it is changed.
Each entity class also contains a series of partial methods that map to the events shown above in the CustomerID property. You can use these partial methods when and if you want:
partial void OnLoaded();
partial void OnValidate(System.Data.Linq.ChangeAction action);
partial void OnCreated();
partial void OnCustomerIDChanging(string value);
partial void OnCustomerIDChanged();
partial void OnCompanyNameChanging(string value);
partial void OnCompanyNameChanged();
partial void OnContactNameChanging(string value);
partial void OnContactNameChanged();
etc…
As you can see, you can fill in these partial classes in order to handle events on each field of the table. I discuss partial methods in this post. Further discussion of the techniques for using these partial methods is beyond the scope of this post.
The DataContext
From the developer’s perspective, the architecture for LINQ to SQL is quite simple. Working primarily with a single class called the DataContext, LINQ developers can:
- Connect to a database
- Access data
- Submit changes back to the server
The DataContext is generated by the Object Relational Designer. Entity classes such as Customer or Order are usually accessed via the DataContext. In our sample program the DataContext is also stored in DataClasses1.designer.cs. The declaration for the DataContext looks like this:
[System.Data.Linq.Mapping.DatabaseAttribute(Name = "northwnd")]
public partial class DataClasses1DataContext : System.Data.Linq.DataContext
{
… Code omitted here
}
Note: The nomenclature for the DataContext is a bit confusing. Technically, DataClasses1DataContext is a descendant of the internal C# class called DataContext and not the class itself. Nevertheless, developers refer to DataClass1DataContext as "the DataContext."
The DatabaseAttribute shown in the above code fragment tells the compiler to link the DataContext to the Northwind database on the server.
Inside DataClasses1DataContext is property called Customers:
public System.Data.Linq.Table<Customer> Customers
{
get
{
return this.GetTable<Customer>();
}
}
If you write a LINQ query that retrieves customer records from the database, then you can access that data via this property. It is of type Table<Customer> , where the internal Table class becomes in this case a collection of Customer records. One would typically access this property by writing code that looks like this:
DataClasses1DataContext db = new DataClasses1DataContext(ConnectionString);
var query = from c in db.Customers
select c;
When this query is executed, instances of the Customer class are automatically instantiated and filled with data from the Northwnd database. The variable db.Customers references the collection of customer records stored in the DataContext. The developer access the records returned from the database by iterating over them with a foreach statement. Each instance of the identifier q that is pulled from this query will contain an instance of Customer class:
foreach (var q in query)
{
Console.WriteLine(q.City);
}
As you learned in the previous post in this series, the DataContext also provides a means of automatically connecting to a database. Since the subject has already been thoroughly covered, I will not say anything further about that feature of the DataContext in this post.
Summary
In this post you learned a few basic facts about the files auto-generated by the Object Relational Designer. These classes include the:
- The entity classes such as Customer or Order that contain a mapping between the code in your program and the data in your database.
- The entity classes declare each field of the table by type, and declares whether or not it is nullable.
- There are events fired when you update a field in an entity class. You can use the C# 3.0 partial method technology to handle these events.
- The DataContext provides a property that serves as a simple means of accessing individual instances of the Customer class through a collection declared to be of type Table<Customer>.
- The DataContext also provides a means of connecting and opening a database.
Comments
Anonymous
December 10, 2007
You've been kicked (a good thing) - Trackback from DotNetKicks.comAnonymous
December 11, 2007
A simple question, however very important for me. Let's say I have 100 SPs, I added them to designer. Now I updated some of them using Sql scripts, (added or removed some columns), how do I update all the classes generated by Designer? ThxAnonymous
December 12, 2007
I have found both Object Relational Designer and SQLMetal seriously lacking from a change persepctive. I have settled with SQLMetal. Every time i make change to the db layer: I regen the code using SQLMetal Reapply custom editsAnonymous
December 12, 2007
Eugen and no fun, There is no refresh button on the SQL Designer. You can remove the sprocs that you dragged on to the designer and then copy them over again. Alternatively you can use SQLMetal to regenerate the entire DataContext and associated entity classes. The DataContext and Entity classes are declared as partial and support partial methods so that you can write your code in a separate file, hence you should not need to worry about re-applying custom edits. It would seem like the number of custom edits you need to apply should be small or zero.
- Charlie
Anonymous
December 12, 2007
What's the reason to use LINQ and not use by example NHibernate?Anonymous
December 13, 2007
Diego, There are ORM capabilities in both LINQ and NHibernate, but LINQ also integrates querying as a first class citizen in the C# language. LINQ provides a single syntax for querying relational data, XML data, the collections in your program, and it can be extented to query other data sources. It is fully integrated into C#, and therefore provides type checking and full IntelliSense support. LINQ is integrated into the C# language in such a way as to provide a significant change in the way we query data. It also supports transformations, in the sense that you can transform relational data into XML data, or into other types of data. It is also composable, allowing you, for instance, to compose multiple queries into single statements, even when the queries are run against different data sources.
- Charlie
Anonymous
December 17, 2007
The comment has been removedAnonymous
December 30, 2007
Pablo, well LInQ as it was released only support for MS SQL Server. For others RDBMS will be perhaps a support in the near future. cmf.Anonymous
January 18, 2008
Pablo, At this stage, LINQ to Entities plans support for additional providers such as Oracle, but LINQ to SQL has not yet resolved this issue.
- Charlie
Anonymous
February 20, 2008
The comment has been removedAnonymous
May 25, 2008
I saw the possibility in the LinqToSql-designer to override the base class of the DataContext. Would it be possible override this base class with the MySQL DatabaseContext variant from http://code2code.net/Linq_Mysql? Does studio use this datacontext to regenerate the Entity classes?Anonymous
August 29, 2008
It is Great description on how DataContext class works. As a new professional coder, your this post was really very useful. ThanksAnonymous
October 09, 2008
Charlie, I have a set of tables that I create dinamicaly, say Day1, Day2, Day3. I created just one class to read Day1 table and need to re-map Day1 class temporary to read Day2. Don't want to use synonyms. Thanks, VladimirAnonymous
October 30, 2008
Charlie, Do you have any links to discussions on real world usage of the DataContext in a production application? How is it generally used? Does one create the DC each time data is required to be viewed, updated, inserted in the database? Or do you have a data access layer that creates a single context that is around the entire uptime of the application. The reason I ask is if you create one each time you view/update or insert new data then you appear lose the auto notification updates of the data that are bound to the controls on the UI (as implemented by the INotifyPropertyChanged interface in the generated ORM classes). Thanks for any links or opinions!Anonymous
November 09, 2008
Charlie, Has the Linq to SQL using Oracle issue been resolved yet? Ann