In general, a driver should be for a popular productand should already have been tested by users.

2.

You must provide a support URI (this can be a peer support forum or FAQ page).

What FrameworkVersions are Supported?

There are two versions of the LINQPad executable: one for Framework 3.5 and one for Framework 4.0/4.5.Ingeneral, if you target Framework 3.5, your driver will work with both versions.

Concepts

Terminology

Aconnection

corresponds to what the user enters

when they click ‘Add Connection’. This isbroader than theconcept of a database connection in that a LINQPad connection can point to other kinds of data sources

such a webservices URI. Further, a LINQPad connection

can include data context-specific details such as pluralization andcapitalization options. A LINQPad connection isrepresented by theIConnectionInfo

interface.

A

typed

data context

isa

classwith properties/fields/methods that the user can query. A classic example is a typedLINQ to SQLDataContext (or a typed ObjectContext in

Entity Framework):

public classTypedDataContext : DataContext

{

publicIQueryable<Customer>Customers

{ get { return this.GetTable<Customer>(); } }

publicIQueryable<Order>Orders

{ get { return this.GetTable<Orders>();

} }

}

A typed data context does notneed

base class. The following is perfectly valid:

public classTypedDataContext

{

publicIEnumerable<string>CustomerNames

publicint[] Numbers;

public void DoSomething() { … }

}

A

typed data context is mandatory if you want to write a LINQPad Data Context Driver. There are two ways toobtain a typed data context:



Your driver can build one of the fly (Dynamic

Driver)



You can consume a typed data context already defined by the user (Static

Driver)

Static vsDynamic Drivers

When you click ‘Add connection’

in LINQPad, a dialog appears with a list of data context drivers from which tochoose. These aresplit

into two lists:

“Build Data ContextAutomatically”—

Dynamic

data context drivers

“Use a typed data context from your own assembly”—

Static

data context drivers

A

dynamic driver

buildsthe typed data context on the fly. It does this either by generating code and then compilingit,

or by using Reflection.Emit.

A

staticdriver

requires that

the user supply

thetyped data context.The connection dialogthat you write willprompttheuserfor the path to a custom assembly containing the typed data context, and the name of the type.

The advantage

of a dynamic driver isthat the user can just start querying without having to first write aprojectwiththe appropriate typesin Visual Studio.The advantage of a static driver is that it gives users get 100% compatibilitywith whatever they’ve definedin their project.

Youcan implementboth kinds of driver

in the same assembly.

HowLINQPadQueries Work

Recall that users write queries in LINQPad without explicitly referring to a data context:

from c inCustomers

where c.Name.StartsWith ("A")

select new { c.Name, c.Purchases }

To make this work, LINQPadsubclasses

your typed data context, writing the user’s query into a method as follows:

public class UserQuery : TypedDataContext

{

public UserQuery (parameters...) : base (parameters...) { }

void RunUserAuthoredQuery()

{

(

from c in Customers

where c.Name.StartsWith ("A")

select new { c.Name, c.Purchases }

)

.Dump();

}

}

LINQPad then calls the

C# or VB compiler service on the class, compiles it into a temporary assembly, instantiatesthe class, and then callsRunUserAuthoredQuery.

The same principle holds with both dynamic and static drivers.

It is therefore important that your typed datacontext class is not sealed

and has a public constructor.

We’ll discuss later

how to feed parameters intotheconstructor,in

“Fine-Tuning”.

Autocompletion

Autocompletion feeds entirely on the type system, so no special work is required to support

it.

Setting up a Project

To begin, create a new class library in Visual Studio. Set the project properties as follows:



Build | Platform target = “Any CPU”



Signing | Sign the Assembly.Your

assembly must be

strong-name signed.

Then add a reference to LINQPad.exe and subclassDynamicDataContextDriver

orStaticDataContextDriver

(orboth)

as described in the following sections.

The easiest way tobegin

is to copy and paste one of the samples fromthe demo project.

When you’re done, zip up your

target

.dll

(and any dependencies) and adda file calledheader.xml

with thefollowing content:

<?xml

version="1.0"

encoding="utf-8"

?>

<DataContextDriver>

<MainAssembly>YourAssembly.dll</MainAssembly>

<SupportUri>http://mysite.com</SupportUri>

</DataContextDriver>

YourAssembly.dll

should be name of the assembly containing the drivers.

There can be any number of driver

classes

in this assembly; LINQPad looks for allpublic non-abstract

types that are based onDynamicDataContextDriver

orStaticDataContextDriver.

Once you’vegot a zip file, changeitsextension

from.zip

to.lpx.

You’ll then be able to import this into LINQPadby clicking ‘Add Connection’, ‘More Drivers’ and ‘Browse’.

When you import a driver into LINQPad, all that LINQPad does is

unzip the driver’s contents into the a folderbased in the following location:

%programdata%\LINQPad\Drivers\DataContext\4.0\

“4.0” refers to the .NET Framework version. If you’re writing a3.5

driver, this will be “3.5” instead.

Framework4.5drivers should also be 4.0 since there is no separate LINQPad build for Framework 4.5—LINQPad 4.0consumes

Framework 4.5types

as

available.

Tacked onto the end of this path isa folder comprising the

name ofthe assemblyand its

public key token in parenthesis.For

example, if you’re running Windows 7, the driverfilesmight end upin the following directory:

Builds an assembly containing a typed data context, and returns data for the Schema Explorer.

///

</summary>

///

<param name="cxInfo">Connection information, as entered by the user</param>

///

<param name="assemblyToBuild">Name and location of the targetassembly to build</param>

///

<param name="nameSpace">The suggested namespace of the typed data context. You must update this

///

parameter if you don't use the suggested namespace.</param>

///

<param name="typeName">The suggested type name of the typed data context. You must update this

///

parameter if you don't use the suggested type name.</param>

///

<returns>Schema which will be subsequently loaded into the Schema Explorer.</returns>

public

abstract

List<ExplorerItem> GetSchemaAndBuildAssembly (IConnectionInfo

cxInfo,

AssemblyName

assemblyToBuild,

ref

string

nameSpace,ref

string

typeName);

This methodmust

do two things:



Dynamically build an assembly containing a typed data context



Return the schema to display in the Schema Explorer

In the demo project there’s a complete exampleof a dynamic driver for ADO.NET Data Services. (This isfunctionally almost identical to LINQPad’s new built-in driver for Data Services).

There are no restrictions on the kinds of members that a typed data context canexpose. Your design goalshould be to provide the best querying experience for the end user.

Building theList<ExplorerItem>

for the Schema Explorer

is justas

with a static driver. However, there are twoways tosource

the raw information:



Build

the schema from the same metadata that you used

to build the typed data context



First build the typed data context,and then

reflect over the typed data context to build theList<ExplorerItem>.

The advantage of the first approach is that you have moredata on hand. This extra information can help, forinstance,

indistinguishing many:1 from many:many relationships.

Application Domains & Loading Assemblies

To provide isolation between queries, LINQPad runs each queryin its own application domain

(we call these“query domains”). If a user creates five queries, each runs itsownquery domain—even if they all use the same datacontext.

The following virtual driver methods all execute in aquery domain

(these methods are explained in “Fine-Tuning”):

GetContextConstructorArguments

InitializeContext

TearDownContext

OnQueryFinishing

GetCustomDisplayMemberProvider

PreprocessObjectToWrite

DisplayObjectInGrid

GetProviderFactory

GetIDbConnection

ExecuteESqlQuery

ClearConnectionPools

When running in a query domain, LINQPad ensures that assemblies that the user might want to rebuild in VisualStudio are “shadowed” to a temporary folder, so that they’re not locked while the query is open. This includeseverything ina

static data context folder, as well as any other reference the user has brought in that aren’t part ofProgramFiles

or theWindows

directory. You don’t have to do anything special to take advantage of shadowing,unless you want to load an assembly explicitly withAssembly.LoadFrom/LoadFile, in which case you shouldinsteadcall DataContextDriver’s helper method,LoadAssemblySafely. This will ensure that (a) shadowedassemblies are loaded from the correct location, and (b) you don’t end up with multiple copies of the same assemblyin memory.

Driver methods that aren’t in the above list (such asGetConnectionDescription) don’t run in a query domain—because a query doesn’t exist when they’re called. Instead, LINQPad creates an app domain per driver (a “driverdomain”) in which to run them. If you callLoadAssemblySafely

from a driver domain, it simply thunks toAssembly.LoadFrom. LINQPad recycles the driver domain after calling GetSchema,GetSchemaAndBuildAssembly and ShowConnectionDialog. This means you can load user assemblies directly inthese methods and notworry about the effects of locking them.

Assembly Resolution

You may need to reference other assemblies that are not part of the .NET Framework. In general, you can simplypackage in the.lpx

file

and LINQPad will load them automatically as needed. If youwant toexplicitly

tostore your data; if you populate these correctly you can callGetCxString

/

GetConnection

to get a valid connection string/

IDbConnection.

If you want to support other databases, however, you’ll need to save the details to

custom elements in

cxInfo.DriverData. You should then build the connection string yourself and write it tocxInfo.DatabaseInfo.CustomCxString

(if you fail to take this step, LINQ queries will work but userswon’t be able to write old-fashioned SQL queries, unless you overrideGetIDbConnection—see“Supporting SQL Queries”).

PerformingAdditionalInitialization /Teardown on theDataContext

Youmight want to assign properties on a newly created data context—or call methods to perform furtherinitialization. To do so, overrideInitializeContext. You can also perform teardown by overridingTearDownContext:

///

<summary>This virtual method is called after a data context object has been instantiated, in

///

preparation for a query. You can use this hook to perform additional initialization work.</summary>

public

virtual

void

InitializeContext (IConnectionInfo

cxInfo,object

context,

QueryExecutionManager

executionManager)

{ }

///

<summary>This virtual method is called after a query has completed. You canuse this hook to

///

perform cleanup activities such as disposing of the context or other objects.</summary>

public

virtual

void

TearDownContext (IConnectionInfo

cxInfo,object

context,

QueryExecutionManager

executionManager,

object[] constructorArguments) { }

TearDownContext

does not run if the user calls theCache

extension method or theUtil.Cache

methodtopreserve

results between query runs.

A useful application of overridingInitializeContext

is to

set uppopulation of

the SQL translation tab.

When a query runs, LINQPad preserves the same DataContextDriver object from the time it callsInitializeContext to when it calls TearDownContext.This meansstore state

in fields that youdefineinyour data context driver class.

You can safely access this state in GetCustomDisplayMemberProvider,PreprocessObjectToWrite and OnQueryFinishing.

There’s also anOnQueryFinishing

method that you can override. Unlike

TearDownContext, this runs

just

before

the query ends, so you can Dump extra output in this method. You can also block for as long as you like—whilewaiting

on

some background threads to finish, for instance. If the user gets tired of waiting, they’ll hit the Cancelbutton in which case your thread will be aborted, and the TearDownContext method will then run. (The next thingto happen

is that your application domain will be torn down and recreated, unless the user’s requested otherwise inEdit | Preferences | Advanced, or has cached objects alive).

///

<summary>This method is called after the query's main thread has finished running the user's code,

///

but before the query has stopped. If you've spun up threads that are still writing results, you can

///

use this method to wait out those threads.</summary>

public

virtual

void

OnQueryFinishing (IConnectionInfo

cxInfo,object

context,

QueryExecutionManager

executionManager) { }

Another way to extend a query’s “life” is to callUtil.GetQueryLifeExtensionToken. Calling this puts the queryinto an “asynchronous” state upon completion until you dispose

You can make queries automatically referenceadditional assemblies and import additional namespaces byoverriding the following methods:

///

<summary>Returns a list of additional assemblies to reference when building queries. To refer to

///

an assembly in the GAC, specify its fully qualified name, otherwise specified the assembly's full

///

location on the hard drive. Assemblies in the same folder as the driver, however, don't require a

///

folder name. If you're unable to find the necessary assemblies, throw an exception, with a message

///

indicating the problem assembly.</summary>

public

virtual

IEnumerable<string> GetAssembliesToAdd (IConnectionInfo

cxInfo)

///

<summary>Returns a list of additional namespaces that should be imported automatically into all

///

queries that use this driver. This should include the commonly used namespaces of your ORM or

///

querying technology

.</summary>

public

virtual

IEnumerable<string> GetNamespacesToAdd (IConnectionInfo

cxInfo)

LINQPad references the following assembliesautomatically:

"System.dll",

"Microsoft.CSharp.dll", (in version 4.x)

"System.Core.dll",

"System.Data.dll",

"System.Transactions.dll",

"System.Xml.dll",

"System.Xml.Linq.dll",

"System.Data.Linq.dll",

"System.Drawing.dll",

"System.Data.DataSetExtensions.dll"

"LINQPad.exe"

LINQPad imports the following namespaces automatically:

"System",

"System.IO",

"System.Text",

"System.Text.RegularExpressions",

"System.Diagnostics",

"System.Threading",

"System.Reflection",

"System.Collections",

"System.Collections.Generic",

"System.Linq",

"System.Linq.Expressions",

"System.Data",

"System.Data.SqlClient",

"System.Data.Linq",

"System.Data.Linq.SqlClient",

"System.Transactions",

"System.Xml",

"System.Xml.Linq",

"System.Xml.XPath",

"LINQPad"

You can prevent LINQPad from importing any of these namespaces by overriding this method:

///

<summary>Returns a list of namespace imports that should be removed to improve the autocompletion

///

experience. This might include System.Data.Linq if you're not using LINQ to SQL.</summary>

public

virtual

IEnumerable<string> GetNamespacesToRemove (IConnectionInfo

cxInfo)

Removing theSystem.Data.Linq

namespacemakes sense if you’rewritingdriver for an ORM,because

youmightotherwise

conflict with

LINQ to SQL’s type names.

Overriding AreRepositoriesEquivalent

After you’ve got everything else working, a nice

(and easy)

touch is to overrideAreRepositoriesEquivalent. Thisensures that if a user runs a LINQ query created on another machine that references a different (but equivalent)connection, you won’t end up with multiple identical connections in the Schema Explorer.

Here’s the default implementation:

///

<summary>Returns true if two<seecref="IConnectionInfo"/>

objects are semanticallyequal.</summary>

public

virtual

bool

AreRepositoriesEquivalent (IConnectionInfo

c1,IConnectionInfo

c2)

{

if

(!c1.DatabaseInfo.IsEquivalent (c2.DatabaseInfo))return

false;

return

c1.DriverData.ToString() == c2.DriverData.ToString();

}

The call toDriverData.ToString()

can lead to false positives, as it’s sensitive to XML element ordering. Here’s anoverridden version for the AstoriaDynamicDriver (ADO.NET Data Services):

public

override

bool

AreRepositoriesEquivalent (IConnectionInfo

r1,IConnectionInfo

r2)

{

// Two repositories point to the same endpoint if their URIs are the same.

Another nice touch with dynamic drivers is to overrideGetLastSchemaUpdate. This method is defined inDynamicDataContextDriver:

///

<summary>Returns the time that the schema was last modified. If unknown, return null.</summary>

public

virtual

DateTime? GetLastSchemaUpdate (IConnectionInfo

cxInfo) {return

null; }

LINQPad calls this after the user executes an old-fashioned SQL query. If it returns a non-null value that’s laterthan its last value, it automatically refreshes the Schema Explorer.This is useful in that quite often, the reason forusers running a SQL query is to create a new table or perform some other DDL.

Output from this

method may also be used in the future for caching data contexts between sessions.

With static drivers, no action is required: LINQPad installs a file watcher on the target assembly.When

thatassembly changes, itautomaticallyrefreshes the Schema Explorer.

Supporting SQLQueries

LINQPad lets users run old-fashioned SQL queries, by setting the query language to “SQL”. If it makes for yourdriver to support this, you can gain more control over how connections are created by overriding the followingmethods:

///

<summary>Allows you to override the default factory, which is obtained by calling

///

DbProviderFactories.GetFactory on DatabaseInfo.Provider. This can be useful if you want

///

to use uninstalled database drivers. This method is called if the user executes a query

///

with the language set to 'SQL'. Overriding GetIDbConnection renders this methodredundant.</summary>

public

virtual

DbProviderFactory

GetProviderFactory (IConnectionInfo

cxInfo)

{

try

{return

DbProviderFactories.GetFactory (cxInfo.DatabaseInfo.Provider); }

catch

(ArgumentException

ex)

{

throw

new

DisplayToUserException

(ex.Message, ex);

// Not installed

}

}

///

<summary>Instantiates a databaseconnection for queries whose languages is set to 'SQL'.

///

By default, this calls cxInfo.DatabaseInfo.GetCxString to obtain a connection string,

///

then GetProviderFactory to obtain a connection object. You can override this if you want

///

more control over creating the connection or connection string.</summary>

public

virtual

IDbConnection

GetIDbConnection (IConnectionInfo

cxInfo)

{

string

cxString = cxInfo.DatabaseInfo.GetCxString ();

if

(string.IsNullOrEmpty (cxString))

throw

new

DisplayToUserException

("A valid database connection string could not be obtained.");

var

cx = GetProviderFactory (cxInfo).CreateConnection ();

cx.ConnectionString = cxInfo.DatabaseInfo.GetCxString ();

return

cx;

}

OverridingGetIDbConnection

means you don’t have to populate the connection string inDatabaseInfo.There’salso a method that you can override to support “ESQL” queries, although this is really only relevant to EntityFramework:

public

virtual

void

ExecuteESqlQuery (IConnectionInfo

cxInfo,string

query)

{

throw

new

Exception

("ESQL queries are not supported for this type of connection");

}

Clearing Connection Pools

If your driver creates database connections, you can override the following method, which is called when the userright-clicks a connection and chooses “Clear all connections”.

public

virtual

void

ClearConnectionPools (IConnectionInfo

cxInfo)

{

}

Application Configuration Files

With static data context drivers, a user’s assembly may rely on an application configuration file. You can specify itslocation either by writing it toIConnectionInfo.AppConfigPath, or by overriding the following driver method:

public

virtual

string

GetAppConfigPath (IConnectionInfo

cxInfo)

{

return

cxInfo.AppConfigPath;

}

Customizing the Icon

From version 4.42.10, you can provide a custom

16x16

icon for your data contexts. Just include two files in yourdriver folder:Connection.png

andFailedConnection.png

(the latter is applied when a connection is in error).

These are fed into a 16x16 ImageList and are upscaled in high-DPI scenarios.

Custom Features

The follow methods currently do nothing. They are to support specialized options in the

future without breakingdriver compatibility:

public

object

InvokeCustomOption (string

optionName,params

object

[] data)

{

return

null;

}

public

virtual

object

OnCustomEvent (string

eventName,params

object

[] data)

{

return

null;

}

CustomizingOutput

LINQPad’s output window works bywalking

object graphs that you Dump, emitting

XHTML which it thendisplays in an embedded web browser.

This is the normal “Rich Text” output mode (LINQPad also lets you displayresults to data grids; we cover this later).

There are two ways to control output formatting. The first is to overridePreprocessObjectToWrite:

the idea hereis that you simply replace the object in question with another onethat has the members that you want to render

(orelse, simply,

the desiredHTML).

OverridingPreprocessObjectToWrite

is conceptually simple, but creating a proxy with the right members isawkward if the members need to be chosen at runtime. So, instead of

doing this, you canoverrideGetCustomDisplayMemberProvider

and implementICustomMemberProvider.

This lets you return an array ofvalues to display, along with their

names and types.

OverridingPreprocessObjectToWrite

///

<summary>This lets you replaceany non-primitively-typed object with another object for

///

display. The replacement object can optionally implement ICustomMemberProvider for further

///

control of output formatting.</summary>

public

virtual

void

PreprocessObjectToWrite (ref

object

objectToWrite,ObjectGraphInfo

info) { }

LINQPad callsPreprocessObjectToWrite

before writing all non-primitive types, including enumerables and otherobjects. You can replaceobjectToWrite

with anything you like; it can be an object specially designed for outputformatting (effectively a proxy).

By calling Util.RawHtml, you can even output HTML directly:

if

(objectToWriteis

MySpecialEntity) objectToWrite =Util.RawHtml ("<h1>foo</h1>");

(In the following section, there’s a more elaborate example on how to detectentities andentity collections.)

To “swallow” the object entirely so that nothing is written, settingobjectToWrite

tonull

might seem reasonable,but it won’t work

because ‘null’ will be then written in green. Instead, do this:

objectToWrite = info.DisplayNothingToken;

An example of when you might do this is if writing a driver for Reactive Framework. When a user dumps anIObservable<T>, you’d want to subscribe to the observable and have your subscription methods Dump outputrather than emitting output there and then.

Implementing ICustomMemberProvider

Another way to control output formatting is tooverride the following driver method:

///

<summary>Allows you to change how types are displayed in the output window-

method is an alternative to implementing ICustomMemberProvider in the target types. See

///

http://www.linqpad.net/FAQ.aspx#extensibility for more info.</summary>

public

virtual

ICustomMemberProvider

GetCustomDisplayMemberProvider (object

objectToWrite)

{

return

null;

}

IfobjectToWrite

is not an entity whose output you want to customize, return null.

Otherwise, return an object thatimplements

ICustomMemberProvider:

public interface ICustomMemberProvider

{

// Each of these methods must return a sequence

// with the same number of elements:

IEnumerable<string> GetNames();

IEnumerable<Type> GetTypes();

IEnumerable<object> GetValues();

}

(As an alternative to

overridingGetCustomDisplayMemberProvider, you canimplementICustomMemberProvider

in your entity type itself; this ensures that your custom output formatting takes effectwhether or not your driver is in use. This can be done without taking a dependency on LINQPad.exe.)

You can identify entities via attributes or by looking for a base type, depending on your ORM. For instance,suppose all entities are based onEntity<T>, and entity collections are of some type which implementsIEnumerable<T>, where T is anentity:

If the user chooses “Results to Data Grids”,the output customizations in the above sections

do not apply. This isbecause LINQPad works differently when rendering grids: unlike with HTML formatting, it does not eagerly walkobject graphs (it doesn’t need to in order to display data in a flat grid). Instead, upon encountering a non-primitiveobject, it displays a hyperlink in the grid and evaluates that object only when the user clicks the link. Hence you can(and will want to) expose all lazily evaluated properties.

You might still want to remove extraneous members from the output, though, or perhaps take over the renderingentirely with your ownUI control.

Both are possible (and easy) by overriding the following driver method:

public

virtual

void

DisplayObjectInGrid (object

objectToDisplay,GridOptions

options)

(This method was introduced in LINQPad 2.40/4.40,which was pushed out as an automatic updatein 2012).

Here’sGridOptions:

public

class

GridOptions

{

public

string

PanelTitle {get;set; }

public

string[] MembersToExclude {get;set; }

}

Here’s how to tell LINQPad to remove the fields/properties named “_context”, “ChangeTracker”

You can swap out the object to render simply by callingbase.DisplayObjectInGrid

with a different object.

You can also tell LINQPad to display your own control in place of its DataGrid, simply by not calling the basemethod at all and instead dumping a WPF or Windows Forms control:

public

override

void

DisplayObjectInGrid (object

objectToDisplay,GridOptions

options)

{

if

(IsEntityOrEntities(objectToDisplay.GetType()))

new

System.Windows.Forms.DataGrid

{ DataSource = objectToDisplay }.Dump (options.PanelTitle);

else

base.DisplayObjectInGrid (objectToDisplay, options);

}

(You can gain more control over how LINQPad displaysa

Windows Forms control or WPF element by using themethods on LINQPad’s staticPanelManager

class instead of dumping the control.)

Troubleshooting

Exception Logging

If your driver throws an exception, LINQPad writes the exception details and stack trace to its log file. The log filesits in%localappdata%\linqpad\logs\

For Windows 7 and Vista, this is normally:

C:\Users\UserName\AppData\Local\LINQPad\logs

Debugging

To debug your driver:



Start LINQPad



From Visual Studio, go toDebug | Attach to Process

and

locate LINQPad.exe



Setdesiredbreakpoints

in your project



Enable break

on exception

inDebug | Exceptions

if desired.

You can insert breakpoints in queries by calling methods onSystem.Diagnostics.Debugger (Launch, Break). Youcan also write queries that use reflection to display information about the current typed data