为EF提供动态自定义数据库提供程序

Mar*_*ell 9 .net provider ado.net entity-framework

作为分析工具的一部分,我有一个自定义ADO.NET堆栈,它充当标准ADO.NET的"装饰器",因此它实际上不做任何工作 - 它只是传递调用(但是使用日志记录等) ).除此之外,我提供了一个DbProviderFactory来自我的自定义连接,它实现IServiceProvider并提供自定义DbProviderServices.

这适用于大多数工具,包括LINQ-to-SQL - 然而,Entity Framework并不满意.

例如 - 说我有:

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
    ...
Run Code Online (Sandbox Code Playgroud)

到目前为止这么好 - 以上两条线都有效; 返回正确的(自定义)提供程序信息.

现在我们可以添加一个EF模型:

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

失败,例外:

无法将类型为'(我的自定义连接)'的对象强制转换为'System.Data.SqlClient.SqlConnection'.

在分配与命令的连接期间; 本质上,它默认为sql-server提供程序SSpace,并生成一个裸体SqlCommand.然后尝试分配conn给生成的命令,该命令无法工作(如果所有装饰器都已到位,它将正常工作,而使用装饰DbCommand).

现在,将这一点包装起来的重点意味着我真的不想更改EDMX来注册一个单独的工厂.我只是想让它知道我的谎言,该死的谎言和装饰者.

以下作品,通过攻击SSpace(设置private readonly我无权了解或滥用的字段)的内容:

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

有了这个,使用正确的工厂SSpace.然而,这显然令人讨厌.

所以:我在这里错过了一招吗?如何以较少的措施拦截EF提供商?

Ste*_*iel 5

应该可以使用EFProviderWrappers中的方法.我知道你提到过你尝试过但我只是在成功的情况下通过这种方式进行了斗争.

在上面的代码示例中,您无法将标准MetadataWorkspace传递给EntityConnection构造函数,因为MetadataWorkspace仍将指向SSDL部分中的原始DbProviderFactory(在您的情况下为SqlClient).我知道你不想直接修改你的EDMX/SSDL(我也没有)但是EFProviderWrappers工具包有一些可能对你有用的辅助方法.特别有用的是EntityConnectionWrapperUtils.cs类.它将采用您的原始EDMX(它甚至可以从嵌入式资源中提取它)并更新XML,使其指向您的自定义DbProviderFactory.它在运行时完成所有操作,因此您无需进行任何更改.你需要为你的DbProviderFactory提出一个Invariant名称并注册它 - 如果你还没有这样做的话.

然后,您可以将自定义MetadataWorkspace与自定义DbConnection一起传递给EntityConnection构造函数,然后EF应在需要创建DbCommand时调用您的工厂.