Предоставление собственного провайдера БД на лету для EF - PullRequest
9 голосов
/ 07 июня 2011

Как часть инструмента профилирования, у меня есть собственный стек 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».

, что при назначении соединения команде; по сути, он по умолчанию провайдер sql-сервера для 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);

С этим, правильный завод использует SSpace. Однако это явно противно.

Итак: я здесь пропускаю трюк? Как я могу перехватить поставщика EF менее решительными мерами?

1 Ответ

5 голосов
/ 13 июля 2011

Должно быть возможно использовать подход из EFProviderWrappers . Я знаю, вы упомянули, что пытались это сделать, но я сам успешно справлялся с подобными вещами.

В приведенном выше примере кода вы не можете передать стандартное MetadataWorkspace в конструктор EntityConnection, поскольку MetadataWorkspace по-прежнему будет указывать на исходный DbProviderFactory (в вашем случае SqlClient) в разделе SSDL. Я знаю, что вы не хотите вносить изменения непосредственно в EDMX / SSDL (как и я), но у инструментария EFProviderWrappers есть некоторые вспомогательные методы, которые могут быть вам полезны. Особенно полезным является класс EntityConnectionWrapperUtils.cs . Он возьмет ваш оригинальный EDMX (он может даже извлечь его из встроенного ресурса) и обновит XML, чтобы он указывал на ваш пользовательский DbProviderFactory. И все это происходит во время выполнения, поэтому вам не нужно вносить никаких изменений. Вам нужно придумать имя инварианта для вашего DbProviderFactory и зарегистрировать его - если вы этого еще не сделали.

Затем вы можете передать настраиваемое MetadataWorkspace вместе с вашим настраиваемым DbConnection в конструктор EntityConnection, и тогда EF должен вызвать вашу фабрику, когда ей потребуется создать DbCommand.

...