#.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 — это решение 🙂