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

时间:2011-06-06 21:10:56

标签: .net entity-framework ado.net provider

作为分析工具的一部分,我有一个自定义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
    ...

到目前为止一切顺利 - 以上两条线都有效;返回正确的(自定义)提供商信息。

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

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

失败,但有例外:

  

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

在分配与命令的连接期间;实际上,它已默认为SSpace的sql-server提供程序,并生成了一个裸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);

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

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

1 个答案:

答案 0 :(得分:5)

应该可以使用EFProviderWrappers中的方法。我知道你提到过你曾经尝试过,但我只是成功地通过这种方式进行了斗争。

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

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