標籤:.exe 反轉 實現 查詢 遞迴 封裝 sel script reverse
通過前幾篇文章 我們處理了 一對一, 一對多,多對多關係 很好的發揮了ORM架構的做用 但是 少說了一種 樹形結構的處理, 而這種樹形關係 我們也經常遇到,常見的N級類別的處理, 以及經常有資料與類別掛鈎。今天主要寫下EF處理樹形結構以及 MVC如何展示樹形結構。 前面幾篇的例子 一直用的是一個例子,內容是連貫的。這篇是完全單獨的~
先來說下工作中會遇到的常見情境 針對這幾個情境來處理~
1.類別
a.類別可以有無限層級
b.類別的最末端 不確定是第幾級 某個節點 可以到二級 其他的節點 有可能到四級
c.tree型展示整個類別 並可以對tree進行CRUD (可以一次遞迴全部載入 也可以非同步載入 )
d.麵包屑型展示類別
e.刪除父類 應把下面所有的子類刪除
2.與類別掛鈎的資料 (本文是文章)
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 { [email protected]})
@Html.ActionLink("修改", "Edit", new { [email protected]})
</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發給你們~
MVC3+EF4.1學習系列(十)----MVC+EF處理樹形結構