C#無限分類,遞迴重建排好序的樹狀資料來源

來源:互聯網
上載者:User

現在有無限分類表結構如下:

create table OA_GoodType (   ID               int                  not null,--ID欄位,u也可以說是子ID   Name             varchar(50)          null,--名稱   Pid             int              not null,--父類ID ,如果等於ID的時候就是頂級分類,(也可以設定為0的時候是頂級分類)   Path            varchar(100)         not null,--路徑(父類的集合,全部都包含‘0,’開頭,最後一位元字就是自身的id的值)   orderId             int               null,--排序   constraint PK_OA_GOODTYPE primary key (ID))

上面提到Path欄位都是以 '0,' 開頭,這樣有個好處就是選出某個類(ID為3)下面的子類時直接可以用path like ',3,'
為表示結構關係,插入以下資料:

INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(1,'被服',1,'0,1',4)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(2,'提花被',1,'0,1,2',2)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(3,'軍用棉被',1,'0,1,3',1)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(4,'軍用棉被1.2米',2,'0,1,2,4',1)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(37,'救援工具類',37,'0,37',2)INSERT INTO OA_GoodType([Id],[Name],[Pid],[Path],[OrderId]) VALUES(43,'糧油食品類',43,'0,43',6)

一般來說,對該結構的無限分類,要選出樹狀的結構圖非常簡單:只一條sql語句就夠了:
select * from OA_GoodType order by path asc
但是這樣的語句無法按照OrderID去排序
所以如果有要求排序的話,就必須對錶進行遞迴了,
如下的方法,對資料按照排序產生一顆樹狀的DataTable:

/// <summary>  /// dt 要從中產生樹狀資料來源  /// IdField ID欄位列名  /// ParentField 父級ID列名  /// Pid 父級ID值  /// OrderField 排序欄位  /// </summary>public DataTable GetTreeList(DataTable dt, string IdField, string ParentField, int Pid,string OrderField=""){    DataTable newDT = dt.Clone();// 複製dt 的結構,包括所有 dt 架構和約束,並無資料;    DataRow[] rows;    /*     * 如果父級pid等於本ID時表示頂級分類的情況 用以下語句    */    if (Pid==0)//選出頂級的類別    {rows = dt.Select(ParentField + "=" + IdField, OrderField); // 從dt 中查詢合格記錄(選出所有大類);    }    else//選出屬於所傳ID的子類    {rows = dt.Select(ParentField + "<>" + IdField + " and "+ParentField+"=" + Pid, OrderField); // 從dt 中查詢合格記錄(選出大類pid的子類);    }        /*     * 如果父ID為0的時候表示頂級分類,用以下語句     * rows = dt.Select(ParentField + "=" + Pid, OrderField);     */    if (rows.Length > 0)    {foreach (DataRow row in rows)  // 將查詢的結果添加到dt中;{    newDT.Rows.Add(row.ItemArray);    int innerpid = int.Parse(row["id"].ToString());    DataRow[] Childows = dt.Select(ParentField + "<>" + IdField + " and " + ParentField + "=" + innerpid, OrderField);//選出子類    if (Childows.Length > 0)//如果存在子類,對子類進行遞迴    {DataTable innerDT = GetTreeList(dt, IdField, ParentField, innerpid, OrderField);//擷取子類DataTableforeach (DataRow innerrow in innerDT.Rows)//對子類的DataTable添加進總資料來源{    newDT.Rows.Add(innerrow.ItemArray);}    }}    }    dt.Dispose();//現不清楚是否需要該語句,執行該語句也正常顯示    return newDT;}

用法如下:

DataTable dt = comm_table.GetList("select * from OA_GoodType").Tables[0];//重組樹狀資料來源,所以此處的SQL可以不用排序        subCate.DataSource = comm_table.GetTreeList(dt,"id","Pid",0,"OrderID");        subCate.DataBind();//subCate是Repeater控制項

附上對於無限分類的表的一些SQL操作

1、選出某個類所屬的父類路徑(包括名稱和id);對於本表而言,本方法一般是在列出屬於某個類下面的物品詳情的時候,順便列出該物品所屬的一系列分類

create proc getPathInfo          @Id varchar(120),  --需要選出路徑名稱的記錄的id 或者路徑         @IdField varchar(30), --儲存ID的欄位名          @NameField varchar(30), --儲存名稱的欄位名          @PathField varchar(30), --儲存路徑的欄位名          @Table varchar(30),  --要操作的表名          @Split varchar(10)='→',  --串連路徑用到的分隔字元  @isPath bit = 0   --傳進來的是路徑的話設定為1          as          DECLARE @sql nvarchar(300)          DECLARE @Path varchar(50)  --儲存父類路徑的id集合        DECLARE @PathName varchar(100) --迴圈時儲存各個父類名稱(如果有的名稱太長可適當增大該欄位)     DECLARE @AllPath varchar(500)  --儲存最終各個父類名稱形成的字串(如果有的名稱太長可適當增大該欄位)             --DECLARE @index int          SET @AllPath=''  if(@isPath=1)--傳進來的是路徑的時候       SET @Path=@Id  else  begin   SET @sql = 'select @Path='+@PathField+' from '+@Table+' where '+@IdField+'='+@Id   EXEC sp_executesql @sql, N'@Path as varchar(50) OUTPUT', @Path OUTPUT  end  --print(@Path)          DECLARE @NameCursor cursor          set @sql=N'select '+@NameField+' from '+@Table+' where '+@IdField+' in('+@Path+') order by '+@PathField+' asc'-- 遊標查詢語句          -- 遊標處理          set @sql = N'SET @NameCursor = CURSOR FORWARD_ONLY          FOR           '+@sql+N'          OPEN @NameCursor'          EXEC sp_executesql @sql, N'@NameCursor CURSOR OUTPUT', @NameCursor OUTPUT          fetch NEXT from @NameCursor into @PathName          while @@fetch_status=0          begin           SET @AllPath = @AllPath + @Split + @PathName--把路徑用串連符串連起來,由於一開始的時候@AllPath 為空白,所以再返迴路徑的時候要截取掉第一個分隔字元         fetch NEXT from @NameCursor into @PathName          end;        --對於名字截取掉第一個分隔字元,sqlserver的字串下標以1開始,對於ID,所有的ID都包含了 0,所以要從第三個開始        select substring(@AllPath,len(@Split)+1,len(@AllPath)) as PathName,substring(@Path,3,len(@Path)) as PathID        close @NameCursor          deallocate @NameCursor

可用語句 exec getPathInfo 20,'id', 'name', 'path', 'OA_GoodType' 去測試以上語句的返回結果

二、選出某個類下面的所有子類id,本方法用處很多,例如一個分類下面有好多個分類,每個分類下面都包含了物品(對於文章系統的話可以說是文章),
那麼一般來說點擊父類的時候都需要列出屬於該分類下面的所有文章,包括其子類或者子類下的子類;

create proc getChildInfo        @Id varchar(100),  --需要選出路徑名稱的記錄的id (或者路徑)       @IdField varchar(30), --儲存ID的欄位名        @NameField varchar(30), --儲存名稱的欄位名        @PathField varchar(30), --儲存路徑的欄位名        @Table varchar(30),  --要操作的表名        @Split varchar(10)=',',  --串連所有子類用到的分隔字元  @isPath bit = 0   --傳進來的是路徑的話設定為1     as        DECLARE @sql nvarchar(300)        DECLARE @ChildID varchar(20)  --儲存父類路徑的id集合      DECLARE @AllChild varchar(120)        SET @AllChild=''  -- 遊標查詢語句  if(@isPath=1)--傳進來的是路徑的時候     SET @sql = N'select '+@IdField+N' from '+@Table+N' where '+@PathField+N'='''+@Id+N''' or '+@PathField+N' like '''+@Id+N',%'''  else   SET @sql = N'select '+@IdField+N' from '+@Table+N' where '+@PathField+N' like ''%,'+@Id+N''' or '+@PathField+N' like ''%,'+@Id+N',%'''  DECLARE @IDCursor cursor        -- 遊標處理        set @sql = N'SET @IDCursor = CURSOR FORWARD_ONLY        FOR         '+@sql+N'        OPEN @IDCursor'        EXEC sp_executesql @sql, N'@IDCursor CURSOR OUTPUT', @IDCursor OUTPUT        fetch NEXT from @IDCursor into @ChildID        while @@fetch_status=0        begin         SET @AllChild = @AllChild + @Split + @ChildID--把路徑用串連符串連起來,由於一開始的時候@AllPath 為空白,所以再返迴路徑的時候要截取掉第一個分隔字元       fetch NEXT from @IDCursor into @ChildID        end;      --對於名字截取掉第一個分隔字元,sqlserver的字串下標以1開始,對於ID,所有的ID都包含了 0,所以要從第三個開始      select substring(@AllChild,len(@Split)+1,len(@AllChild)) as PathName  close @IDCursor        deallocate @IDCursor

可用語句 exec getPathInfo 1,'id', 'name', 'path', 'OA_GoodType' 去測試以上語句的返回結果

結束!!!!!!!!!

附:再分享一下無限分類查詢、添加和修改的時候的一些操作:

/* 查詢某個類(假設id是3)下的直接子類*/Select * from OA_GoodType where Pid=3/* 查詢某個類(假設id是3)下的所有子類*/Select * from OA_GoodType where pid=3 or path like '%,3,%'

添加和修改的時候所屬大類的值都使用用path,如下:

頁面上用select 框列出所有的分類:

如過要按照排序去列出來,那麼就用上面的方法先排序,或者直接用:Select  * from OA_GoodType order by path asc

然後用這方法列出來一個梯級的select:(因為C#的丟失了,就用PHP的代替了:)假設資料來源是一個數組:$cateArr

$options = '';foreach($step_1[0] as $v){$options .= '<option id="'.$v['id'].'" value="'.$v['path'].'">';$cate_count = intval($v['level']);if($cate_count > 1){//如果有兩級及以上//將類別欄位中的每一級分類替換為4個空格輸出,設定縮排$options .= str_repeat('  ',$cate_count-1);$options .= '|--'.$v['name'].'</option>';}else{$options .= $v['name'].'</option>';}}

然後直接輸出就可以了:

<select name="belong" id="belong" >      <option id="0" value="0">頂級分類</option>      <?php echo $options?></select>

顯示如下:

這樣在儲存的時候可以這樣進行:

$belong = preg_replace('/[^\d|,]/','',$_POST['belong'];$pid = 0;$level = 1;$path = '';//處理輸入資料if($belong && strpos($belong,',') === false){//如果有選擇地區,但是地區不合法MessageShowAndGoback('所屬地區有誤!');}if($belong == '0'){//是頂級分類的時候,路徑就是 0,+本類id$path_pre = '0,';}else{//不是頂級分類的時候,路徑就是父類路徑 $belong +本類id$path_pre = $belong.',';$Arr = explode(',',$belong);$pid = end($Arr);$level = count($Arr);}$insertArr = array(//這裡多了一個欄位:level,好處就是:當你在只需要某一級分類的時候可以直接獲得:select * from table where level=2;'level'=>$level,'pid'=>$pid,'path'=>$path);//插入相應記錄之後記著更新該記錄的path欄位!因為path欄位的最後一個數字是本記錄的id的值,這個值是自增的,所以只能現插入後更新://假設記錄的自增id是:$pid$sql = 'Update table  set path = '.$path_pre.$id.' where id='.$id;

修改的時候比較複雜,要處理的問題比較多,例如:1、不能把本類的所屬分類 修改成它的子類。2、要把本類的子類也一同轉移到新的所屬分類上,那就涉及把它的子類的path改成對應的新的path頁面上列出的分類仍然是用path作為值:

(可以用一下語句設定給分類所屬的分類:)

jQuery("#"+$id).selected=true;

js階段判定是否違反1中所說條件:

把原來的path儲存在一個叫old_path的隱藏欄位中

var path_old = form.old_path.value;var path_new = form.belong.value;if(path_new.indexOf(path_old+",") != -1){//移動到的分類中包含了原來的分類alert('不能移動到本類的子分類下!');return false;}



php儲存的時候要做如下處理:

$belong = preg_replace('/[^\d|,]/','',$this->input->post('belong'));//擷取原來的所屬地區資訊$old_path = preg_replace('/[^\d|,]/','',$this->input->post('old_path'));//根據路徑擷取原來分類所屬地區$parents_arr = explode(',',$old_path);$old_pid = $parents_arr[count($parents_arr)-2];//儲存原來的所屬地區$fixPath = false;//判斷是否有修改所屬地區//1、判斷原來的所屬分類和現在的所屬分類是不是一致$newParends =  explode(',',$belong);$newPID = end($newParends);if($old_pid != $newPID){//原來的所屬分類和現在的所屬分類是不是一致//也不是移動到本分類下if($rid != $newPID){$fixPath = true;}}//1、如果有修改所屬地區,需要對該地區下的所有分類進行轉移,更改path以及levelif($fixPath){//有修改所屬分類的時候if(strpos($belong,$old_path) !== false){MessageShowAndGoback('不能移動到本類的子分類下');}$Arr = explode(',',$belong);$updatetArr['pid'] = end($Arr);$updatetArr['level'] = count($Arr);//新的地區等級$updatetArr['path'] = $belong.','.$rid;$old_level = count($parents_arr)-1;//原來地區等級}if($fixPath){//有修改路徑的時候,把他的子類全部都改過來$where = ' (pid =\''.$rid.'\' or path like \''.$old_path.',%\')';$newLevel = $updatetArr['level']-$old_level;$rows = $this->AreaClassifys->Update(array('path'=>"replace(path,'$old_path','".$updatetArr['path']."')",'level'=>"level+$newLevel"),$where,false);//$sql = "update cms_category set path = replace(path,'$src_path','$new_path') where path like '$src_path%' or pid = $rid}

完結

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.