Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Providing an on-the fly custom db provider for EF

As part of a profiling tool, I have a custom ADO.NET stack that acts as a "decorator" around standard ADO.NET, so it doesn't actually do any work - it just passes on the calls (but with logging, etc). Among other things, I have provided a DbProviderFactory from my custom connection that implements IServiceProvider and supplies a custom DbProviderServices.

This works great for most tools, including LINQ-to-SQL - however, Entity Framework is not happy.

For example - say I have:

MetadataWorkspace workspace = new MetadataWorkspace(
     new string[] { "res://*/" }, 
     new Assembly[] { Assembly.GetExecutingAssembly() });
using(var conn = /* my custom wrapped DbConnection */)
{
    var provider = DbProviderServices
          .GetProviderServices(conn); // returns my custom DbProviderServices
    var factory = DbProviderServices
          .GetProviderFactory(conn); // returns my custom DbProviderFactory
    ...

so far so good - the above two lines work; the correct (custom) provider info is returned.

Now we can add an EF model:

    using (var ec = new EntityConnection(workspace,conn))
    using (var model = new Entities(ec))
    {
        count = model.Users.Count(); // BOOM!
    }

fails with exception:

Unable to cast object of type '(my custom connection)' to type 'System.Data.SqlClient.SqlConnection'.

which is during assignment of a connection to a command; essentially, it has defaulted to the sql-server provider for the SSpace, and generated a naked SqlCommand. It is then trying to assign conn to the generated command, which can't work (it will work correctly if all the decorators are in place, and a decorated DbCommand was used instead).

Now, the whole point of wrapping this on the fly means I don't really want to have to change the EDMX to register a separate factory. I just want it to know about my lies, damned lies and decorators.

The following works, by hacking into the guts of SSpace (setting a private readonly field that I have no rights to know about or abuse):

    StoreItemCollection itemCollection =
        (StoreItemCollection)workspace.GetItemCollection(DataSpace.SSpace);
    itemCollection.GetType().GetField("_providerFactory",
        BindingFlags.NonPublic | BindingFlags.Instance)
        .SetValue(itemCollection, factory);

With this in place, the correct factory is used by SSpace. However, this is clearly nasty upon nasty.

So: am I missing a trick here? How can I intercept the EF provider with less drastic measures?

like image 992
Marc Gravell Avatar asked Jun 06 '11 21:06

Marc Gravell


1 Answers

It should be possible to use the approach from the EFProviderWrappers. I know you mentioned you tried it but I just fought through this kind of thing myself with success.

In your code sample above, you can't pass a standard MetadataWorkspace to the EntityConnection constructor because the MetadataWorkspace will still point to the original DbProviderFactory (in your case SqlClient) in the SSDL section. I know you don't want to be modifying your EDMX/SSDL directly (neither did I) but the EFProviderWrappers toolkit has some helper methods that might be of use to you. Especially helpful is the EntityConnectionWrapperUtils.cs class. It will take your original EDMX (it can even extract it from an embedded resource) and update the XML so it points to your custom DbProviderFactory. And it does this all at runtime so you don't need to make any changes. You'll need to come up with an Invariant name for your DbProviderFactory and register it - if you haven't done that already.

Then you can pass the custom MetadataWorkspace along with your custom DbConnection to the EntityConnection constructor and EF should then call to your factory when it needs to make a DbCommand.

like image 177
Stephen McDaniel Avatar answered Oct 21 '22 20:10

Stephen McDaniel