CHAPTER 5
A data context driver is a mechanism that allows programmers to extend LINQPad in order to support several data sources. In other words, it is the way to add new drivers to the following dialog.

Figure 32: The Choose Data Context Dialog
LINQPad can query any data source without a custom data context driver, but in this case, the user must manually reference libraries, import custom namespaces, and formulate all queries like the following example.
Code Listing 41: Querying Data without a Custom Data Context Driver
var dataSource = new ItemsData(); (from i in dataSource.Items where i.Name.StartsWith ("D") select new { i.Name, i.UnitPrice }).Dump(); |
Instead, by using a custom data context driver, the query from the previous example turns into the following code.
Code Listing 42: Querying Data with a Custom Data Context Driver
(from i in dataSource.Items where i.Name.StartsWith ("D") select new { i.Name, i.UnitPrice }).Dump(); |
If we compare Code Listing 41 and Code Listing 42, the difference between them is the instantiation of the ItemsData() class. It might not seem like a big deal for small queries, but in large queries, the use of a data context driver helps you avoid writing many extra lines of code.
A data context driver is employed every time a user adds a connection to LINQPad. Those connections appear on the Connection’s tree view area of the user interface.
To add a connection, click the Add Connection hyperlink located at the top of the Connection’s tree view area. The dialog shown in Figure 32 pops up on the screen. If you click the View More Drivers button (which is highlighted in the same figure), the dialog displayed in the following figure appears.

Figure 33: The Choose a Driver Dialog
The Drivers Gallery is the first item shown in the dialog from Figure 33. It’s assumed that you’re online—if you’re not, this gallery is not displayed. You can click the Download hyperlink of any driver from the gallery to download and install it onto your computer. Clicking the Browse button located near the bottom of the dialog allows you to choose a driver previously deployed onto your computer. LINQPad asks for the location of the driver file in the following dialog.

Figure 34: The Browse LINQPad Data Context Driver Dialog
Once the driver is located, you can click the file name, and then click Open. If you don’t want to install a driver, you can abort the process by clicking Cancel.
Note: As shown in Figure 34, the extension for a data context driver file is .lpx (LINQPad extension).
When the driver is properly installed, it will become visible in the dialog shown in Figure 33.
Writing a data context driver is not trivial, but the process isn’t as complex as you might guess. The basic steps for building a driver are:
LINQPad’s extensibility model has been designed in a way that makes it quick to write a data context driver with basic functionality.
There are no special terms or conditions for writing a data context driver, unless you plan to submit the driver to the LINQPad Driver Gallery, which is outside the scope of this book.
There are some basics to cover before building a data context driver, to give you some perspective on how LINQPad treats data in the background. These basics are discussed in the following sections.
A connection relates to what you’ll enter after clicking Add Connection. This is wider than the classic concept of a database connection; a LINQPad connection can refer to other types of data sources, such a web service URI. In addition, a LINQPad connection can include data context-specific details, such as pluralization and capitalization options.
Note: A LINQPad connection is represented by the IConnectionInfo interface.
A typed data context is a class with properties, fields, and methods that can be queried by the user. A typical example is a typed LINQ to SQL DataContext or a typed ObjectContext in Entity Framework, both of which are shown in the following code listing.
Code Listing 43: A Typical Data Context Example
public class TypedDataContext : DataContext { public IQueryable<Product> Products { get { return this.GetTable<Product>(); } } public IQueryable<InventoryEntry> InventoryEntries { get { return this.GetTable<InventoryEntry>(); } } } |
We can define a DataContext without using a base class, as shown in the following code example.
Code Listing 44: A Typed Data Context with No Base Class
public class TypedDataContext { public IEnumerable<string> ProductNames public int[] Numbers; public void DoYourThings() { … } } |
When writing data context drivers, it’s mandatory to define a typed data context. There are two techniques for getting a typed data context:
When you click the Add Connection hyperlink in LINQPad, the dialog that appears shows two lists from which you can choose a driver:
So, why the separate lists? This is because different kinds of drivers work in different ways. The following list explains how dynamic and static drivers work:
The advantage of using a dynamic driver is that it allows the user to query data without having to first write classes in a Visual Studio project. The advantage of using a static driver is that it gives the user a finer degree of control, and compatibility with typed data contexts written in previous projects.

Figure 35: The Choose Data Context Dialog Showing the Two Kinds of Data Context Drivers
Note: Both kinds of drivers can be implemented in the same project.
Every time the user writes a query, it doesn’t explicitly refer to a particular data context. Instead, the user writes queries as shown in the following code example.
Code Listing 45: A Typical Query
from p in Products where p.Name.Contains ("CARD") select new { p.Name, p.UnitPrice } |
When the user runs this query, LINQPad subclasses the typed data context by transforming the user’s query into a method, as seen in the following code example.
Code Listing 46: A Query After Being Transformed by LINQPad
public class UserQuery : TypedDataContext { public UserQuery (parameters...) : base (parameters...) { } void RunUserAuthoredQuery() { ( from p in Products where p.Name.Contains ("CARD") select new { p.Name, p.UnitPrice } ) .Dump(); } } |
After that, LINQPad calls the C# or VB compiler service (depending on the language selected in the user interface), compiles the code into a temporary assembly, creates an instance of the class, and finally, calls RunUserAuthoredQuery. This principle applies to both dynamic and static drivers.
Note: The TypedDataContext class of a data context driver must not be a sealed class and must have a public constructor.
All the concepts discussed previously will be combined in the following sections in order to create a custom data context driver. Let’s have some fun!
The first step is to set up a Visual Studio project with the following properties:
The following figure shows the project configuration.

Figure 36: The Data Context Driver Project Properties
After configuring the project as shown in Figure 36, click OK to create the project files, which will be located at D:\LINQPad Samples\MyDataContextDriver.
Every time LINQPad tries to install a data context driver, it looks for a file named header.xml. If this file is not present in the deployment package, the installation process will fail. So, the first thing to do is add the corresponding header.xml file to the project. This file can be created in a text editor (like Notepad), according to the structure shown in the following code example.
Code Listing 47: The header.xml File
<?xml version="1.0" encoding="utf-8" ?> <DataContextDriver> <MainAssembly> MyDataContextDriver.dll</MainAssembly> <SupportUri>http://YourURIHere</SupportUri> </DataContextDriver> |
The file should be saved at the project’s root directory (in this case D:\LINQPad Samples\MyDataContextDriver). Next, it should be added to the project by right-clicking on the project’s name node in the Solution Explorer, and then clicking the Add > Existing Item option from the context menu.

Figure 37: Adding an Existing Item to The Project
The Add Existing Item dialog will appear. Select All Files (*.*) in the file types list, and then go to the D:\LINQPad Samples\MyDataContextDriver folder to select the header.xml file. Finally, click the Add button to complete the process and make the file visible in the solution items tree.
As mentioned previously, the header.xml file is necessary for deploying the data context driver to ensure that the file will be copied to the project’s output directory, along with the assembly files and dependencies. To accomplish this, change the file’s properties by right-clicking the file name and going to the Properties window located at the bottom of the Solution Explorer. Set the Build Action property to Content and the Copy to Output Directory property to Copy if newer.
These steps are shown in the following figures.

Figure 38: The Add Existing Item Dialog

Figure 39: The header.xml File in the Project

Figure 40: The header.xml File Properties
As mentioned previously, a context driver project can hold both dynamic and static context drivers. To accomplish this, we’ll create a pair of folders named mydynamicdriver and mystaticdriver within the project.

Figure 41: The Dynamic and Static Context Drivers Folders
At this point, the only reference needed is the one that points to LINQPad.exe. Let’s add this reference to the project.

Figure 42: The Reference to LINQPad Added into the Project
There are two base classes for context drivers: DynamicDataContextDriver and StaticDataContextDriver. The name of each type tells us the kind of driver we can build with each one of them. Both types are derived from the base class DataContextDriver, which is defined in the LINQPad.Extensibility.DataContext namespace. This class defines some abstract members that will be implemented during project creation.
The first thing to do is add a code file for each kind of data context driver. These files will be named MyDynamicDataContextDriver.cs and MyStaticDataContextDriver.cs, and will define the MyDynamicDataContextDriver and MyStaticDataContextDriver classes, respectively. Since both classes are derived from the DataContextDriver class, Visual Studio automatically asks us to implement the abstract members defined in DataContextDriver. We simply let Visual Studio generate the implementation code automatically. Now, the code for both classes looks like the following examples.
Code Listing 48: MyDynamicDataContextDriver.cs
using System.Collections.Generic; using System.Reflection; using LINQPad.Extensibility.DataContext; namespace MyDataContextDriver.mydynamicdriver { public class MyDynamicDataContextDriver : DynamicDataContextDriver { public override string GetConnectionDescription(IConnectionInfo cxInfo) { throw new System.NotImplementedException(); } public override bool ShowConnectionDialog(IConnectionInfo cxInfo, bool isNewConnection) { throw new System.NotImplementedException(); } public override string Name => "My Dynamic Data Context Driver (Demo)"; public override string Author => "Getting the Most from LINQPad Succinctly"; public override List<ExplorerItem> GetSchemaAndBuildAssembly(IConnectionInfo cxInfo, AssemblyName assemblyToBuild, ref string nameSpace,ref string typeName) { throw new System.NotImplementedException(); } } } |
Code Listing 49: MyStaticDataContextDriver.cs
using System; using System.Collections.Generic; using LINQPad.Extensibility.DataContext; namespace MyDataContextDriver.mystaticdriver { public class MyStaticDataContextDriver : StaticDataContextDriver { public override string GetConnectionDescription(IConnectionInfo cxInfo) { throw new NotImplementedException(); } public override bool ShowConnectionDialog(IConnectionInfo cxInfo, bool isNewConnection) { throw new NotImplementedException(); } public override string Name => "My Static Data Context Driver (Demo)"; public override string Author => "Getting the Most from LINQPad Succinctly"; public override List<ExplorerItem> GetSchema(IConnectionInfo cxInfo, Type customType) { throw new NotImplementedException(); } } } |
The code shown in the previous examples has already implemented the Name and Author abstract members. The Name member returns the name for the driver that is displayed in the LINQPad Driver column of the Choose Data Context dialog. The Author member, as it suggests, displays the name of the driver’s author in the corresponding column of the Choose Data Context dialog.
At this point, we will focus on building the static data context driver defined in MyStaticDataContextDriver.cs. To do so, we’re going to follow the series of steps explained in the following sections.
The GetSchema method
The first step is implementing the GetSchema abstract method shown in Code Listing 48. This method is used to return a hierarchy of objects that will be displayed in the Schema Explorer (which is the Connection’s tree view area of the user interface). This implementation is presented in the following code listing.
Code Listing 50: GetSchema Method Implementation
public override List<ExplorerItem> GetSchema(IConnectionInfo cxInfo, Type customType) { //First, we iterate throught all top level properties of custumType var topLevelProps = ( from prop in customType.GetProperties() where prop.PropertyType != typeof (string) // Get and display all properties of IEnumerable<T> let ienumerableOfT = prop.PropertyType.GetInterface ("System.Collections.Generic.IEnumerable`1") where ienumerableOfT != null orderby prop.Name select new ExplorerItem (prop.Name, ExplorerItemKind.QueryableObject, ExplorerIcon.Table) { IsEnumerable = true, ToolTipText = FormatTypeName (prop.PropertyType, false), // Store entity type to the Tag property. This will be used later. Tag = ienumerableOfT.GetGenericArguments()[0] } ).ToList (); // Create a lookup element, associating each element type to the properties of that type. // This will allow to build hyperlinks which let the user click between relationships. var elementTypeLookup = topLevelProps.ToLookup (tp => (Type)tp.Tag); // Populate the properties of each entity foreach (var table in topLevelProps) table.Children = ((Type)table.Tag) .GetProperties() .Select (childProp => GetChildItem (elementTypeLookup, childProp)) .OrderBy (childItem => childItem.Kind) .ToList (); return topLevelProps; } private ExplorerItem GetChildItem (ILookup<Type, ExplorerItem> elementTypeLookup, PropertyInfo childProp) { //if the property's type is in the list of entities, then we're going to assume that it's a Many:1 or //1:1 referefence. It's not reliable to identify 1:1s relationships purely from reflection. if (elementTypeLookup.Contains (childProp.PropertyType)) return new ExplorerItem (childProp.Name, ExplorerItemKind.ReferenceLink, ExplorerIcon.ManyToOne) { HyperlinkTarget = elementTypeLookup [childProp.PropertyType].First (), ToolTipText = FormatTypeName (childProp.PropertyType, true) // FormatTypeName is a LINQPad's helper method that returns a nicely formatted type name. }; //We're going to check if the property's type is a collection of entities var ienumerableOfT = childProp.PropertyType.GetInterface ("System.Collections.Generic.IEnumerable`1"); // If it isn't we return the Name and Type of the property as an ExplorerItem if (ienumerableOfT == null) return new ExplorerItem(childProp.Name + " (" + FormatTypeName(childProp.PropertyType, false) + ")",ExplorerItemKind.Property, ExplorerIcon.Column); //Now, we're going to check if it is a 1:Many relationship var elementType = ienumerableOfT.GetGenericArguments()[0]; if (elementTypeLookup.Contains(elementType)) return new ExplorerItem (childProp.Name, ExplorerItemKind.CollectionLink, ExplorerIcon.OneToMany) { HyperlinkTarget = elementTypeLookup [elementType].First (), ToolTipText = FormatTypeName (elementType, true) }; //If it isn't, this is an ordinary property. return new ExplorerItem (childProp.Name + " (" + FormatTypeName (childProp.PropertyType, false) + ")", ExplorerItemKind.Property, ExplorerIcon.Column); } |
The first thing this code does is get a list of all top-level properties of the Type passed to the method. In the case of a database type, the entities (tables) of the database will be the top-level properties.
The next step to complete is the association of the elements for each top-level type (again, in the case of a database type, the elements will be the columns of each entity) into the Children property of each top-level element. The GetChildItem private method formats each descendant element in order to create a hyperlink when one of these descendants is involved in a relationship with another entity. The result of this implementation can be seen in the following figure.

Figure 43: The GetSchema Method in Action
The ShowConnectionDialog method
This method must display a modal WPF dialog that will prompt the user for connection information. Since we’re going to use WPF in the project, a reference to System.Xaml must be added to this. After that, we’re going to right-click the mystaticdriver tree view’s node and select the Add > New Item option from the context menu. Now, in the Add New Item dialog, look for the WPF section. The dialog should look like the following figure.

Figure 44: The WPF Section in the Add New Item Dialog
We can see in Figure 44 that only one option is available in the WPF section, and it’s not a form, but a WPF User Control. To solve this problem, we’re going to make a little tweak to the MyDataContextDriver.csproj file. Using a text editor, we’ll add the following line to the global <PropertyGroup> section in the file.
Code Listing 51: Code Line for Tweaking the MyDataContextDriver.csproj File
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> |
The <ProjectTypeGuids> tag tells Visual Studio which kind of project it’s dealing with. In this case, {60dc8134-eba5-43b8-bcc9-bb4bc16c2548} stands for WPF, and {FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} stands for a C# project. Now, the global <PropertyGroup> section of MyDataContextDriver.csproj should look like the following code example.
Code Listing 52: The Global <PropertyGroup> Section Tweaked
<PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>{59F6858D-5BAC-4A9F-9D84-40E012AFC36C}</ProjectGuid> <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids> <OutputType>Library</OutputType> <AppDesignerFolder>Properties</AppDesignerFolder> <RootNamespace>MyDataContextDriver</RootNamespace> <AssemblyName>MyDataContextDriver</AssemblyName> <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <FileAlignment>512</FileAlignment> <Deterministic>true</Deterministic> </PropertyGroup> |
Now, when we select Add > New Item from the context menu, the dialog will look like the following figure.

Figure 45: The WPF Section After Tweaking MyDataContextDriver.csproj
To add the WPF form, we’re going to type ConnectionDialog into the Name text box, and then click the Add button. Now, a ConnectionDialog.xaml file entry will be displayed in the Solution Explorer. To add the necessary controls to enter connection information, we’re going to right-click the entry and select View Designer from the context menu. Once the designer is displayed on the screen, we’ll place those controls using the designer’s toolbox, dragging each one of them into the form. The final result of these actions will be a form that will look like the following figure.

Figure 46: The Connection Dialog in the WPF Form’s Designer
Now, we will create the event handlers for every control in the form (except labels). The resulting code is shown in the following example.
Code Listing 53: Interaction Logic for ConnectionDialog
using System; using System.Diagnostics.CodeAnalysis; using System.IO; using System.Windows; using LINQPad.Extensibility.DataContext; namespace MyDataContextDriver.mystaticdriver { /// <inheritdoc cref="Window" /> /// <summary> /// Interaction logic for ConnectionDialog.xaml /// </summary> public partial class ConnectionDialog { readonly IConnectionInfo _cxInfo; public ConnectionDialog(IConnectionInfo cxInfo) { _cxInfo = cxInfo; DataContext = cxInfo.CustomTypeInfo; InitializeComponent(); } private void BrowseAssembly(object sender, RoutedEventArgs e) { var dialog = new Microsoft.Win32.OpenFileDialog() { Title = "Choose custom assembly", DefaultExt = ".dll", }; if (dialog.ShowDialog() == true) _cxInfo.CustomTypeInfo.CustomAssemblyPath = dialog.FileName; } [SuppressMessage("ReSharper", "CoVariantArrayConversion")] private void ChooseType(object sender, RoutedEventArgs e) { var assemPath = _cxInfo.CustomTypeInfo.CustomAssemblyPath; if (assemPath.Length == 0) { MessageBox.Show("First enter a path to an assembly."); return; } if (!File.Exists(assemPath)) { MessageBox.Show("File '" + assemPath + "' does not exist."); return; } string[] customTypes; try { customTypes = _cxInfo.CustomTypeInfo.GetCustomTypesInAssembly(); } catch (Exception ex) { MessageBox.Show("Error obtaining custom types: " + ex.Message); return; } if (customTypes.Length == 0) { MessageBox.Show("There are no public types in that assembly."); return; } var result = (string)LINQPad.Extensibility.DataContext.UI.Dialogs.PickFromList("Choose Custom Type", customTypes); if (result != null) _cxInfo.CustomTypeInfo.CustomTypeName = result; } private void BrowseAppConfig(object sender, RoutedEventArgs e) { var dialog = new Microsoft.Win32.OpenFileDialog() { Title = "Choose application config file", DefaultExt = ".config", }; if (dialog.ShowDialog() == true) _cxInfo.AppConfigPath = dialog.FileName; } private void BtnOK_Click(object sender, RoutedEventArgs e) => DialogResult = true; } } |
The most significant member of this code is _cxInfo, which is an IConnectionInfo type. This object will hold the connection information using the attributes detailed in the following table.
Table 3: IConnectionInfo Attributes
Attribute | Description |
|---|---|
CustomTypeInfo.CustomAssemblyPath | Path to the custom assembly file containing the entity types that will be displayed in the Schema Explorer. |
CustomTypeInfo.GetCustomTypesInAssembly() | Retrieves a list of all public custom types exposed by the custom assembly. |
CustomTypeInfo.CustomTypeName | Name of the custom type that will be used to populate the Schema Explorer. |
AppConfigPath | Path to the configuration file associated with the custom assembly being used (.config file extension). |
Once the form’s design is finished, we’ll use it in the ShowConnectionDialog method, as shown in the following code example.
Code Listing 54: ShowConnectionDialog Implemented
public override bool ShowConnectionDialog(IConnectionInfo cxInfo, bool isNewConnection) => new ConnectionDialog(cxInfo).ShowDialog() == true; |
Now, if you add a connection using our driver, the connection dialog will appear, asking for the information needed to create it. If you click Cancel, the ShowDialog method will return false, and the process will be terminated. Otherwise, LINQPad will populate the Schema Explorer with the custom type’s entities and their attributes.
The GetConnectionDescription method
Finally, we need to implement the GetConnectionDescription method in order to display the selected custom type’s name in the Schema Explorer. This will be accomplished using the code in the following example.
Code Listing 55: GetConnectionDescription Method Implemented
// We'll use the description of the custom type and its public override string GetConnectionDescription(IConnectionInfo cxInfo) => cxInfo.CustomTypeInfo.GetCustomTypeDescription(); |
We can see the use of CustomTypeInfo.GetCustomTypeDescription, which is the method that retrieves the name of the custom type selected for populating the Schema Explorer.
The completed static data context driver
The static data context driver will be obtained as a result of completing all the previous steps. The final code is shown in the following example.
Code Listing 56: Completed MyStaticDataContextDriver
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using LINQPad.Extensibility.DataContext; namespace MyDataContextDriver.mystaticdriver { public class MyStaticDataContextDriver : StaticDataContextDriver { // We'll use the description of the custom type and its assembly for Static Drivers public override string GetConnectionDescription(IConnectionInfo cxInfo) => cxInfo.CustomTypeInfo.GetCustomTypeDescription(); public override bool ShowConnectionDialog(IConnectionInfo cxInfo, bool isNewConnection) => new ConnectionDialog(cxInfo).ShowDialog() == true; public override string Name => "My Static Data Context Driver (Demo)"; public override string Author => "Getting the Most from LINQPad Succinctly"; public override List<ExplorerItem> GetSchema(IConnectionInfo cxInfo, Type customType) { // First, we iterate through all top-level properties of customType var topLevelProps = ( from prop in customType.GetProperties() where prop.PropertyType != typeof (string) // Get and display all properties of IEnumerable<T> let ienumerableOfT = prop.PropertyType.GetInterface ("System.Collections.Generic.IEnumerable`1") where ienumerableOfT != null orderby prop.Name select new ExplorerItem (prop.Name, ExplorerItemKind.QueryableObject, ExplorerIcon.Table) { IsEnumerable = true, ToolTipText = FormatTypeName (prop.PropertyType, false), // Store entity type to the Tag property. This will be used later. Tag = ienumerableOfT.GetGenericArguments()[0] } ).ToList (); // Create a lookup element, associating each element type to the properties of that type. // This will allow us to build hyperlinks that let the user click between relationships. var elementTypeLookup = topLevelProps.ToLookup (tp => (Type)tp.Tag); // Populate the properties of each entity foreach (var table in topLevelProps) table.Children = ((Type)table.Tag) .GetProperties() .Select (childProp => GetChildItem (elementTypeLookup, childProp)) .OrderBy (childItem => childItem.Kind) .ToList (); return topLevelProps; } private ExplorerItem GetChildItem (ILookup<Type, ExplorerItem> elementTypeLookup, PropertyInfo childProp) { // If the property's type is in the list of entities, then we're going to assume that it's a Many:1 or // 1:1 reference. It's not reliable to identify 1:1 relationships purely from reflection. if (elementTypeLookup.Contains (childProp.PropertyType)) return new ExplorerItem (childProp.Name, ExplorerItemKind.ReferenceLink, ExplorerIcon.ManyToOne) { HyperlinkTarget = elementTypeLookup [childProp.PropertyType].First (), ToolTipText = FormatTypeName (childProp.PropertyType, true) // FormatTypeName is LINQPad's helper method that returns a nicely formatted type name. }; // We're going to check if the property's type is a collection of entities var ienumerableOfT = childProp.PropertyType.GetInterface ("System.Collections.Generic.IEnumerable`1"); // If it isn't, we return the Name and Type of the property as an ExplorerItem if (ienumerableOfT == null) return new ExplorerItem(childProp.Name + " (" + FormatTypeName(childProp.PropertyType, false) + ")",ExplorerItemKind.Property, ExplorerIcon.Column); // Now, we're going to check if it is a 1:Many relationship var elementType = ienumerableOfT.GetGenericArguments()[0]; if (elementTypeLookup.Contains(elementType)) return new ExplorerItem (childProp.Name, ExplorerItemKind.CollectionLink, ExplorerIcon.OneToMany) { HyperlinkTarget = elementTypeLookup [elementType].First (), ToolTipText = FormatTypeName (elementType, true) }; // If it isn't, this is an ordinary property. return new ExplorerItem (childProp.Name + " (" + FormatTypeName (childProp.PropertyType, false) + ")", ExplorerItemKind.Property, ExplorerIcon.Column); } } } |
To deploy the data context driver, we need to perform the following steps:
Now, it’s time to install our developed driver into LINQPad. To complete this action, it’s mandatory to try adding a new connection by clicking the Add Connection hyperlink in the user interface. As seen at the beginning of this chapter, LINQPad will show the Choose Data Context dialog. In order to work with custom drivers, we need to click the View More Drivers button. This will bring up the Choose a Driver dialog, as seen in the “Data context drivers from the user’s perspective” section. Once the dialog is displayed, we’ll click the Browse button, and the Browse LINQPad Data Context Driver dialog will be shown.

Figure 47: Browse LINQPad Data Context Driver Dialog Displaying Our Custom Context Data Driver
Next, we need to locate the file for our custom driver and double-click its name. This will open the file and install the driver. When finished, LINQPad will show the following dialog.

Figure 48: Context Data Driver Successfully Installed
Now, the Choose Data Context dialog will display our custom data context drivers as available options.

Figure 49: Our Custom Context Data Drivers in the Choose Data Context Dialog
To test our driver, select the Use a typed data context from your own assembly option from the Choose Data Context dialog, and choose My Static Data Context Driver (Demo) from the drivers list. Click Next to continue, and the connection dialog will be displayed.

Figure 50: The Connection Dialog of Our Custom Context Data Driver
We’re going to use the Entity Data Model assembly created in Chapter 2 to test our driver. So, after we browse the file system and select the uspostalcodes.dll assembly, the connection dialog should look like the following figure.

Figure 51: The uspostalcodes.dll Assembly Selected
Now, we’re going to browse the file system and locate the uspostalcodes.dll.config file, which is the application configuration file required by the connection dialog. Finally, we’re going to choose the uspostalcodes.uspostalcodesEntities custom type. This type will be used to populate the Schema Explorer.

Figure 52: The Choose Custom Type Dialog
After you complete the previous steps, the connection dialog will look like this.

Figure 53: The Connection Dialog Filled In
After you click OK, LINQPad will create the connection and display the entities in the user interface.

Figure 54: The uspostalcodesEntities Displayed Using Our Custom Driver
To display the description of our custom driver, right-click the uspostalcodesEntities node. A context menu will be displayed, and the description for our driver will be shown at the top of this menu. The following figure shows an example.

Figure 55: LINQPad Using Our Custom Driver
We will execute a query of one of the displayed entities to finish our testing process. In this case, we’ll show the elements stored in the states entity using a data grid. To accomplish this, right-click the states node in the user interface, and then select the View top 100 rows in grid option.

Figure 56: Executing a Query with The Driver
LINQPad will automatically generate the appropriate code for executing the query, and the results will be displayed in the output panel.

Figure 57: Displaying the Results of the Query
We can debug our custom driver by following these steps:

Figure 58: The Attach to Process Dialog
When a custom driver throws an exception, LINQPad writes the exception stack trace and details in a log file. This file resides in the %localappdata%\linqpad\logs\ folder, which is usually located at C:\Users\UserName\AppData\Local\LINQPad\logs.
Code Listing 57: LINQPad log.txt File Example
5.36.03 2018-12-19T09:54:55.8803299-07:00 NotImplementedException: The method or operation is not implemented. at MyDataContextDriver.mystaticdriver.MyStaticDataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Repository.GetFriendlyName(FriendlyNameMode mode) First chance data: LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(cxInfo) offset=0xFFFFFFFF -LINQPad.Repository.GetFriendlyName(mode) offset=0x7C -LINQPad.Repository.GetFriendlyName() offset=0x1 -LINQPad.UI.SchemaTreeInternal.RepositoryNode..ctor(r) offset=0x22 -LINQPad.UI.SchemaTreeInternal.StaticSchemaNode..ctor(r) offset=0x7 -LINQPad.UI.SchemaTree.AddCx(repos,child,selectNode,expandNode,massPopulation,updateAllNodeText) offset=0x35 -LINQPad.UI.SchemaTree.AddCx(r,selectNode,expandNode) offset=0xD -LINQPad.UI.SchemaTree.AddCx() offset=0x2C -LINQPad.UI.SchemaTree.ProcessLeftMouseDown(node) offset=0x250 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x27 -LINQPad.Program.Run(queryToLoad,runQuery,activationCode,activateAll,deactivate,noForward,noUpdate,caller) offset=0x27C -LINQPad.Program.Go(args) offset=0x763 -LINQPad.Program.Start(args) offset=0xA3 -LINQPad.ProgramStarter.Run(args) offset=0x12 -LINQPad.Loader.Main(args) offset=0x2B4 5.36.03 2018-12-19T09:54:55.9595444-07:00 NotImplementedException: The method or operation is not implemented. at MyDataContextDriver.mystaticdriver.MyStaticDataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Repository.GetFriendlyName(FriendlyNameMode mode) First chance data: LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(cxInfo) offset=0xFFFFFFFF -LINQPad.Repository.GetFriendlyName(mode) offset=0x7C -LINQPad.UI.QueryControl.UpdateFocusedRepository() offset=0x3E -LINQPad.UI.QueryControl._schemaTree_AfterSelect(sender,e) offset=0x7 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x3C -LINQPad.UI.SchemaTree.WndProc(m) offset=0x3C -LINQPad.UI.SchemaTree.AddCx(repos,child,selectNode,expandNode,massPopulation,updateAllNodeText) offset=0xED -LINQPad.UI.SchemaTree.AddCx(r,selectNode,expandNode) offset=0xD -LINQPad.UI.SchemaTree.AddCx() offset=0x2C -LINQPad.UI.SchemaTree.ProcessLeftMouseDown(node) offset=0x250 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x27 -LINQPad.Program.Run(queryToLoad,runQuery,activationCode,activateAll,deactivate,noForward,noUpdate,caller) offset=0x27C -LINQPad.Program.Go(args) offset=0x763 -LINQPad.Program.Start(args) offset=0xA3 -LINQPad.ProgramStarter.Run(args) offset=0x12 -LINQPad.Loader.Main(args) offset=0x2B4 5.36.03 2018-12-19T09:54:55.9675620-07:00 NotImplementedException: The method or operation is not implemented. at MyDataContextDriver.mystaticdriver.MyStaticDataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Repository.GetFriendlyName(FriendlyNameMode mode) First chance data: LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(cxInfo) offset=0xFFFFFFFF -LINQPad.Repository.GetFriendlyName(mode) offset=0x7C -LINQPad.UI.QueryControl.UpdateFocusedRepository() offset=0x3E -LINQPad.UI.QueryControl._schemaTree_AfterSelect(sender,e) offset=0x7 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x3C -LINQPad.UI.SchemaTree.WndProc(m) offset=0x3C -LINQPad.UI.SchemaTree.AddCx(repos,child,selectNode,expandNode,massPopulation,updateAllNodeText) offset=0xED -LINQPad.UI.SchemaTree.AddCx(r,selectNode,expandNode) offset=0xD -LINQPad.UI.SchemaTree.AddCx() offset=0x2C -LINQPad.UI.SchemaTree.ProcessLeftMouseDown(node) offset=0x250 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x27 -LINQPad.Program.Run(queryToLoad,runQuery,activationCode,activateAll,deactivate,noForward,noUpdate,caller) offset=0x27C -LINQPad.Program.Go(args) offset=0x763 -LINQPad.Program.Start(args) offset=0xA3 -LINQPad.ProgramStarter.Run(args) offset=0x12 -LINQPad.Loader.Main(args) offset=0x2B4 5.36.03 2018-12-19T09:54:56.0106750-07:00 NotImplementedException: The method or operation is not implemented. at MyDataContextDriver.mystaticdriver.MyStaticDataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Repository.GetFriendlyName(FriendlyNameMode mode) First chance data: LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(cxInfo) offset=0xFFFFFFFF -LINQPad.Repository.GetFriendlyName(mode) offset=0x7C -LINQPad.UI.QueryControl.UpdateFocusedRepository() offset=0x3E -LINQPad.UI.QueryControl._schemaTree_AfterSelect(sender,e) offset=0x7 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x3C -LINQPad.UI.SchemaTree.WndProc(m) offset=0x3C -LINQPad.UI.SchemaTree.AddCx(repos,child,selectNode,expandNode,massPopulation,updateAllNodeText) offset=0xED -LINQPad.UI.SchemaTree.AddCx(r,selectNode,expandNode) offset=0xD -LINQPad.UI.SchemaTree.AddCx() offset=0x2C -LINQPad.UI.SchemaTree.ProcessLeftMouseDown(node) offset=0x250 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x27 -LINQPad.Program.Run(queryToLoad,runQuery,activationCode,activateAll,deactivate,noForward,noUpdate,caller) offset=0x27C -LINQPad.Program.Go(args) offset=0x763 -LINQPad.Program.Start(args) offset=0xA3 -LINQPad.ProgramStarter.Run(args) offset=0x12 -LINQPad.Loader.Main(args) offset=0x2B4 5.36.03 2018-12-19T09:54:56.0157221-07:00 NotImplementedException: The method or operation is not implemented. at MyDataContextDriver.mystaticdriver.MyStaticDataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Repository.GetFriendlyName(FriendlyNameMode mode) First chance data: LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(cxInfo) offset=0xFFFFFFFF -LINQPad.Repository.GetFriendlyName(mode) offset=0x7C -LINQPad.UI.QueryControl.UpdateFocusedRepository() offset=0x3E -LINQPad.UI.QueryControl._schemaTree_AfterSelect(sender,e) offset=0x7 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x3C -LINQPad.UI.SchemaTree.WndProc(m) offset=0x3C -LINQPad.UI.SchemaTree.AddCx(repos,child,selectNode,expandNode,massPopulation,updateAllNodeText) offset=0xED -LINQPad.UI.SchemaTree.AddCx(r,selectNode,expandNode) offset=0xD -LINQPad.UI.SchemaTree.AddCx() offset=0x2C -LINQPad.UI.SchemaTree.ProcessLeftMouseDown(node) offset=0x250 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x27 -LINQPad.Program.Run(queryToLoad,runQuery,activationCode,activateAll,deactivate,noForward,noUpdate,caller) offset=0x27C -LINQPad.Program.Go(args) offset=0x763 -LINQPad.Program.Start(args) offset=0xA3 -LINQPad.ProgramStarter.Run(args) offset=0x12 -LINQPad.Loader.Main(args) offset=0x2B4 5.36.03 2018-12-19T09:54:56.0307559-07:00 NotImplementedException: The method or operation is not implemented. at MyDataContextDriver.mystaticdriver.MyStaticDataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(IConnectionInfo cxInfo) at LINQPad.Repository.GetFriendlyName(FriendlyNameMode mode) First chance data: LINQPad.Extensibility.DataContext.DataContextDriver.GetConnectionDescription(cxInfo) offset=0xFFFFFFFF -LINQPad.Repository.GetFriendlyName(mode) offset=0x7C -LINQPad.Repository.GetFriendlyName() offset=0x1 -LINQPad.UI.SchemaTreeInternal.RepositoryNode.UpdateText() offset=0x2 -LINQPad.UI.SchemaTreeInternal.StaticSchemaNode.UpdateText() offset=0x7 -LINQPad.UI.SchemaTree.UpdateAllNodeText() offset=0x31 -LINQPad.UI.SchemaTree.AddCx(repos,child,selectNode,expandNode,massPopulation,updateAllNodeText) offset=0xFC -LINQPad.UI.SchemaTree.AddCx(r,selectNode,expandNode) offset=0xD -LINQPad.UI.SchemaTree.AddCx() offset=0x2C -LINQPad.UI.SchemaTree.ProcessLeftMouseDown(node) offset=0x250 -LINQPad.UI.SchemaTree.WndProc(m) offset=0x27 -LINQPad.Program.Run(queryToLoad,runQuery,activationCode,activateAll,deactivate,noForward,noUpdate,caller) offset=0x27C -LINQPad.Program.Go(args) offset=0x763 -LINQPad.Program.Start(args) offset=0xA3 -LINQPad.ProgramStarter.Run(args) offset=0x12 -LINQPad.Loader.Main(args) offset=0x2B4 |
Part of the code of this chapter is based on information and examples provided by Joseph Albahari. This information and the code associated with it are public and can be downloaded from https://www.linqpad.net/DataContextDrivers.docx and https://www.linqpad.net/DataContextDriverDemo.zip.
A data context driver is a mechanism that allows programmers to extend LINQPad in order to support several data sources. LINQPad can query any data source without a custom data context driver, but in this case, the user must manually reference libraries, import custom namespaces, and formulate all queries.
A data context driver is employed every time a user adds a connection to LINQPad. Those connections appear on the Connection’s tree view area of the user interface. To add a connection, click the Add Connection hyperlink located at the top of the Connection’s tree view area.
The basic steps for building a driver are:
There are no special terms or conditions for writing a data context driver, unless you plan to submit the driver to LINQPad’s Driver Gallery, which is beyond the scope of this book.
The following are basic concepts about data context drivers:
For the purposes of this book, writing a data context driver requires you to set up a Visual Studio project with the following properties:
Every time LINQPad tries to install a context data driver, it looks for a file named header.xml. If this file is not present in the deployment package, the installation process will fail. So, the first thing to do is to add the corresponding header.xml file to the project. This file can be created in a text editor (like Notepad).
There are two base classes for context drivers: DynamicDataContextDriver and StaticDataContextDriver. The name of each type tells us the kind of driver we can build with each one of them. Both types are derived from the base class DataContextDriver, which is defined in the LINQPad.Extensibility.DataContext namespace. This class defines some abstract members that will be implemented during project creation. The files named MyDynamicDataContextDriver.cs and MyStaticDataContextDriver.cs will define the MyDynamicDataContextDriver and MyStaticDataContextDriver classes, to implement both kind of drivers. Since both classes are derived from the DataContextDriver class, Visual Studio automatically asks us to implement the abstract members defined in it. We simply let Visual Studio generate the implementation code automatically.
We focused on building the static data context driver defined in MyStaticDataContextDriver.cs by implementing the GetSchema abstract method. This method is employed to return a hierarchy of objects that will be displayed in the Schema Explorer (which is in the Connection’s tree view area of the user interface). Then, we implemented the ShowConnectionDialog method, which displays a modal WPF dialog to prompt the user for connection information.
To deploy the data context driver, we complete the following steps:
To install the custom driver in LINQPad, it’s mandatory that you try adding a new connection by clicking the Add Connection hyperlink in the user interface. Then, LINQPad will show the Choose Data Context dialog. Next, click the View More Drivers button. This will bring up the Choose a Driver dialog. Once the dialog is displayed, click Browse, and the Browse LINQPad Data Context Driver dialog will be shown. After that, locate the file for our custom driver and double-click its name. When installation is finished, LINQPad will show the Installation Succeeded dialog.
Finally, we tested our custom driver using the Entity Data Model assembly created in Chapter 2.