Last month, the ADO.NET team hosted a number of ADO.NET Data Provider writers on campus to present information on how to enhance an existing provider to support the Entity Framework features in the upcoming Orcas release.  To help provider writers who were unable to attend, we’re publishing the material we presented to our team blog.

 

In this blog post, you’ll find information about how to extend an existing provider.  The blog also has a .zip attachment that contains the slide decks presented at the event, a help file that covers the CommandTree class, and the source code for a sample ADO.NET Data Provider.

 

The sample provider extends the SqlClient provider that’s included with version 2.0 of the .NET Framework.  It follows the steps listed in the blog post and includes code to handle SQL generation, commonly referred to as SQLGen.  The SQLGen code uses the standard visitor pattern to generate a SqlCommand’s CommandText and Parameters collection based on the CommandTree supplied.

 

Provider writers can use the code in the provider to enhance and extend their existing providers.  The sample provider targets Microsoft SQL Server 2005 and Microsoft SQL Express and contains the SQLGen logic similar to the logic inside of the version of SqlClient that will ship with Orcas.  Provider writers can run Entity Framework scenarios with the sample provider, setting breakpoints and stepping into code to better understand the SQLGen logic – including what CommandTree the Entity Framework generated, how SqlClient converts the CommandTree into a SqlCommand.

 

This version of the sample provider is designed for the March CTP of Orcas.  The ADO.NET team plans to update the sample provider for subsequent public releases of Orcas.  Please note that the sample provider is not supported.

 

If you’re a provider writer working on enhancing your provider for Orcas and you’re not already in touch with the ADO.NET team, please let me know.

 

David Sceppa

ADO.NET Program Manager

David.Sceppa@microsoft.com

 

_________________________________________________________________________________________ 

 

 

This specification describes how to extend an existing ADO.NET Data Provider to support the Entity Framework in ADO.NET Orcas.  Once a provider writer has extended their existing provider to support the Entity Framework, all Entity Framework scenarios will be supported using that provider (assuming the SQL Gen logic in the provider supports generating the appropriate store-specific queries) including generation of mapping files and classes via EdmGen.Exe.

 

1       Supporting instantiation via DbProviderFactories.GetFactory

Note:  This section refers ADO.NET 2.0 features that the Entity Framework relies upon.

 

The primary Entity Framework scenarios involve supplying the provider's name as part of the connection string.

 

string providerConnectionString = @"Data Source=.\SQLExpress;Initial Catalog=Northwind;" +

                                   "Integrated Security=True";

string entityConnectionString =

       string.Format(@"Metadata=.\NorthwindModel.csdl|.\NorthwindModel.ssdl|.\NorthwindModel.msl;" +

                      "Provider=System.Data.SqlClient;" +

                      "Provider Connection String=\"{0}\"", providerConnectionString);

 

using (EntityConnection connection = new EntityConnection(entityConnectionString))

{

 

}

 

 

The Entity Framework parses the connection string and extracts the provider's name and the connection string for the underlying provider, then instantiates the database connection using code like:

 

DbProviderFactory factory = DbProviderFactories.GetFactory(providerName);

DbConnection connection = factory.CreateConnection();

connection.ConnectionString = providerConnectionString;

 

 

This means that for a provider to plug into the Entity Framework, its factory class must be accessible via DbProviderFactories.GetFactory.  Doing so has three main requirements:

1.1.1     The provider is listed in the combined configuration file

The provider may be listed in the machine configuration file, the application configuration file, etc.  The provider's configuration entry should be under configuration / system.data / DbProviderFactories and typically looks like this.

 

<add name="SqlClient Data Provider"

     invariant="System.Data.SqlClient"

     description=".Net Framework Data Provider for SqlServer"

     type="System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0,

           Culture=neutral, PublicKeyToken=b77a5c561934e089" />

 

Note: The simple way to generate the value for the type attribute is to store typeof(ProviderFactory).AssemblyQualifiedName.

1.1.2     The provider's factory class is accessible via Type.GetType()

Calling DbProviderFactories.GetFactory also requires that provider factory's assembly qualified name can be used to access the provider factory class type via Type.GetType().  The two main ways to ensure that your provider can be instantiated in this fashion are to either register the assembly in the GAC (using regasm.exe) or to include the assembly in the application's directory.

1.1.3     The provider's ProviderFactory class has a public static Instance field

Each provider included with the .NET Framework exposes a public static Instance field on the provider factory class.  ADO.NET relies on this pattern in the call to DbProviderFactories.GetFactory.  If the desired provider does not follow this pattern, the call to GetFactory will throw an InvalidOperationException with the following message:

 

The requested .Net Framework Data Provider's implementation does not have an Instance field of a System.Data.Common.DbProviderFactory derived type.

 

2       Implementing ICloneable on the Command class

As part of the SQL Gen layer, described later, the Entity Framework requires that the underlying provider is able to generate a DbCommand given a CommandTree.  The Entity Framework may execute the query specified in the DbCommand multiple times.  To ensure that executing the query does not side effect the Command, the Entity Framework requires that the Command objects that a provider generates implements ICloneable and clones the Command and only uses the Execute methods of the cloned Commands.

 

Here is some sample code for cloning a Command object.  The code assumes the SampleCommand already implements the ADO.NET 2.0 features and that the SampleParameter class follows a similar pattern for cloning.

 

public partial class SampleCommand : ICloneable

{

    object ICloneable.Clone()

    {

        SampleCommand clone = new SampleCommand();

        clone.Connection = this.Connection;

        clone.CommandText = this.CommandText;

        clone.CommandType = this.CommandType;

        clone.CommandTimeout = this.CommandTimeout;

        clone.DesignTimeVisible = this.DesignTimeVisible;

        clone.Transaction = this.Transaction;

        clone.UpdatedRowSource = this.UpdatedRowSource;

 

        foreach (SampleParameter p in this.Parameters)

            clone.Parameters.Add(((ICloneable) p).Clone());

 

        return clone;

    }

}

 

 

 

3       Overriding DbConnection.DbProviderFactory

There are many areas where the Entity Framework's API requires only a Connection object.  However, the Entity Framework needs to access the ProviderFactory class for the provider given only an instance of its Connection class.

 

As of ADO.NET Orcas, there is no public way to access a ProviderFactory given a Connection.  In ADO.NET Orcas, the DbConnection class has a new private ProviderFactory property, which calls a protected DbProviderFactory property.  This is the same pattern established in ADO.NET 2.0 for properties like DbCommand.Connection.  Note: The ProviderFactory property will likely be made public in a subsequent version of ADO.NET.

 

Once you've overridden the DbProviderFactory property, the Entity Framework will be able to access your provider's ProviderFactory class given an instance of the Connection class. 

 

public partial class SampleConnection

{

    protected override DbProviderFactory DbProviderFactory

    {

        get { return SampleFactory.Instance; }

    }

}

 

 

 

4      Implementing IServiceProvider on the ProviderFactory class

The Entity Framework relies on a new class called DbProviderServices, which will be discussed later, as a starting point for retrieving database metadata, type information, SQL Generation, etc.  The Entity Framework relies on the IServiceProvider pattern for accessing the DbProviderServices class given ProviderFactory, using code like:

 

DbProviderServices providerServices = null;

if (providerFactory as IServiceProvider != null)

{

    IServiceProvider iServiceProvider = (IServiceProvider) providerFactory;

    providerServices = (DbProviderServices) iServiceProvider.GetService(typeof(DbProviderServices);

}

 

In order for this code to succeed, you must implement IServiceProvider for your ProviderFactory, and return an instance of your provider's DbProviderServices class on calls to GetService that request DbProviderServices using code like:

 

public partial class SampleFactory : IServiceProvider

{

    object IServiceProvider.GetService(Type serviceType)

    {

        if (serviceType == typeof(DbProviderServices))

            return new SampleProviderServices();

        else

            return null;

    }

}

 

For more information on IServiceProvider, see the MSDN documentation at:

http://msdn2.microsoft.com/en-us/library/system.iserviceprovider.getservice.aspx

 

 

 

5      Implementing DbProviderServices

The DbProviderServices class is the starting point for surfacing provider-specific types, metadata and functions (also known as the provider manifest), a DbCommand given a CommandTree (also known as SQL Generation), and mapping information required by the Entity Framework tools to generate classes and mapping files based on the tables and columns available.

 

The DbProviderServices class is used within the Entity Framework and is not expected to be accessed directly by users.  There is no requirement to make the class public in ADO.NET Orcas.

 

internal class SampleProviderServices : DbProviderServices

{

 

}

5.1     Implementing GetDbInformation

The Entity Framework relies on the DbProviderServices class to access metadata about the underlying provider in order to understand the various data types and functions available through the provider.  This information is commonly referred to as the provider manifest.

 

The Entity Framework tools also rely on the DbProviderServices class to access a store schema definition and a store schema mapping that describes how to access the schema information for the data store.  The tools then use this mapping information to retrieve the store schema information to generate mapping files and class files for applications.

 

You can surface this information via the protected GetDbInformation method using code like the following.  In this example, we expect that the XML information has been compiled into the provider as resources.

 

protected override XmlReader GetDbInformation(string informationType, DbConnection connection)

{

    if (informationType == DbProviderServices.ProviderManifest)

    {

        if (connection == null)

            throw new ArgumentNullException("Expected non-null connection on call to GetDbInformation");

        if (connection.GetType() != typeof(SampleConnection))

            throw new ArgumentException(string.Format("Wrong connection type.  Expecting SampleConnection, received {0}",

                                                      connection));

 

        return this.GetXmlResource("OrcasSampleProvider.Resources.SampleProviderServices.ProviderManifest.xml");

    }

    else if (informationType == DbProviderServices.StoreSchemaDefinition)

    {

        return this.GetXmlResource("OrcasSampleProvider.Resources.SampleProviderServices.StoreSchemaDefinition.ssdl");

    }

    else if (informationType == DbProviderServices.StoreSchemaMapping)

    {

        return this.GetXmlResource("OrcasSampleProvider.Resources.SampleProviderServices.StoreSchemaMapping.msl");

    }

 

    throw new NotSupportedException(string.Format("SampleProviderServices does not support informationType of {0}",

                                                  informationType));

}

 

private XmlReader GetXmlResource(string resourceName)

{

    Assembly executingAssembly = Assembly.GetExecutingAssembly();

    Stream stream = executingAssembly.GetManifestResourceStream(resourceName);

    return XmlReader.Create(stream);

}

 

Note:  This portion of the ADO.NET API is still under development and does not yet appear in publicly available builds.  Earlier builds exposed the provider manifest using the following code.

 

[Obsolete("Please use GetInformation(DbProviderServices.ProviderManifest, connection) instead")]

public override XmlReader GetProviderManifest(DbConnection connection)

{

    if (connection == null)

        throw new ArgumentNullException("connection");

    else

        return this.GetXmlResource("OrcasSampleProvider.Resources.SampleProviderServices.ProviderManifest.xml");

}

 

5.2     Surfacing SQL Generation

As noted earlier, the DbProviderServices class is also the starting point for transforming a CommandTree into a store-specific query (DbCommand).  The provider is responsible for generating both the CommandText and the Parameters for the Command.

 

Once the provider has implemented and surfaced this transformation logic, often called SQL Generation, the Entity Framework is able to execute queries generated via the EntityCommand, ObjectQuery<T> etc.

 

You can use code like the following to surface your SQL Generation layer via DbProviderServices.

 

public override DbCommandDefinition CreateCommandDefinition(DbCommand prototype)

{

    return base.CreateCommandDefinition(prototype);

}

 

public override DbCommandDefinition CreateCommandDefinition(DbConnection connection, CommandTree commandTree)

{

    DbCommand prototype = CreateCommand(connection, commandTree);

    DbCommandDefinition result = this.CreateCommandDefinition(prototype);

    return result;

}

 

internal DbCommand CreateCommand(DbConnection connection, CommandTree commandTree)

{

    //SQL Generation logic goes here!

    throw new NotImpelementedException("SQL Generation logic not yet supplied!");

}

 

Note:  Generating a DbCommand based on a CommandTree is non-trivial.  Use the SQL Generation logic that's part of the sample provider in the attachment as an example.

 

 

6      Transactions

The Entity Framework supports both local (DbTransaction) and distributed (System.Transaction) transactions.  For example, the work done within a call to ObjectContext.SaveChanges is implicitly wrapped in a DbTransaction unless a transaction (DbTransaction or System.Transaction) is already active.  In each case, the Entity Framework uses the transactional functionality of the underlying provider. 

 

6.1     DbTransaction

As noted above, the Entity Framework implicitly wraps submitting changes in transactions.  Therefore, providers must support DbTransactions.

 

6.2     System.Transactions

Using the Entity Framework with System.Transactions requires that the underlying provider also supports System.Transactions by supporting the EnlistTransaction method on the Connection class.