Почему одна функция, запущенная в SQL Server CLR, может вызвать сбой, хотя в автономном приложении она работает нормально?

Эти два метода ниже похожи, за исключением того, что один обрабатывает нулевые значения, а другой — нет. Для обработки нулевых значений он использует тип SqlString и проверяет свойство get_IsNull.

Почему первый может вызывать ошибку "A .NET Framework error occurred during execution of user-defined routine or aggregate "CheckMailingAddress": ." при запуске внутри SQL CLR, а второй - нет?

В частности, ошибка TSQL "Msg 10329, Level 16, State 49, Line 1 .Net Framework execution was aborted."

.method public hidebysig static bool CheckMailingAddress(valuetype [System.Data]System.Data.SqlTypes.SqlString param0) cil managed
{
   .maxstack 8
    L_0000: ldarga.s param0
    L_0002: nop 
    L_0003: nop 
    L_0004: call instance bool [System.Data]System.Data.SqlTypes.SqlString::get_IsNull()
    L_0009: brfalse L_0010
    L_000e: ldc.i4.1 
    L_000f: ret 
    L_0010: ldarga.s param0
    L_0012: nop 
    L_0013: nop 
    L_0014: call instance string [System.Data]System.Data.SqlTypes.SqlString::get_Value()
    L_0019: call class DatabaseValues.MailingAddress DatabaseValues.MailingAddress::op_Explicit(string)
    L_001e: pop 
    L_001f: ldc.i4.1 
    L_0020: ret 
}

.method public hidebysig static bool CheckMailingAddress(string param0) cil managed
{
   .maxstack 8
    L_0000: ldarg.0 
    L_0001: call class DatabaseValues.CheckMailingAddress DatabaseValues.CheckMailingAddress::op_Explicit(string)
    L_0006: pop 
    L_0007: ldc.i4.1 
    L_0008: ret 
}

Имейте в виду, насколько мне известно, MSIL верен, потому что оба эти метода работают при вызове в отдельном приложении. Только при вызове внутри SQL CLR происходит сбой первого из двух. В SQL CLR функция определена с типом "nvarchar(4000)", который, насколько я знаю, должен хорошо работать с SqlString.

Вероятно, я мог бы также реализовать первый метод, используя «строку», и по-прежнему выполнять проверку на нулевое значение, но он использует SqlString, чтобы воспользоваться преимуществами свойств интерфейса INullable «IsNull» и «Value», поскольку он является частью общего генератора кода, где другие Можно использовать типы Sql*.

РЕЗЮМЕ ПРОСТОЙ ПРОБЛЕМЫ:

Для тех, кого отвлекает MSIL в теле метода; игнорируй это. Я перекомпилировал функции, чтобы они вообще ничего не делали, и моя точка зрения заключается в том, что когда "SqlString", а не "string", является типом ввода, CLR взрывается и завершается без сообщения об ошибке, а возвращаемое значение равно NULL, а не ПРАВДА.

//Crashes when input parameter is "SqlString"
.method public hidebysig static bool CheckMailingAddress(valuetype [System.Data]System.Data.SqlTypes.SqlString param0) cil managed
{
   .maxstack 8
    L_001f: ldc.i4.1 
    L_0020: ret 
}

//Doesn't Crash when input parameter is "string"
.method public hidebysig static bool CheckMailingAddress(string param0) cil managed
{
   .maxstack 8 
    L_0007: ldc.i4.1 
    L_0008: ret 
}

person Triynko    schedule 03.05.2011    source источник
comment
Вы пытались подключить к этому отладчик Visual Studio? Кроме того, не могли бы вы включить код на исходном языке (например, C#, VB и т. д.), а не на языке MSIL?   -  person Ryan    schedule 04.05.2011
comment
MSIL — это исходный язык. Да, я подключил отладчик. Он терпит неудачу без какого-либо конкретного сообщения об ошибке, кроме того, что я дал, и он не достигает какой-либо точки останова внутри CLR.   -  person Triynko    schedule 04.05.2011
comment
Вау, чувак, ты пишешь процедуры CLR в MSIL? Могу ли я спросить, почему? Вы пробовали взглянуть на то, что некоторые скомпилированные C # делают то же самое?   -  person Matt Whitfield    schedule 04.05.2011
comment
Ага! Унифицированные классы данных для приложений и базы данных. Единая точка обслуживания + быстрые функции ограничения проверки SQL CLR.   -  person Triynko    schedule 04.05.2011
comment
В частности, мои классы данных имеют настраиваемые теги атрибутов, которые указывают (TableName,FieldName,AllowNull), указывая поля, к которым применяется проверка. Наконец, мой проект DeployDatabaseAssembly отражает все пространство имен класса данных, считывает эти атрибуты, генерирует чрезвычайно эффективные функции, которые вы видите в MSIL выше, для каждого класса данных, объединяет их в сборку класса данных с помощью ILMerge, развертывает сборку на сервере SQL, создает обертки функций SQL CLR для них и, наконец, связывает их вместе, по одному логическому вызову на поле, в ограничение TSQL для каждой таблицы:)   -  person Triynko    schedule 04.05.2011
comment
Итак, предположим, что MailingAddress (подкласс моего класса RegexConstrainedString) применяется к трем разным полям в трех разных таблицах. У него будет три экземпляра моего пользовательского SqlFunctionCheck, которые определяют каждую из этих таблиц/полей. Кроме того, если для любого из этих полей параметр AllowNull имеет значение true, проект DeployDatabaseAssembly создаст функцию CheckMailingAddress с MSIL, которая проверяет значение null перед попыткой явного приведения к MailingAddress (что приведет к ошибке). Если нулевые значения не требуются, создается более простой MSIL.   -  person Triynko    schedule 04.05.2011
comment
Проблема здесь в том, что с null-safe MSIL тип SqlString используется вместо строки, так что можно использовать методы интерфейса INullable, что необходимо, потому что проект DeployDatabaseAssembly может генерировать функции и для числовых типов, в которых В этом случае он будет использовать SqlInt32, SqlBoolean и т. д., которые реализуют интерфейс INullable... поэтому код использует его как шаблон для безопасного от нуля MSIL (т.е. для любого типа Sql* он вызывает INullable get_IsNull и get_Value для проверки наличия null и получить фактическое значение, если оно продолжает проверять значение с помощью явного приведения.   -  person Triynko    schedule 04.05.2011
comment
Честно говоря, я не думаю, что с кодом MSIL вообще что-то не так. Я думаю, что, возможно, недавнее исправление для SQL Server внесло ошибку, которая вызывает проблемы с упорядочиванием данных в функции с типом SqlString, потому что эти методы отлично работают вне SQL Server, и раньше они прекрасно работали внутри SQL Server. Интерфейсы функций TSQL для них всегда использовали nvarchar(4000) в качестве типа параметра, и единственное, что теперь отличается в моей базе данных, это то, что мои поля также имеют тип nvarchar(4000), тогда как раньше они были varchar(3000).   -  person Triynko    schedule 04.05.2011
comment
Унифицированные классы данных для приложений и базы данных — что сказать? Не могли бы вы объяснить, почему это невозможно в C#?   -  person Mitch Wheat    schedule 04.05.2011
comment
@Triyko: это классический пример трудного пути!   -  person Mitch Wheat    schedule 04.05.2011
comment
@ Митч. Единственный способ не трудный путь. Чтобы использовать одно регулярное выражение C# для ограничения типа данных как в приложении, так и в базе данных, нужно использовать SQL CLR. Единственный способ предотвратить попадание неверных данных в поле базы данных — установить активное ограничение проверки для таблицы, которая проверяет поле. Единственный способ использовать существующее регулярное выражение C# — заставить проверочное ограничение вызывать функцию CLR, включающую регулярное выражение. Я просто автоматизировал развертывание, поэтому мне не нужно поддерживать ничего, кроме этого единственного определения в файле C#. Это работает уже БОЛЕЕ ГОДА; теперь это не удается.   -  person Triynko    schedule 04.05.2011
comment
Используя ограничение Regex на основе C#/CLR в базе данных, люди не могут даже вводить недопустимые данные с помощью SSMS, и в этом суть. Я обеспечил целостность данных в точке входа в базу данных и использую безопасные для типов классы данных во всем своем приложении, поэтому данные, которые оно обрабатывает, всегда действительны. И это делается путем поддержки одного класса данных C# и нажатия кнопки развертывания при внесении изменений. В TSQL нет копирования и вставки кода, регулярных выражений или перезаписи ограничений в соответствии с C#. Он унифицирован и автоматизирован и (привык и в основном работает до сих пор) прекрасно работает.   -  person Triynko    schedule 04.05.2011
comment
Я просто думаю, что по какой-то причине у SQL CLR есть проблема с маршаллингом данных в тип SqlString. Ты увидишь. Я буду следить за отчетом об ошибке.   -  person Triynko    schedule 04.05.2011
comment
Кроме того, почему он не написан на C#... на самом деле генератор написан на C# и использует вызовы AssemblyBuilder.Emit для генерации опкодов MSIL. Таким образом, я могу использовать классы Reflection, такие как MethodInfo, и передавать фактические ссылки на Type для создания этих простых функций CLR. Альтернативой может быть объединение строк имени типа в код C# и его динамическая компиляция, что является излишним для запуска синтаксических анализаторов и валидаторов для чего-то настолько простого, что я могу просто генерировать OpCodes напрямую и чисто.   -  person Triynko    schedule 04.05.2011
comment
@Triyko: если это кажется слишком сложным, обычно так оно и есть.   -  person Mitch Wheat    schedule 04.05.2011
comment
@ Митч. Это совсем не сложно, это ваше мнение. Это очень просто для меня. Проработал больше года. Вы, ребята, отвлекаетесь на MSIL; игнорировать это. MSIL на самом деле не имеет значения, и я разместил его только в качестве справочного материала для тех, кто хорошо разбирается в SQL CLR. Я хотел сказать, что он падает ТОЛЬКО потому, что SqlString является типом входного параметра. Даже если все, что делает метод, это возвращает true, он дает сбой, когда тип ввода SqlString, но не когда это строка, и он не должен этого делать.   -  person Triynko    schedule 04.05.2011
comment
Возможно, чьи-то слова понятны. См. PDF-файл здесь: mediafire.com/?g1j6db15cq7lgu3. Прокрутите страницу до третьей, чтобы прочитать Код упаковки для продвижения кросса -Уровень повторного использования. Это в основном то, что я делаю, за исключением того, что я автоматизировал процесс, поэтому мне не нужно вручную писать оболочки, использующие типы Sql*, и пересылать их в мои классы данных для проверки.   -  person Triynko    schedule 04.05.2011
comment
@Triyko: ты продолжаешь в это верить! смешной :)   -  person Mitch Wheat    schedule 04.05.2011
comment
@ Митч: Это легко. Я надеялся, что смогу убедить вас, но, как и все остальное, это кажется сложным только тогда, когда вы этого не понимаете. Все, что я сделал, это создал небольшое приложение для автоматического создания такой функции: bool CheckMailingAddress(string field_from_db) {(MailingAddress)field_from_db;return true;}, развернул сборку, в которую он упакован, и создал функцию в TSQL. Оболочка CheckMailingAddress вызывается из ограничений базы данных, которые выдают ошибку при сбое приведения к MailingAddress и возвращают true в противном случае. Это просто и быстро.   -  person Triynko    schedule 04.05.2011
comment
Он способен применить регулярное выражение .NET к 1,5 миллионам строк данных nvarchar(4000) менее чем за 15 секунд, и это намного быстрее, чем что-либо, написанное на TSQL, для выполнения той же проверки в проверочном ограничении.   -  person Triynko    schedule 04.05.2011
comment
Ограничения проверки TSQL (также автоматически сгенерированные и развернутые) выглядят следующим образом: ([dbo].[CheckEmailAddress]([Email])=(1) AND [dbo].[CheckMailingAddress]([Address])=(1) AND [dbo].[CheckPhoneNumber]([Phone])=(1)), и это невероятно быстро.   -  person Triynko    schedule 04.05.2011
comment
Починил это! В какой-то момент я переключил свой DeployDatabaseAssembly на целевой .NET 4.0. Я переключил его обратно на .NET 3.5, и SqlString больше не доставляет мне проблем! Итак, теперь я пытаюсь выяснить, могу ли я переключить проект обратно на версию 4.0 для процесса генерации кода, но сгенерировать DLL, ориентированную на сборки .NET 3.5. Возможно, я могу получить ссылки на типы иначе, чем typeof(SqlString), например Assembly.LoadFrom(v3.5\System.Data.dll).GetType(System.Data.SqlTypes.SqlString), или установить некоторые параметры в AssemblyBuilder или вызвать DefineDynamicAssembly для AppDomain создан для .NET 3.5.   -  person Triynko    schedule 04.05.2011


Ответы (1)


Я нашел источник проблемы и смог ее решить, но не уверен в деталях.

В какой-то момент я переключил свой проект DeployDatabaseAssembly на .NET 4.0, а AssemblyBuilder, должно быть, сгенерировал сборку, предназначенную и для .NET 4.0. Переключение проекта на целевой .NET 3.5 устранило проблему.

Забавно то, что исходная DLL (database.dll), которая содержит все мои типы данных, по-прежнему нацелена на .NET 3.5 и была оставлена ​​такой намеренно, потому что я знал, что SQL Server прямо сейчас поддерживает только CLR 2.0, что фактически делает его несовместимым с .NET 4.0. , потому что для .NET 4.0 требуется CLR 4.0. Используя ILMerge, я комбинировал динамическую сборку, содержащую сгенерированные функции, с моей существующей (.NET 3.5) database.dll. В конечном итоге это привело к гибридной сборке сборки .NET 4.0, которая в основном была основана на функциях и классах .NET 3.5. Странно, что мне удалось заставить работать функции, которые использовали базовые параметры типа String и int, но тип SqlString вызывал сбои... очевидно, потому что он был извлечен из .NET 4.0 System.Data.dll, поскольку он был упоминается как typeof(SqlString) в моем DeployDatabaseAssembly, предназначенном для .NET 4.0. Просто странно, как это зависало без каких-либо сообщений об ошибках или без каких-либо предупреждений о несовместимости с загруженными модулями SQL CLR.

Хотел бы я знать, как заставить AssemblyBuilder работать в приложении .NET 4.0 для создания сборки, ориентированной на .NET 3.5...

Обновление: проблема полностью решена

Я сосредоточился на выводе ILMerge и пошел дальше и переключил DeployDatabaseAssembly обратно на .NET 4.0. Кстати, я использую ILMerge в качестве эталона в своем проекте, так как это сборка .NET.

Установив параметр ILMerge следующим образом:

ILMerge merger = new ILMerge();
merger.SetTargetPlatform( "v2", @"C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client");

Полученная DLL развертывается на SQL Server (как и раньше), но на этот раз она работает без ошибок.

Интересно, что если я заменю только v3.5 в пути к целевой платформе на v4.0 и попытаюсь развернуть сборку на SQL Server, то сразу же во время развертывания я получу полезное сообщение об ошибке CREATE ASSEMBLY для сборки 'имя моей сборки ' не удалось, так как сборка создана для неподдерживаемой версии среды выполнения CLR.. Странно, что когда я вообще не устанавливал целевую платформу, она нормально развертывалась, но вылетала без каких-либо сообщений об ошибках.

В этой таблице приведены приведенные выше комбинации конфигураций и результаты:

person Triynko    schedule 04.05.2011
comment
Меня не удивляет, что проблема оказалась за пределами кода, который вы разместили. Но в качестве примечания, учитывали ли вы тот факт, что кому-то другому, вероятно, придется это поддерживать? Если ваши требования к производительности не заключаются в том, что это должна быть абсолютная скорость убийцы, я бы рекомендовал делать это на C# по той простой причине, что есть больше людей, которые могут читать/писать C#, чем могут читать/писать MSIL. Вы можете оставить довольно большой кусок невероятно сложного для понимания кода следующему разработчику вашей компании. Опять же, если это действительно требование, это цена, которую нужно заплатить. - person Ryan; 05.05.2011
comment
Это должна быть абсолютная убийственная скорость, а выдача MSIL напрямую гарантирует, что в миксе нет компилятора, внедряющего неоптимальный код ни при каких обстоятельствах. На самом деле программа полностью написана на C# и использует класс ILGenerator и его метод Emit для генерации кодов операций MSIL (перечисление System.Reflection.Emit.OpCodes), поэтому на самом деле никогда не приходится иметь дело с синтаксисом MSIL. Опубликованный MSIL был получен путем открытия сгенерированной сборки в .NET Reflector. - person Triynko; 24.05.2011
comment
Что касается внимания к другим разработчикам... понимаете ли вы, что вся цель этой утилиты состоит в том, чтобы облегчить им задачу. Хотя сама утилита сложна, она не предназначена для обновления, потому что это действительно готовый инструмент разработки, и я бы лично поддержал его или выпустил как открытый исходный код. Он включает в себя множество сложных функций, связанных с запросом различных типов функций в SQL Server, созданием этих функций, обработкой сопоставлений из .NET в типы данных SQL, развертыванием сборок, созданием/отключением/включением/проверкой ограничений и т. д. - person Triynko; 24.05.2011
comment
С помощью этой утилиты разработчики могут создать новый класс данных с таким заголовком: общедоступный закрытый класс Имя пользователя: RegexConstrainedString и пометить его атрибутом, подобным этому [SqlFunctionCheck(Table=Users,Field=Username)]. Затем, просто запустив эту утилиту, она автоматически сделает для них следующее: сгенерирует новую оптимизированную сборку, развернет ее в базе данных, заменив старую сборку, создаст функцию CheckUsername SQLCLR, создаст проверочное ограничение для таблицы (сочетая вызовы этой функции и любые другие, помеченные для применения к той же таблице), и перепроверьте все ограничения. - person Triynko; 24.05.2011