2012年6月28日 星期四

Dynamic LINQ + Entity Framework - Part.3:ASP.NET MVC 應用

 

我都快要忘記有開「Dynamic LINQ + Entity Framework」這個系列的主題,

離這個主題的上一篇文章發佈日期都快要超過三個月了,所以趕快來補一下這個主題的進度,

前兩篇是說基本的整合與程式應用,所以這次就直接進入到 ASP.NET MVC 網站專案的應用,

先藉由幾個簡單的範例來說明「Dynamic LINQ + Entity Framework」在網站專案中的操作應用。

 

Dynamic LINQ 可以在 .NET 的專案中使用,不是只有限定在 ASP.NET MVC 中才能使用,

我目前工作上所開發的專案是 ASP.NET WebForms,也是一樣有使用 Dynamic LINQ。

 


為了在專案中可以清楚得知由 Dynamic LINQ 所轉出的 SQL Statement 內容,所以專案會使用 MiniProfiler,

如果還不知道 MiniProfiler 或是還沒有使用過的朋友,可以藉由「MiniProfiler 2.0.1」這篇文章來了解如何使用,

P.S. MiniProfiler 在 2012-06-12 已經更新到 2.0.2

MiniProfiler 除了可以在 ASP.NET MVC 網站專案中使用,也可以在 ASP.NET WebForms 網站專案中使用,

有關 ASP.NET WebForms 網站專案中使用 MiniProfiler 的資訊,可以參考「MiniProfiler with ASP.NET WebForms」。

 

資料庫為使用 Northwind(北風資料庫 in MS SQL Server 2008)

 

基本應用一

從「Categories」Table 中把 CategoryName 含有單字「a」的資料給找出來,然後再以 CategoryID 做升冪排序。

在使用 Dynamic SQL 進行操作之前,我們先來看看不使用 Dynamic SQL 操作的情況,

 

不使用 Dynamic SQL

controller

public ActionResult Index()
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        var query = db.Categories
            .Where(x => x.CategoryName.Contains("a"))
            .OrderBy(x => x.CategoryID);
        ViewData.Model = query.ToList();
        return View();
    }
}

實際的執行結果

image

而轉出的 SQL Statement

image

 

看完一般的 LINQ Query Expression 執行之後,接著我們來看看使用 Dynamic LINQ 的執行。

 

使用 Dynamic LINQ

controller

public ActionResult Index2()
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        var query = db.Categories
            .Where("CategoryName.Contains(@0)", "a")
            .OrderBy("CategoryID");
        ViewData.Model = query.ToList();
        return View();
    }
}

實際的執行結果

image

轉出的 SQL Statement

image

 

比較兩者的執行結果,其實轉出的 SQL Statement 都是一樣的,所以執行的結果當然也是相同的,

以上的這個基本應用一,其實是複習「Dynamic LINQ + Entity Framework - Part.1:MS SQL Server, LINQPad」,

因為上面的程式內容就是使用「Dynamic LINQ + Entity Framework - Part.1:MS SQL Server, LINQPad」裡面的程式。

 

 

基本應用二

接著再來看看其他的應用,這次就跳過一般正常的 LINQ Query Expression 的對照執行,直接使用 Dynamic LINQ 的操作,

這一次的執行情境是這樣的:

查詢「Customers」中的資料, 將 City 位於「London」且顧客訂單數量大於等於 10 的顧客給找出來,

最後再以 CustomerID 做降冪排序處理。

這次的情境就有包含到關連資料的應用,所以我們來看看程式中使用 Dynamic LINQ 的寫法,

 

Controller

public ActionResult Test1()
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        var query = db.Customers
            .Where("City == @0 and Orders.Count >= @1", "London", 10)
            .OrderBy("CustomerID desc")
            .Take(10);
        ViewData.Model = query.ToList();
        return View();
    }
}

其實看到程式的寫法應該很好理解,就是原本 LINQ Query Expression 的寫法用一般字串的寫法。

 

執行結果:

image

轉出的 SQL Statement:

image

 

 

進階應用

看完了比較基本的用法後,接著再來看看比較進階一點的使用方式,

一般實際的應用並不會是很單純的查詢或是基本的資料顯示,通常都會有一些條件限制的動態調整,

例如說,我想要針對「Customers」的資料做些動態的資料過濾的顯示,要以下拉選單的選擇來顯示各城市的顧客,

並且希望可以對指定的欄位做升冪或是降冪的排序處理。

如果是使用一般的 LINQ Query Expression 也是一樣可以達到這樣的處理,只不過程式的寫法就會不具彈性,

而且如果未加以優化處理或改善的程式,其長度一定會是相當驚人,如果是改用 Dynamic LINQ 來處理就會精簡許多。

 

先來看頁面的顯示

image

畫面中可以看到上方紅色線框的地方就是我們可以設定查詢條件的地方,使用三個下拉選單來做選擇,

第一個下拉選單是顧客所在城市的查詢條件,預設為顯示全部城市。

第二個下拉選單是選擇顯示資料的排序欄位,預設為使用 CompantName 來做排序。

第三個下拉選單是選擇欄位的排序類型,預設為升冪排序「ASC」。

 

直接看 controller 中的 Action() 方法,

大家可以看到 Test2 這個 Action() 方法內的程式相當簡單,使用黃色標註起來就是使用 Dynamic LINQ 的部分,

public ActionResult Test2(string city = "all", string columnName = "CompanyName", string sort = "asc")
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        PrepareDropDwonLists(city, columnName, sort);
        var query = db.Customers.Select(x => x);
        if (!city.Equals("all"))
        {
            query = query.Where("City == @0", city ?? "London");
        }
        query = query.OrderBy(string.Format("{0} {1}",columnName,sort));
        ViewData.Model = query.ToList();
        return View();
    }
}

 

在上面的 Action() 方法中有看到一個 PrepareDropDwonLists(city, columnName, sort) 方法,

這是用來產生前端頁面上的三個下拉選單,

private void PrepareDropDwonLists(string city, string columnName, string sort)
{
    using (NorthwindEntities db = new NorthwindEntities())
    {
        //City DropDownList
        var cities = db.Customers.Select(x => x.City).OrderBy(x => x).Distinct();
        Dictionary<string, string> cityDict = new Dictionary<string, string>();
        cityDict.Add("ALL Cities", "all");
        foreach (var item in cities)
        {
            cityDict.Add(item, item);
        }
        ViewData["CityDDL"] = DropDownListHelper.GetDropdownList
        (
            "city",
            "city",
            cityDict,
            new { id = "city" },
            string.IsNullOrWhiteSpace(city) ? "all" : city,
            false,
            ""
        );
        //Column DDL
        Dictionary<string, string> columnDict = new Dictionary<string, string>();
        var columns = this.GetPrimitiveEdmMemberNames(db.Customers.EntitySet.ElementType);
        foreach (var item in columns)
        {
            columnDict.Add(item, item);
        }
        ViewData["ColumnDDL"] = DropDownListHelper.GetDropdownList
        (
            "columnName",
            "columnName",
            columnDict,
            new { id = "columnName" },
            string.IsNullOrWhiteSpace(columnName) ? "CustomerID" : columnName,
            false,
            ""
        );
        //Sort DDL
        Dictionary<string, string> sortDict = new Dictionary<string, string>();
        sortDict.Add("ASC", "asc");
        sortDict.Add("DESC", "desc");
        ViewData["SortDDL"] = DropDownListHelper.GetDropdownList
        (
            "sort",
            "sort",
            sortDict,
            new { id = "sort" },
            string.IsNullOrWhiteSpace(sort) ? "asc" : sort,
            false,
            ""
        );
    }
}

 

產生下拉選單的方法則是用我之前有介紹過的「ASP.NET MVC 後端產生DropDownList」,

不過這邊所使用的 Method 是有做過一點修改的,如下:

#region GetDropdownList
/// <summary>
/// 產生下拉選單html(適合用於非TModel的資料,以IDictionary傳入下拉選單的值).
/// </summary>
/// <param name="tagID">下拉選單的Tag ID</param>
/// <param name="tagName">拉選單的Tag Name</param>
/// <param name="optionData">下拉選單Option的Text與Value.</param>
/// <param name="htmlAttributes">The HTML attributes.</param>
/// <param name="defaultSelectValue">預選值.</param>
/// <param name="appendFirstItem">是否加入第一個Option.</param>
/// <param name="firstItemText">如果appendFirstItem為true,第一個Option要顯示的文字,如果沒有指定則顯示[請選擇].</param>
/// <returns></returns>
/// <!-- 適合用於非TModel的資料,以IDictionary<string, string>傳入下拉選單的值,就可以產生下拉選單的Html Tag-->
/// <!-- 此為靜態方法, by Kevin -->
public static string GetDropdownList(
    string tagID, 
    string tagName, 
    IDictionary<string, string> optionData, 
    object htmlAttributes, 
    string defaultSelectValue, 
    bool appendFirstItem, 
    string firstItemText)
{
    if (string.IsNullOrEmpty(tagID))
    {
        throw new ArgumentNullException("tagID", "產生DropDownList時 tagID 不得為空");
    }
    if (string.IsNullOrEmpty(tagName))
    {
        tagName = HttpUtility.HtmlEncode(tagID);
    }
 
    TagBuilder select = new TagBuilder("select");
    select.Attributes.Add("id", tagID);
    select.Attributes.Add("name", tagName);
 
    StringBuilder renderHtmlTag = new StringBuilder();
 
    IDictionary<string, string> newOptionData = new Dictionary<string, string>();
 
    if (appendFirstItem)
    {
        newOptionData.Add(new KeyValuePair<string, string>(firstItemText ?? "請選擇", ""));
    }
 
    if (optionData != null)
    {
        foreach (var item in optionData)
        {
            newOptionData.Add(item);
        }
    }
 
    foreach (var option in newOptionData)
    {
        TagBuilder optionTag = new TagBuilder("option");
        optionTag.Attributes.Add("value", option.Value);
        if (!string.IsNullOrEmpty(defaultSelectValue) && defaultSelectValue == option.Value.Trim())
        {
            optionTag.Attributes.Add("selected", "selected");
        }
        optionTag.SetInnerText(option.Key);
        renderHtmlTag.AppendLine(optionTag.ToString(TagRenderMode.Normal));
    }
    select.MergeAttributes(new RouteValueDictionary(htmlAttributes));
    select.InnerHtml = renderHtmlTag.ToString();
    return select.ToString();
}
#endregion

 

而在取得「Customers」這個 Table 的欄位,則使用「取得 Entity Framework 中 Entity 對應 Table 的原生 Column Name

由文章中的方法就可以取得「Customers」的原生欄位名稱。

 

其實前端頁面並沒有什麼好解說的,所以就貼上 cshtml 的原始碼內容,

@model IEnumerable<MvcMSSQL.Models.Customer>
@{
    ViewBag.Title = "Test2";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
@using (Html.BeginForm("Test2", "Home", FormMethod.Post, new { id="CustomerList" }))
{
    <h2>Test2</h2>
    <fieldset>
        <br />
        City:@Html.Raw(ViewData["CityDDL"].ToString()) 
        Sort ColumnName:@Html.Raw(ViewData["ColumnDDL"].ToString())
        Sort Type:@Html.Raw(ViewData["SortDDL"].ToString())
        <input type="submit" value="submit" id="ButtonSubmit" />
    </fieldset>
    <table>
        <tr>
            <th>
                CompanyName
            </th>
            <th>
                ContactName
            </th>
            <th>
                ContactTitle
            </th>
            <th>
                Address
            </th>
            <th>
                City
            </th>
            <th>
                Region
            </th>
            <th>
                PostalCode
            </th>
            <th>
                Country
            </th>
            <th>
                Phone
            </th>
            <th>
                Fax
            </th>
        </tr>
        @foreach (var item in Model)
        {
            <tr>
                <td>
                    @item.CompanyName
                </td>
                <td>
                    @item.ContactName
                </td>
                <td>
                    @item.ContactTitle
                </td>
                <td>
                    @item.Address
                </td>
                <td>
                    @item.City
                </td>
                <td>
                    @item.Region
                </td>
                <td>
                    @item.PostalCode
                </td>
                <td>
                    @item.Country
                </td>
                <td>
                    @item.Phone
                </td>
                <td>
                    @item.Fax
                </td>
            </tr>
        }
    </table>
}

 

完成前後端的程式之後,就來執行並檢視是否符合需求:

一開始進入頁面的執行結果與 SQL Statement

image

第一個 SQL Statement 是取得第一個城市下拉選單的內容值,

第二個 SQL Statement 是執行查詢的 SQL Query Statement,

image

 

變更 City 的條件值、排序欄位、排序類型

image

查詢結果

image

SQL Statement

image

 

我在這邊沒有列出使用一般 LINQ 查詢語法來做同樣需求的程式內容,就留給各位自己去實作這一段,

在不用 Dynamic LINQ 的情況下,使用一般的 LINQ 程式語法寫出一樣的功能以執行出一樣的結果,

相信大家一定會寫出相當「精采」的程式出來,

如果有實際去用一般的 LINQ 語法來實作一次,然後回來與使用 Dynamic LINQ 的程式做個比較,

你會發現到使用 Dynamic LINQ 所寫出來的程式會精簡許多。

 


 

由上面一路看下來,有很多人就會想說,好像我是在鼓吹大家不要用一般的 LINQ 語法做查詢,而要改用 Dynamic LINQ,

絕對不是!

我這邊必須強調一點,基本的 LINQ 查詢方式是一定要學會使用的,而且是主要使用的方式,

Dynamic LINQ 必須要在你對基本 LINQ 有了基礎並且要上手之後才能去考慮使用,

你不了解基本 LINQ 的情況下就使用 Dynamic LINQ,這無疑是自找死路,因為你在使用的時候一定會遇到很多的問題,

Dynamic LINQ 是建立在 LINQ 的基礎之上,所以一定要了解並且清楚知道 LINQ 怎麼用,

而 Dynamic LINQ 只是提供另外一個彈性的方式。

 

 

以上

沒有留言:

張貼留言

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。