Использование COMовских dll в SQL CLR

Следующим логичным стремлением было превратить "Расширенные свойства файлов"\Скрипт 1 в хранимую процедуру или функцию. Гордо создав новый SQL Serverный проект ("Табличные CLR-функции для ТЧайников"\Рис.1 - 5), я отправился в References проекта, чтобы добавить %windir%\System32\SHELL32.dll и к своему ужасу ее там не обнаружил. Вместо достойного выбора "Расширенные свойства файлов"\Рис.2 диалог Add References имеет крайне аскетичный вид:

image001

Рис.1

Только особо проверенные сборки имеют привилегию участвовать в SQL Serverных проектах. И это правильно, ибо не фиг кого попало пускать in-process святая святых. Но что если какая-то библиотека не попала в список доверенных лиц не из-за своей вредоносности, а потому что ее не успели проверить? Лично я зуб даю на отсечение, что она безопасна. Вооруженные передовым знанием "Подписание внешней или небезопасной сборки", мы не позволим дурацкому вижуал студийному шаблону наступить на горло нашей песне. Делаем copy той начинки кода, которую нам уже заботливо приготовил шаблон (в принципе, ничего сверхъестественного в нем нет):

image003

рис.2

закрываем нафиг SQL Serverный проект и открываем новый типа Class Library, в который вставляем скопированный код, свободно добавляем ссылку на SHELL32.dll и дополнительно пишем:

using System;

using System.Data;

using System.Data.SqlClient;

using System.Data.SqlTypes;

using Microsoft.SqlServer.Server;

using System.IO;

public partial class StoredProcedures

{

    static Shell32.ShellClass shell = new Shell32.ShellClass();

    static Shell32.Folder folder;

   

    /// <summary>

    /// Процедура выводит список свойств заданного файла, как они значатся в эксплорере

    /// </summary>

    /// <param name="fileFullName">Полное имя файла</param>

    [Microsoft.SqlServer.Server.SqlProcedure]

    public static void FileExtProps(SqlString fileFullName)

    {

        folder = shell.NameSpace(Path.GetDirectoryName(fileFullName.Value));

        //Набор пар имя свойства - его значение будем хранить в таблице

        DataTable dt = new DataTable();

        dt.Columns.Add("ID", typeof(int)); dt.Columns["ID"].AutoIncrement = true;

        dt.Columns.Add("Name", typeof(string));

        dt.Columns.Add("Value", typeof(string));

       

        //Собираем названия свойств, все, сколько ни есть

        for (int i = 0; ; i++)

        {

            string s = folder.GetDetailsOf(null, i);

            if (string.IsNullOrEmpty(s)) break;

            DataRow r = dt.NewRow();

            r["Name"] = s;

  dt.Rows.Add(r);

        }

       

        //Собираем к ним их значения для данного файла

        Shell32.FolderItem fi = folder.Items().Item(Path.GetFileName(fileFullName.Value));

        foreach (DataRow r in dt.Rows) r["Value"] = folder.GetDetailsOf(fi, (int)r["ID"]);

       

        //Выводим таблицу

        int maxNameWidth = 0;

        //Определение максимальной ширины колонки Name.

        //Афигеть. LINQ над DataRowCollection не работает - https://cs.rthand.com/blogs/blog\_with\_righthand/archive/2006/01/15/284.aspx.

        //В IEnumerable<DataRow> оборачивать в лом.

        //DataTable.Compute(Max()) воспринимает в чистом виде колонку dt.Compute("Max(ID)", ""). Выражение от нее dt.Compute("Max(Len(Name))", ""), Name.Length, ... не понимает :(

        //DataTable.Select() вообще в кач-ве п-ров понимает только фильтр и сортировку, output list не задашь.

        //Столько технологий, а максимум длины приходится считать по старинке.

        foreach (DataRow r in dt.Rows) maxNameWidth = maxNameWidth < r["Name"].ToString().Length ? r["Name"].ToString().Length : maxNameWidth;

        foreach (DataRow r in dt.Rows) SqlContext.Pipe.Send(String.Format("{0, 2:d}. {1, -" + maxNameWidth.ToString() + "}\t-> {2}", r[0], r[1], r[2]));

    }

};

Подписываем сборку, как рассказывалось в "Подписание внешней или небезопасной сборки" и деплоим на SQL Server. Выставляем небезопасный permission_set из-за SHELL32.dll. Поскольку это обычная библиотека классов, приходится руками проделывать то, что обычно за сценой делает VS в случае SQL Serverного проекта:

if object_id('uspTest', 'PC') is not null drop proc uspTest

if exists(select 1 from sys.assemblies where name = 'MyAssembly') drop assembly MyAssembly

create assembly MyAssembly from 'C:\Temp\ClassLibrary1\bin\Debug\ClassLibrary1.dll' with permission_set = unsafe

go

create proc uspTest @filename nvarchar(255) as external name MyAssembly.StoredProcedures.FileExtProps

go

exec uspTest 'c:\Demo\Частичное обновление FILESTREAM.txt'

Убеждаемся, что работает:

image005

рис.3

Отладка производится аттачем к SQL Serverному процессу из VS, выставлением там брейкпойнта и запуском процедуры из SSMS.