нужен алгоритм для создания вложенных категорий в c # - PullRequest
4 голосов
/ 11 августа 2010

У меня есть структура таблицы, как показано ниже:

categoryID bigint , primary key , not null
categoryName nvarchar(100) 
parentID bigint, not null

, где категории ID и parentID имеют отношение один ко многим друг с другом

, и я хочу создать вложенные категориис неограниченной глубиной из этой таблицы в моей программе.

У меня есть решение, но оно не так хорошо работает, и только возвращает рут, пожалуйста, посмотрите код:

    private static string createlist(string catid, string parent)
    {

        string sql = "SELECT categoryID , categoryName FROM category WHERE parentID = " + parent;
        SqlConnection cn = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=sanjab;Integrated Security=True");
        cn.Open();
        SqlCommand cmd = new SqlCommand(sql, cn);
        SqlDataReader sdr = cmd.ExecuteReader();

        while (sdr.Read())
        {
            if (catid != "")
                catid += ", ";
            catid += sdr[1].ToString();
            createlist(catid, sdr[0].ToString());


        }
        return catid;
    }

хотя кодне очень эффективен, потому что он открывает много соединений одновременно, но с помощью вышеприведенного кода и небольшого количества настроек я могу управлять 2-уровневыми категориями глубины, но более того, это доставляет мне много хлопот.

Есть ли более простой метод или алгоритм?

regards.

Ответы [ 5 ]

4 голосов
/ 11 августа 2010

Предполагая, что вы хотите иметь дело со всем графом иерархических объектов категории одновременно, и предполагая, что вы имеете дело с разумным числом категорий (осознанный акцент), лучше всего загрузить все данные из вашей базы данных SQL за один раз, затем создайте граф дерева объектов в памяти, а не попадите в базу данных для каждого узла в дереве, что может привести к сотням просмотров базы данных.

Например, если было 5 категорий и у каждой было 5 подкатегорий, и у каждой подкатегории было 5 подкатегорий, то вы просматривали 31 попадание в базу данных, чтобы загрузить его с помощью рекурсии из точки базы данных. of-view, даже если вы имеете дело только с 125 фактическими записями базы данных. Выбор всех 125 записей за один раз не приведет к банкротству банка, в котором может быть более 31 попадания в базу данных.

Для этого я бы сначала вернул ваши категории в виде сплющенного списка (псевдокод):

public IList<FlattenedCategory> GetFlattenedCategories()
{
    string sql = "SELECT categoryID, categoryName, parentID FROM category";
    SqlConnection cn = // open connection dataReader etc. (snip)

    while (sdr.Read())
    {
        FlattenedCategory cat = new FlattenedCategory();
        // fill in props, add the 'flattenedCategories' collection (snip)
    }

    return flattenedCategories;
}

С классом FlateredCategory, блокирующим что-то вроде этого:

public class FlattenedCategory
{
    public int CategoryId { get; set; }
    public string Name { get; set; }
    public int? ParentId { get; set; }
}

Теперь у нас есть коллекция всех категорий в памяти, мы строим дерево следующим образом:

public IList<Category> GetCategoryTreeFromFlattenedCollection(
    IList<FlattenedCategory> flattenedCats, int? parentId)
{
    List<Category> cats = new List<Category>();

    var filteredFlatCats = flattenedCats.Where(fc => fc.ParentId == parentId);

    foreach (FlattenedCategory flattenedCat in filteredFlatCats)
    {
        Category cat = new Category();
        cat.CategoryId = flattenedCat.CategoryId;
        cat.Name = flattenedCat.Name;

        Ilist<Category> childCats = GetCategoryTreeFromFlattenedCollection(
            flattenedCats, flattenedCat.CategoryId);

        cat.Children.AddRange(childCats);

        foreach (Category childCat in childCats)
        {
            childCat.Parent = cat;
        }

        cats.Add(cat);
    }

    return cats;
}

И назовите это так:

IList<FlattenedCategory> flattenedCats = GetFlattenedCategories();
Ilist<Category> categoryTree = GetCategoryTreeFromFlattenedCollection(flattenedCats, null);

Примечание: в этом примере мы используем Nullable INT для ParentCategoryId, обнуляемое значение будет означать, что это корневая категория (верхнего уровня) (без родителя). Я бы порекомендовал вам сделать поле parentID в вашей базе данных тоже обнуляемым.

Предупреждение: Код не проверен, просто псевдокод, поэтому используйте на свой страх и риск. Это просто для демонстрации общей идеи.

3 голосов
/ 11 августа 2010

Сначала я создал бы класс Category для хранения ваших категорий и дочерних категорий.это позволит вам реализовать рекурсию, считывая ваши бесконечно глубокие дочерние категории.Это выглядело бы так: (Отказ от ответственности, ни один из перечисленных ниже не был протестирован с компилятором, поэтому могут быть некоторые синтаксические ошибки)

public class Category{
     public int CategoryId { get; private set; }
     public string CategoryName { get; private set; }
     public int ParentId { get; private set; }
     public List<Category> ChildCategories { get; private set; }

     //I would recommend using a parentId of 0 or some other int value that can't occur in your database to describe a category that has no parent.
     public Category(int categoryId, string categoryName, int parentId)
     {
         CategoryId = categoryId;
         CategoryName = categoryName;
         ParentId = parentId;
         ChildCategories = getChildCategories();
     }

     //I didn't have this method setting the ChildCategories directly because I would 
     //recommend refactoring your data access code into a data access class of some sort
     //also depending on the amount of categories you could be generating a lot of database calls.
     //It may make more sense to get the whole table and pass in the resulting dataset, then use recursion to build your nested category structure.
     private List<Category> getChildCategories()
     {
          List<Category> resultingCategories = new List<Category>();
          //Data access logic here to get a dataset assume the variable holding the  
          //data reader is named sdr

          while (sdr.Read()){
              //This assumes categoryId is the first column
              int childCategoryId = Convert.ToInt32(sdr[0]);

              //This assumes categoryName is the second column
              string childCategoryName = sdr[1];

              Category childCategory = new Category(childCategoryId, childCategoryName, CategoryId);
              resultingCategories.Add(childCategory);
          }
          return resultingCategories;
     }
}

Также только сторонаЕсли вы контролируете все данные, передаваемые в функцию доступа к данным, настройте параметры запроса.Это гарантирует, что ваше приложение не попадет под столь распространенную атаку SQL-инъекцией.

2 голосов
/ 11 августа 2010

Как я уже говорил в комментариях, я думаю, что это больше вопрос sql.

В моем предыдущем примере было 2 таблицы.Боже мой, это выглядит лучше: Рекурсивный вызов или объединение SQL для родителей / детей?

Вот хороший поиск в Google, чтобы найти больше: http://www.google.com/search?q=sql+children+recursive+site%3Astackoverflow.com

Общие выражения таблицысделано именно для этой цели: http://msdn.microsoft.com/en-us/library/ms190766.aspx

Принятый ответ в порядке, и грубое принуждение это в C # приемлемо.Однако хорошо, если ваши категории - это небольшой список, и он будет оставаться небольшим списком.Это как раз та вещь, которая через 3 года будет страдать от проблем с производительностью по мере роста таблицы.

1 голос
/ 11 августа 2010

Вложенные множества являются одним из хорошо продуманных решений для SQL-конца этого уравнения. Для этого требуется немного больше логики приложения, поэтому вам нужно максимально абстрагировать поведение.

У них есть некоторые ограничения, если элементы часто вставляются или удаляются, но это не должно применяться в вашем случае. Решение, когда у вас есть частые вставки или удаления, обычно заключается в том, чтобы создать разрывы нумерации среди детей.

http://www.sqlteam.com/article/more-trees-hierarchies-in-sql содержит ссылки на раннюю статью Celko на эту тему и некоторые современные вариации на эту тему.

0 голосов
/ 11 августа 2010

Вы должны выполнить рекурсию в своем коде, чтобы получить все необходимые данные с помощью «select * from yourtable» в свою память, а затем выполнить рекурсивный метод. Это будет намного быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...