作为分析工具的一部分,我有一个自定义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提供商?
答案 0 :(得分:5)
应该可以使用EFProviderWrappers中的方法。我知道你提到过你曾经尝试过,但我只是成功地通过这种方式进行了斗争。
在上面的代码示例中,您无法将标准MetadataWorkspace传递给EntityConnection构造函数,因为MetadataWorkspace仍将指向SSDL部分中的原始DbProviderFactory(在您的情况下为SqlClient)。我知道你不想直接修改你的EDMX / SSDL(我也没有)但是EFProviderWrappers工具包有一些可能对你有用的辅助方法。特别有用的是EntityConnectionWrapperUtils.cs课程。它将采用您的原始EDMX(它甚至可以从嵌入式资源中提取它)并更新XML,使其指向您的自定义DbProviderFactory。它在运行时完成所有操作,因此您无需进行任何更改。你需要为你的DbProviderFactory提出一个Invariant名称并注册它 - 如果你还没有这样做的话。
然后,您可以将自定义MetadataWorkspace与自定义DbConnection一起传递给EntityConnection构造函数,然后EF应该在需要创建DbCommand时调用您的工厂。