MVC3+EF4.1學習系列(十)----MVC+EF處理樹形結構

經過前幾篇文章 咱們處理了 一對一, 一對多,多對多關係 很好的發揮了ORM框架的作用 可是 少說了一種 樹形結構的處理, 而這種樹形關係 咱們也常常遇到,常見的N級類別的處理, 以及常常有數據與類別掛鉤。今天主要寫下EF處理樹形結構以及 MVC如何展現樹形結構。 前面幾篇的例子 一直用的是一個例子,內容是連貫的。這篇是徹底單獨的~javascript

先來講下工做中會遇到的常見場景 針對這幾個場景來處理~html

1.類別java

a.類別能夠有無限級別jquery

b.類別的最末端 不肯定是第幾級 某個節點 能夠到二級 其餘的節點 有可能到四級web

c.tree型展現整個類別 並能夠對tree進行CRUD   (能夠一次遞歸所有加載  也能夠異步加載 )ajax

d.麪包屑型展現類別sql

e.刪除父類 應把下面全部的子類刪除數據庫

2.與類別掛鉤的數據 (本文是文章)json

a. 能夠根據任意級別的類別 查看文章併發

b. 合併兩個類別的文章

 

上面這些場景 基本覆蓋了類別操做的常見狀況 若是你們以爲還有什麼要處理 能夠給我說 我補充上去~~

下面開始講解~

 

一.準備工做

1.如何創建類別實體類 來展現樹形結構

上代碼

複製代碼
 /// <summary>
/// 類別
/// </summary>
public class Category
{
/// <summary>
/// 主鍵
/// </summary>
public int CategoryId { get; set; }

/// <summary>
/// 類別名字
/// </summary>
[Required()]
[StringLength(5)]
public string CategoryName { get; set; }

/// <summary>
/// 父ID
/// </summary>
public Nullable<int> ParentId { get; set; }

/// <summary>
/// 上面的父節點
/// </summary>
public virtual Category Parent { get; set; }

/// <summary>
/// 下面的子節點
/// </summary>
[ForeignKey("ParentId")]
public virtual ICollection<Category> ChildKeys { get; set; }

/// <summary>
/// 該類別的文章集合
/// </summary>
public virtual ICollection<Article> articleList { get; set; }

/// <summary>
/// 編號
/// </summary>
public string Note { get; set; }


/// <summary>
/// 狀態
/// </summary>
public string State
{
get;
set;
}

/// <summary>
/// 級別
/// </summary>
public Nullable<int> Lev
{
get;
set;
}
/// <summary>
/// 排序
/// </summary>
public int Sort
{
get;
set;
}
}
複製代碼

 

這樣的設計 很好的展現了樹形結構 一個節點有一個父類 多個子類 一個類別能夠有多個文章 這裏說下 後面的四個屬性 不是必要的~

 

2.文章實體類

上代碼 這個比較好理解 不解釋了~

 

文章實體類

 

3.創建Context

上代碼

複製代碼
public class TreeDemoContext : DbContext
{

private readonly static string CONNECTION_STRING = "name=WlfSys_EFCF_ConnString";

public DbSet<Category> Category { get; set; }
public DbSet<Article> Article { get; set; }

public TreeDemoContext()
: base(CONNECTION_STRING)
{
// this.Configuration.ProxyCreationEnabled = false;
}


protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();//移除複數表名的契約
}
}
複製代碼

 

這裏不須要使用Fluent API 來映射實體類與數據庫的關係 裏面也沒什麼亮點( 其實我一直想知道 怎麼用Fluent API 映射 來解決下面的問題 有知道的高人指點下~~ 感激 )

 

4.數據庫初始化

這是我這種方法 用ef處理樹形結構最關鍵的一點 熟練使用ef的人 看了我上面的類別實體類的創建 就會發現這是錯誤的 由於這會形成自引用 看下圖生成的數據庫表結構

 

 

因爲自引用 插入時會出現 INSERT 語句與 FOREIGN KEY SAME TABLE 約束"Category_ChildKeys"衝突。

個人解決辦法是 在初始化數據庫時 刪除這個外鍵約束 上代碼

 

複製代碼
 public class TreeDemoInitializer : DropCreateDatabaseIfModelChanges<TreeDemoContext>
{
protected override void Seed(TreeDemoContext context)
{
//刪除關聯
context.Database.ExecuteSqlCommand("ALTER TABLE [dbo].[Category] DROP CONSTRAINT [Category_ChildKeys]");


//必須加上ID
var Category = new List<Category>
{
new Category{ CategoryId=1, CategoryName="亞洲", Lev=1, ParentId=0, Note="001",ChildKeys=new List<Category>{
new Category{CategoryId=2,CategoryName="中國",Lev=2,Note="00101",ChildKeys=new List<Category>{ new Category{CategoryId=6, CategoryName="河南", Lev=3, Note="0010101" },new Category{CategoryId=7, CategoryName="廣州", Lev=3, Note="0010102" } }},
new Category{CategoryId=3,CategoryName="日本",Lev=2,Note="00102",ChildKeys=new List<Category>{ new Category{CategoryId=8, CategoryName="日本省1", Lev=3, Note="0010201" },new Category{CategoryId=9, CategoryName="日本省2", Lev=3, Note="0010202" } }
} }
},
new Category { CategoryId=4, CategoryName="歐洲", Lev=1, ParentId=0, Note="002", ChildKeys=new List<Category>{
new Category{CategoryId=5,CategoryName="荷蘭",Lev=2,Note="00201"}
} }

};
Category.ForEach(c => context.Category.Add(c));



var Articles = new List<Article>{

new Article{ ArticleName="小說13", CreateTime=DateTime.Now, CategoryId=5},
new Article{ ArticleName="小說14", CreateTime=DateTime.Now, CategoryId=5},
new Article{ ArticleName="小說15", CreateTime=DateTime.Now, CategoryId=5},
new Article{ ArticleName="小說1", CreateTime=DateTime.Now, CategoryId=6},
new Article{ ArticleName="小說2", CreateTime=DateTime.Now, CategoryId=6},
new Article{ ArticleName="小說3", CreateTime=DateTime.Now, CategoryId=6},
new Article{ ArticleName="小說4", CreateTime=DateTime.Now, CategoryId=7},
new Article{ ArticleName="小說5", CreateTime=DateTime.Now, CategoryId=7},
new Article{ ArticleName="小說6", CreateTime=DateTime.Now, CategoryId=7},
new Article{ ArticleName="小說7", CreateTime=DateTime.Now, CategoryId=8},
new Article{ ArticleName="小說8", CreateTime=DateTime.Now, CategoryId=8},
new Article{ ArticleName="小說9", CreateTime=DateTime.Now, CategoryId=8},
new Article{ ArticleName="小說10", CreateTime=DateTime.Now, CategoryId=9},
new Article{ ArticleName="小說11", CreateTime=DateTime.Now, CategoryId=9},
new Article{ ArticleName="小說12", CreateTime=DateTime.Now, CategoryId=9}

};
Articles.ForEach(a => context.Article.Add(a));

context.SaveChanges();
}
}
複製代碼

 

 

並初始化一些數據進去 這個初始化 就算是類別的添加了 在這添加時 遇到個小問題 咱們的數據庫類別ID默認是自增加的 按理說不用指定主鍵ID 可是不指定ID 像我上面 一下次插入多條時 插入時卻報錯 ~~ 沒法肯定「ContosoUniversity.DAL.Category_ChildKeys」關係的主體端。添加的多個實體可能主鍵相同。指定了ID 才解決了這個問題

 

5. 搭建基本項目結構

依然使用 unit of work +Repository ( 項目大的話 加入Iservice, Service 再加上IOC,這裏只是個簡單的demo) 如圖

 

 

二.關於類別的操做以及展現

1.tree型展現整個類別

在webfrom時代 實現tree展現很容易 由於咱們有犀利的控件 treeview 用treeview控件 再加個遞歸綁定 就很簡單的完成了 這是webfrom的好處 但也是很差的地方 好比 treeview生成出來的 是 table 嵌套table的 我若是想換成ul li怎麼辦 控件開發 形成了耦合度太高 題外話說多了 迴歸正題~

這裏 我用兩種方法實現 treeview的展現

A方法 擴展一個 HtmlHelper

實現 HTML.Tree(類別) 就能展現出treeview

用這個方法前 再說幾句 不喜歡這個方法 由於這有點像用控件了 代碼與視圖依然在一塊兒 第二 這裏我使用了遞歸, 可是小項目的話 沒什麼問題 直接上code

複製代碼
    public static MvcHtmlString Tree(this HtmlHelper html, Category treeModel)
{
return BindTree(treeModel);
}

private static MvcHtmlString BindTree(Category treeModel)
{
StringBuilder sb = new StringBuilder();
if (treeModel != null)
{
sb.Append("<ul>");

List<Category> list = treeModel.ChildKeys.ToList();
foreach (var item in list)
{
sb.Append("<li>");
sb.Append(item.CategoryName);
sb.Append("</li>");
sb.Append(BindTree(item));
}
sb.Append("</ul>");

}
MvcHtmlString mstr = new MvcHtmlString(sb.ToString());
return mstr;
}
複製代碼

 

上面實現了最最簡單的展現樹形結構 無非就是遞歸的運用 這個能夠擴展 是否展現 checkbox 啊 上來默認展現幾級啊 後面的增刪改鏈接啊 and so on~~

 

b. 利用ajax 實現異步加載 ( 我的喜歡的方法 ) 先上一個實現後的圖 我沒作任何美工 樣子很難看~ 你們將就看下

 

前面有小箭頭表示能夠打開~~ 打開後 變成打開的狀態~

下面上視圖 解釋和思路 直接加在裏面了

 

複製代碼
<script type="text/javascript">
$(
function () {
var clickLi = function () {
$(this).children("ul").toggle(); // 切換隱藏和顯示li下面的ul


//切換img圖標是 選中仍是未選中
if ($(this).children("img").attr("src") == "http://www.cnblogs.com/Content/img/selectNode.jpg") {

$(this).children("img").attr("src", "http://www.cnblogs.com/Content/img/noselectNode.jpg");
}
else {
$(this).children("img").attr("src", "http://www.cnblogs.com/Content/img/selectNode.jpg");
}


//何時發送加載下面節點的請求呢?
//在img 屬性不爲空 證實下面有節點 由於沒有節點是不會有img 樹形的 而且他的下面的ul個數爲0
if ($(this).children("img").attr("src") != undefined && $(this).children("ul").length == 0) {
var cid = $(this).attr("id");
var li = $(this);
$.post("GetCategoryById", { id: cid }, function (data) {

if (data == "-1") {
alert("失敗");
}
else {
li.append(data);
}
});
}
return false;
}

//爲何用 live 不是直接click?

//由於 ajax請求加載的 click事件是無論用的 要用live才能夠 ~~切記
$("#CategoryTree li").live("click", null, clickLi);
}
);

</script>

<h2>Index</h2>

<p>
@Html.ActionLink("Create New", "Create")
</p>

@*@Html.Tree(Model);*@

<ul id="CategoryTree">
@foreach (var item in Model.ChildKeys)
{
<li id="@item.CategoryId">
@if(item.ChildKeys.Count > 0)
{
<img src="http://www.cnblogs.com/Content/img/noselectNode.jpg" />
}
<span class="name">@item.CategoryName</span>
@Html.ActionLink("添加", "Create", new { id=@item.CategoryId})
@Html.ActionLink("修改", "Edit", new { id=@item.CategoryId})
</li>
}
</ul>
複製代碼

 

這裏說下jquery ajax請求時 咱們常常返回json 而後來構建 這裏說下mvc另外一種方法 返回一個部分視圖~~ 我很喜歡這種方法 上code~ 不解釋啦~

 

ajax返回部分視圖代碼

 

部分視圖的視圖

 

2.展現麪包屑

樹形結構的展現 咱們常常遇到treeview型的 還會遇到另外一種 麪包屑這樣的 如

咱們這裏要作的就是 根據當前類別 向上一層層推到最上面 並把最後一個加粗顯示~ 個人思路是這樣的 好比當前所在的類別 爲最小的  廣州   根據這個 一層層推到最上面 經過遞歸獲得 廣州>中國>亞洲 在經過反轉字符串 並給加粗就好了~~ 代碼以下

 

複製代碼
    /// <summary>
/// 根據當前類別建造麪包屑
/// </summary>
/// <returns></returns>
public static MvcHtmlString Menu(this HtmlHelper html, Category treeModel)
{

return new MvcHtmlString(MenuReverse(BindMenu(treeModel)));
}

/// <summary>
/// 遞歸調用 獲得 廣州>中國>亞洲
/// </summary>
/// <returns></returns>
private static string BindMenu(Category Model)
{
StringBuilder sb = new StringBuilder();
sb.Append(Model.CategoryName);
if (Model.Parent != null)
{
sb.Append(">");
sb.Append(BindMenu(Model.Parent));
}
return sb.ToString();
}

/// <summary>
/// 反轉字符串 並給最後一個加上黑體字標籤
/// </summary>
/// <returns></returns>
private static string MenuReverse(string menu)
{
return string.Join(">", menu.Split('>').Select((s, i) => i == 0 ? string.Format("<strong>{0}</Strong>", s) : s).Reverse().ToArray());
}
複製代碼

 

3.刪除父類要把下面的子類所有刪除

這裏就涉及到一個樹形結構的重要方法 經過當前類 獲得該類的全部子類子子類等的ID集合 這時刪除時用 delete in(子類集合 ) 就好了 ~~ 忘了 怎麼直接執行SQL語句的~~ 去看上一篇文章.. 經過如今類 得到下面子類集合的方法 以下

複製代碼
  /// <summary>
/// 得到父類下全部子類的集合
/// </summary>
/// <returns></returns>
private List<int> GetCidbyPid(int pid)
{
List<int> cidList = new List<int>();
Category CategoryModel = unitOfWork.CategoryRepository.GetTEntityByID(pid);
foreach (var item in CategoryModel.ChildKeys)
{
cidList.Add(item.CategoryId);
}

foreach (var item in CategoryModel.ChildKeys)
{
cidList.AddRange(GetCidbyPid(item.CategoryId));
}
return cidList;

// 得到 1,2,3,4,5 tring strcid= string.Join(",", cidList);
}
複製代碼

依然是經過遞歸 得到全部的 子節點集合   再經過string.Join(",", cidList) 獲得 delete in ( ) 裏須要的的 就好了~~

 

三.與類別掛鉤的數據的展現

一. 能夠根據任意級別的類別查看文章

依然提供兩個方法~

1.根據類別 查詢文章 利用遞歸 

不過這個效率就太糾結了~~

複製代碼
 /// <summary>
/// 經過任意類別 得到下面的所有文章
/// </summary>
/// <param name="cid"></param>
/// <returns></returns>
private List<Article> GetArticleByCid(int cid)
{
List<Article> ArticleList=new List<Article>();
Category CategoryModel=unitOfWork.CategoryRepository.GetTEntityByID(cid);
if (CategoryModel.ChildKeys.Count == 0)
{
ArticleList.AddRange(CategoryModel.articleList.ToList());
}

foreach (var item in CategoryModel.ChildKeys)
{
ArticleList.AddRange(GetArticleByCid(item.CategoryId));
}
return ArticleList;

}
複製代碼

2. 利用前面說到的 經過當前類 獲得該類的全部子類子子類等的ID集合  再查詢的時候  IN 就好了

這裏有個小知識~ 用linq 執行 sql in的操做~  用 Contains就好了  代碼以下

複製代碼
    public ActionResult Index()
{
//準備測試數據 測試不一樣狀況 GetCidbyPid爲根據ID得到全部子類以及子子類等的集合
//測試最高級
List<int> CidList = GetCidbyPid(1);

// var CidList = GetCidbyPid(2);

//測試最低級
//var CidList = GetCidbyPid(6);

// 貪婪加載類別 爲了顯示類別名字~
var ArticleList = unitOfWork.ArticleRepository.Get(c => CidList.Contains(c.CategoryId), includeProperties: "Category");
return View(ArticleList);
}
複製代碼

二.合併兩個類別的文章

這個很簡單啦~  合併兩個類  就是把一個類下的文章id  都變成另外一個   也就是說批量操做  不要用EF 的一個個更新就行  太慢了~還要發送多條更新語句 

用context.Database.SqlQuery 直接執行 update  這個就不寫代碼啦~

 

四.經過第三方工具Telerik更加酷炫的展現tree

我上面寫的東西 都是小打小鬧 自娛自樂的玩下 有不少地方不完善 不合理,而MVC實現Tree 第三方工具已經有了幫咱們作的很是優秀的了 

這裏給你們推薦個開源的  是Telerik的 tree------介紹與鏈接

把該有的操做基本所有都封裝在了裏面啦~很是方便  並且裏面還有不少其餘的控件~

還有,推薦你們看看源碼  第三方工具必定要多看看實現  不要只會用

 

五.總結

這篇是徹底獨立的  和前面幾篇沒什麼關係~ 代碼貼的都是核心片斷~ 聰明的你們看看就能明白了 並且應該有更好的實現

但願你們分享下EF MVC處理 tree的經驗以及遇到的問題~~

我也提個問題 由於tree結構的操做 不少都用到了遞歸  在EF  高併發 大數據量處理時  我以爲會出現一些問題 但願你們說說怎麼解決

整體內容沒有太多難度~ 你們本身敲敲練練吧 

實在太懶的同窗 留個郵件  我把demo發給大家~

相關文章
相關標籤/搜索