Предоставление «на лету» пользовательского поставщика БД для EF

#.net #entity-framework #ado.net #provider

#.net #entity-framework #ado.net #поставщик

Вопрос:

Как часть инструмента профилирования, у меня есть пользовательский 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 и сгенерировал naked 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. Вы подключали поставщика и фабрику к workspace другим способом, верно? Этот код не показан.

2. @Henk это недостающий шаг. Насколько я могу видеть, это делается на основе чтения всех огромных массивов xml, что не подходит для моего сценария. Показан код, который подключает его — это ужасный шаг отражения. Обратите внимание, я упоминал, что перехваты подключения работают, поэтому factory переменная является моей собственной. Если вы знаете, как подключить рабочее пространство к поставщику: дерзайте!

3. Проверьте подход, используемый в EFProviderWrappers

4. @Ladislav — да, если что-то подтверждает мои опасения: для этого требуются либо изменения в типе контекста объекта, либо изменения в SSDL — в значительной степени то, чего я хочу избежать. И которого вышеупомянутый взлом действительно избегает. Просто кажется сумасшедшим, что это не проще сделать через runtime.

5. @Ladislav что глубоко иронично, учитывая, что одной из ключевых особенностей EF по сравнению с LINQ-to-SQL является… расширяемость; p

Ответ №1:

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

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

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

Комментарии:

1. Если бы я мог дать вам плюс 10, я бы это сделал. Ответ на этот вопрос решил проблему, которую я не мог решить в течение года. EFProviderWrappers — это решение 🙂