为 EF 提供动态自定义数据库提供程序
作为分析工具的一部分,我有一个自定义的 ADO.NET 堆栈,它充当标准 ADO.NET 周围的“装饰器”,因此它实际上不执行任何工作 - 它只是传递调用(但带有日志记录等)。除此之外,我还通过自定义连接提供了一个 DbProviderFactory
,它实现了 IServiceProvider
并提供了自定义 DbProviderServices
。
这对于大多数工具都很有效,包括 LINQ-to-SQL - 然而,实体框架并不高兴。
例如 - 假设我有:
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
的内部(设置一个我无权了解或滥用的私有只读
字段):
StoreItemCollection itemCollection =
(StoreItemCollection)workspace.GetItemCollection(DataSpace.SSpace);
itemCollection.GetType().GetField("_providerFactory",
BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(itemCollection, factory);
完成此操作后,SSpace
将使用正确的工厂。然而,这显然是令人厌恶的。
那么:我在这里错过了一个技巧吗?如何以不太严厉的措施拦截 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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
应该可以使用 EFProviderWrappers 中的方法。我知道你提到过你尝试过,但我自己也成功地克服了这种事情。
在上面的代码示例中,您无法将标准 MetadataWorkspace 传递给 EntityConnection 构造函数,因为 MetadataWorkspace 仍将指向 SSDL 部分中的原始 DbProviderFactory(在您的情况下为 SqlClient)。我知道您不想直接修改 EDMX/SSDL(我也不想),但 EFProviderWrappers 工具包有一些可能对您有用的辅助方法。特别有用的是 EntityConnectionWrapperUtils.cs 类。它将获取您的原始 EDMX(甚至可以从嵌入式资源中提取它)并更新 XML,以便它指向您的自定义 DbProviderFactory。这一切都是在运行时完成的,因此您无需进行任何更改。您需要为您的 DbProviderFactory 提供一个 Invariant 名称并注册它 - 如果您还没有这样做的话。
然后,您可以将自定义 MetadataWorkspace 与自定义 DbConnection 一起传递给 EntityConnection 构造函数,然后 EF 在需要创建 DbCommand 时应调用您的工厂。
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.