Рекурсивные TVF-2

Формат блога неудобен по сравнению с форумом тем, что тему нельзя оформить одной веткой, создав постинг под предыдущим. Здесь как бы каждый постинг это отдельная тема. Данный постинг не тянет на отдельную тему, он является продолжением темы "Рекурсивные TVF".

Существуют упертые люди, которым недостаточно работающего примера, потому что их зацикливает на if (!shallowTraversal) InitMethod(dirName, false). У каждого бывают свои пунктики, абсолютно здоровых людей, как известно, не бывает. Возникает вопрос: почему IEnumerable всякий раз должны быть различны? Почему это не может быть единый List<row_item> enumResult, который просто вынести за скобки InitMethod? Давайте действительно пусть он будет общим в масштабах класса, тогда в методе InitMethod действительно можно будет избежать enumResult.AddRange() к вящей радости слушателей.

Не все так просто. Во-первых, стоит объявить private static List<row_item> enumResult непосредственно внутри класса, немедленно получается ошибка деплоймента "CREATE ASSEMBLY failed because type 'UserDefinedFunctions' in external_access assembly 'SqlClassLibrary' has a static field 'enumResult'. Attributes of static fields in external_access assemblies must be marked readonly in Visual C#, ReadOnly in Visual Basic, or initonly in Visual C++ and intermediate language". Придется написать так:

 

...

   

    private static readonly List<row_item> enumResult = new List<row_item>();

   

    [Microsoft.SqlServer.Server.SqlFunction(Name = "Dir", FillRowMethodName = "FillRow",

        TableDefinition = "fullName nvarchar(1000), dateModified datetime2, size bigint, isDir bit, parent nvarchar(1000)")]

    public static IEnumerable InitMethod(string folder, bool shallowTraversal)

    {

        try

        {

            ...

            }

            foreach (string dirName in Directory.GetDirectories(folder, "*", SearchOption.TopDirectoryOnly))

            {

                if (!shallowTraversal) InitMethod(dirName, false);

                DirectoryInfo di = new DirectoryInfo(dirName);

                row_item r = new row_item(); r.fullName = dirName; r.dateModified = di.LastWriteTime; r.isDir = true;

                enumResult.Add(r);

            }

        }

        ...

        return enumResult;

    }

    ...

}

Скрипт 1

Две строчки, изменившиеся по сравнению с предыдущей версией кода, на которые сейчас стоит обратить внимание, покрашены в жирный цвет. Деплоим проект на SQL Server. Замечательно деплоится. Вызываем, например, select * from dbo.Dir('c:\Temp', 0). Замечательно работает. Ура? Фиг. Вызовем еще раз select * from dbo.Dir('c:\Temp', 0). Что при этом происходит? Правильно, предыдущий результат задваивается. Еще раз. Уже затроился. Смысл понятен. Статический enumResult инициализируется вместе с созданием класса UserDefinedFunctions, которое происходит в момент первого обращения. Все последующие вызовы приводят к тому, что в это поле добавляется новый результат вызова в дополнение к результатам всех предыдущих, которые там уже лежат и накапливаются. Необходимо сбрасывать enumResult.Clear() после каждого вызова T-SQLной функции, а для этого отличать начальный вызов InitMethod от вложенных, по возможности избежав для него дополнительных параметров. Гораздо проще оставить enumResult внутри InitMethod, а рекурсию вынести в отдельную функцию, которая будет вызываться из InitMethod.

using System;

using System.Data;

using System.Data.SqlClient;

using System.Data.SqlTypes;

using Microsoft.SqlServer.Server;

using System.IO;

using System.Collections;

using System.Collections.Generic;

public partial class UserDefinedFunctions

{

    struct row_item

    {

        public string fullName;

        public DateTime dateModified;

        public long size;

        public bool isDir;

    }

   

    [Microsoft.SqlServer.Server.SqlFunction(Name = "Dir", FillRowMethodName = "FillRow",

        TableDefinition = "fullName nvarchar(1000), dateModified datetime2, size bigint, isDir bit, parent nvarchar(1000)")]

    public static IEnumerable InitMethod(string folder, bool shallowTraversal)

    {

        List<row_item> enumResult = new List<row_item>();

        Recursive(folder, shallowTraversal, enumResult);

        return enumResult;

    }

    private static void Recursive(string folder, bool shallowTraversal, List<row_item> enumResult)

    {

        try

        {

            foreach (string fileName in Directory.GetFiles(folder, "*", SearchOption.TopDirectoryOnly))

            {

                FileInfo fi = new FileInfo(fileName);

                row_item r = new row_item(); r.fullName = fileName; r.dateModified = fi.LastWriteTimeUtc; r.size = fi.Length; r.isDir = false;

                enumResult.Add(r);

            }

            foreach (string dirName in Directory.GetDirectories(folder, "*", SearchOption.TopDirectoryOnly))

            {

                if (!shallowTraversal) Recursive(dirName, false, enumResult);

                DirectoryInfo di = new DirectoryInfo(dirName);

                row_item r = new row_item(); r.fullName = dirName; r.dateModified = di.LastWriteTime; r.isDir = true;

               enumResult.Add(r);

            }

        }

        catch (UnauthorizedAccessException) { };

    }

    public static void FillRow(Object o, out SqlString fullName, out DateTime? dateModified, out SqlInt64 size, out SqlBoolean isDir, out SqlString parent)

    {

        row_item r = (row_item)o;

        fullName = r.fullName; dateModified = r.dateModified; size = r.size; isDir = r.isDir; parent = Path.GetDirectoryName(r.fullName);

    }

}

Скрипт 2

С принципиальной точки зрения здесь ничего не изменилось. InitMethod целиком готовит enumResult, который позаписьно выдается FillRow(). Каждая директория пополняет enumResult своим содержанием, единственно, теперь это делается не enumResult.AddRange() после каждого рекурсивного вызова, а в процессе рекурсии. Но если кто счастлив от такого видоизменения, то пожалуйста.