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:
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:
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.
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;
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:
public string CustomerID
if ((this._CustomerID != value))
throw new System.Data.Linq.ForeignKeyReferenceAlreadyHasValueException();
this._CustomerID = value;
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();
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.
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:
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
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
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)
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.
In this post you learned a few basic facts about the files auto-generated by the Object Relational Designer. These classes include the: