使用Entity Framework和 ASP.NET MVC 3 進行伺服器端分

來源:互聯網
上載者:User

原文

在我的二月份資料點專欄中,我展示了 jQuery DataTables 外掛程式,及其在用戶端無縫處理海量資料的能力。這非常適合您要切片和切塊大量資料的 Web 應用程式。本月,我將重點講述使用返回較小負載的查詢來與資料之間進行不同類型的互動。如果您以行動裝置 App程式為目標,則這一點尤為重要。

我將利用 ASP.NET MVC 3 中引入的功能,並示範如何將它們與 Entity Framework 的高效伺服器端分頁功能結合使用。這個任務有兩項挑戰。首先是為 Entity Framework 查詢提供正確的分頁參數。其次,是通過提供指示這裡有過多待檢索資料的可視線索以及觸發檢索的連結,來類比用戶端分頁。

ASP.NET MVC 3 有許多新功能,如新 Razor 視圖引擎、驗證改進功能以及許多的 JavaScript 功能。MVC 的啟動頁位於 asp.net/mvc,您可以從這裡下載 ASP.NET MVC 3 並找到能協助您加快上手速度的部落格文章與培訓視頻的連結。我將使用的新功能之一是 ViewBag。如果您以前使用 ASP.NET MVC,則會知道 ViewBag 是 ViewData 類的增強功能,使您可以使用動態建立的屬性。

ASP.NET MVC 3 為錶帶來的另一個新元素是特殊的 System.Web.Helpers.WebGrid。儘管該網格的功能之一是分頁,但我會在本樣本中使用這個新網格,卻不會使用它的分頁功能,因為該分頁是用戶端分頁,換言之,它會對提供給它的資料集進行分頁,與 DataTables 外掛程式類似。我要使用的是伺服器端分頁。

對於這個小應用程式,您需要使用一個實體物件模型。我使用是從 Microsoft AdventureWorksLT 樣本資料庫建立的一個模型,但我只需要將 Customer 和 SalesOrderHeaders 帶入模型中。我已經將 Customer rowguid、PasswordHash 和 PasswordSalt 屬性移到另一個實體中,這樣我在編輯的時候就不用擔心它們了。除了這個小更改之後,我沒有更改模型的預設值。

我使用預設的 ASP.NET MVC 3 項目模板建立了一個項目。這會預填充許多控制器和視圖,我將讓預設的 HomeController 呈現 Customers。

我將使用一個簡單的 DataAccess 類來提供與模型、上下文以及隨後的資料庫的互動。在此類中,我的 GetPagedCustomers 方法提供伺服器端分頁。如果 ASP.NET MVC 應用程式的目標是允許使用者與所有客戶互動,則單個查詢返回的和在瀏覽器中管理的將是大量的客戶。相反,我們將讓應用程式每次呈現 10 行,而 GetPagedCustomers 將提供該篩選器。我最終需要執行的查詢將如下所示:


  
  1. context.Customers.Where(c =>
  2. c.SalesOrderHeaders.Any()).Skip(skip).Take(take).ToList()

視圖將會知道請求哪個頁面,並將該資訊提供給控制器。控制器將負責瞭解每頁提供多少行。控制器將使用頁碼和每頁行數計算“skip”值。當控制器調用 GetPagedCustomers 方法時,它會傳入計算出的 skip 值以及每頁行數,即“take”值。所以,如果您在第 4 頁上並且每頁顯示 10 行,則 skip 將為 40,而 take 將為 10。

分頁查詢首先建立一個篩選器,只請求那些具有任意 SalesOrder 的客戶。然後,使用 LINQ Skip 和 Take 方法,產生的資料將是這些客戶的一個子集。然後,完整的查詢,包括分頁,將在資料庫中執行。資料庫只返回 Take 方法指定的行數。

該查詢分為幾個部分編寫,使我們能在過程中不斷添加一些技巧。以下是第一部分,將從 HomeController 調用的 GetPagedCustomers 方法:


  
  1. public static List<Customer> GetPagedCustomers(int skip, int take)
  2. {
  3. using (var context = new AdventureWorksLTEntities())
  4. {
  5. var query = context.Customers.Include("SalesOrderHeaders")
  6. .Where(c => c.SalesOrderHeaders.Any())
  7. .OrderBy(c => c.CompanyName + c.LastName + c.FirstName);
  8. return query.Skip(skip).Take(take).ToList();
  9. }
  10. }

調用此方法的控制器 Index 方法將使用我稱為 pageSize 的變數確定將要返回的行數,這個變數將成為 Take 的值。Index 方法還會根據將作為參數傳入的頁碼指定開始位置,如下所示:


  
  1. public ActionResult Index(int?
  2. page)
  3. {
  4. const int pageSize = 10;
  5. var customers=DataAccess.GetPagedCustomers((page ??
  6. 0)*pageSize, pageSize);
  7. return View(customers);
  8. }

這使我們獲得了一個很好的部分。伺服器端分頁已經完全就緒。通過 Index 視表徵圖記中的 WebGrid,我們可以顯示從 GetPagedCustomers 方法返回的客戶。在標記中,您需要聲明和執行個體化該網格、傳入 Model,它代表控制器創造視圖時提供的 List<Customer>。然後,使用 WebGrid GetHtml 方法,您可以設定網路的格式,並指定顯示哪些列。我將只顯示三個客戶屬性:CompanyName、FirstName 和 LastName。您會很開心地發現在您鍵入標記時會獲得完全的 IntelliSense 支援,無論您使用與 ASPX 視圖關聯的文法,還是使用新的 MVC 3 Razor 視圖引擎文法(如以下樣本所示)。在第一列中,我將提供一個 Edit ActionLink,以使使用者可以編輯顯示的任何客戶:


  
  1. @{
  2. var grid = new WebGrid(Model);
  3. }
  4. <div id="customergrid">
  5. @grid.GetHtml(columns: grid.Columns(
  6. grid.Column(format: (item) => Html.ActionLink
  7. ("Edit", "Edit", new { customerId = item.CustomerID })),
  8. grid.Column("CompanyName", "Company"),
  9. grid.Column("FirstName", "First Name"),
  10. grid.Column("LastName", "Last Name")
  11. ))
  12. </div>

結果如圖 1 所示。

圖 1 在 WebGrid 中提供 Edit ActionLink

到目前為止一切順利。但這沒有為使用者提供導航到另一頁資料的方法。有多種方法可以實現這一目的。一種方法是在 URI 中指定頁碼,例如,http://adventureworksmvc.com/Page/3。您肯定不會要求終端使用者來做這個。更易於發現的機制是使用分頁控制項,如頁碼連結“1 2 3 4 5 …”或指示向前或向後的連結,例如“<< >>”。

啟用分頁連結目前的障礙是 Index 視圖頁不知道將會擷取過多的客戶。它只知道它要顯示的客戶範圍是 10。通過向資料訪問層添加一些額外的邏輯,然後通過控制器將其向下傳遞給視圖,您可以解決這個問題。讓我們從資料訪問邏輯開始。

為了知道有沒有當前客戶集以外的記錄,您將需要在以 10 個為一組分頁前對查詢將會返回的所有可能的客戶進行計數。這裡就是在 GetPagedCustomers 中編寫查詢起作用的地方。注意,第一個查詢返回到 _customerQuery 中,這是一個在類層級聲明的變數,如以下所示:


  
  1. _customerQuery = context.Customers.Where(c => c.SalesOrderHeaders.Any());

您可以將 Count 方法附加在該查詢的末尾,以在應用分頁之前獲得匹配查詢的客戶的計數。Count 方法會強制立即執行一個相當簡單的查詢。以下就是在 SQL Server 中執行的查詢,這個查詢會返回一個值作為響應:


  
  1. SELECT
  2. [GroupBy1].[A1] AS [C1]
  3. FROM ( SELECT
  4. COUNT(1) AS [A1]
  5. FROM [SalesLT].[Customer] AS [Extent1]
  6. WHERE EXISTS (SELECT
  7. 1 AS [C1]
  8. FROM [SalesLT].[SalesOrderHeader] AS [Extent2]
  9. WHERE [Extent1].[CustomerID] = [Extent2].[CustomerID]
  10. )
  11. ) AS [GroupBy1]

您得到計數後,可以確定當前客戶頁是第一頁、最後一頁還是二者中間的頁。然後,您可以使用該邏輯確定要顯示哪些連結。例如,如果您在第一頁客戶之後,則當然要顯示一個連結來訪問之前的客戶資料頁面,而這個連結為前一頁,例如。“<<”。

我們可以計算值以在資料訪問類中表示此邏輯,然後將其在封裝類中和客戶一起公開。以下我們將要使用的新類:


  
  1. public class PagedList<T>
  2. {
  3. public bool HasNext { get; set; }
  4. public bool HasPrevious { get; set; }
  5. public List<T> Entities { get; set; }
  6. }

GetPagedCustomers 方法現在將返回 PagedList 類,而不是 List。 圖 2 顯示新版 GetPagedCustomers。

圖 2 新版 GetPagedCustomers


  
  1. public static PagedList<Customer> GetPagedCustomers(int skip, int take)
  2. {
  3. using (var context = new AdventureWorksLTEntities())
  4. {
  5. var query = context.Customers.Include("SalesOrderHeaders")
  6. .Where(c => c.SalesOrderHeaders.Any())
  7. .OrderBy(c => c.CompanyName + c.LastName + c.FirstName);
  8. var customerCount = query.Count();
  9. var customers = query.Skip(skip).Take(take).ToList();
  10. return new PagedList<Customer>
  11. {
  12. Entities = customers,
  13. HasNext = (skip + 10 < customerCount),
  14. HasPrevious = (skip > 0)
  15. };
  16. }
  17. }

填充新的變數後,讓我們看一看 HomeController 中 Index 方法如何能將它們推回視圖。在這裡,您可以使用 ViewBag。我們將仍在視圖中返回客戶查詢的結果,但是您可以額外補充一些值,以協助確定 ViewBag 中下一頁和上一頁連結的標記是什麼樣的。它們隨後可在運行時用於該視圖:


  
  1. public ActionResult Index(int?
  2. page)
  3. {
  4. const int pageSize = 10;
  5. var customers=DataAccess.GetPagedCustomers((page ??
  6. 0)*pageSize, pageSize);
  7. ViewBag.HasPrevious = DataAccess.HasPreviousCustomers;
  8. ViewBag.HasMore = DataAccess.HasMoreCustomers;
  9. ViewBag.CurrentPage = (page ??
  10. 0);
  11. return View(customers);
  12. }

重要的是,必須瞭解 ViewBag 是動態,而不是強型別的。ViewBag 並不真的帶有 HasPrevious 和 HasMore。我剛剛在鍵入代碼時才建立了它們。所以不要吃驚 IntelliSense 沒有向您提供提示。您可以建立自己喜歡的任何動態屬性。

如果您已經在使用 ViewPage.ViewData 字典並且疑惑這有什麼不同,會發現 ViewBag 實際 執行相同的作業。除了使代碼變得美觀外,還鍵入了屬性。例如,HasNext 是 dynamic{bool},而 CurrentPage 是 dynamic{int}。以後檢索這些值時,就不用再轉換它們了。

在標記中,我在 Model 變數中仍有一個客戶列表,但還有一個 ViewBag 變數。您要自己在標記中鍵入動態屬性。工具提示會提醒您屬性是動態,如圖 3 所示。

圖 3 ViewBag 屬性不會通過 IntelliSense 提供,因為它們是動態屬性

以下是使用 ViewBag 變數確定是否顯示導航連結的標記:


  
  1. @{ if (ViewBag.HasPrevious)
  2. {
  3. @Html.ActionLink("<<", "Index", new { page = (ViewBag.CurrentPage - 1) })
  4. }
  5. }
  6. @{ if (ViewBag.HasMore)
  7. { @Html.ActionLink(">>", "Index", new { page = (ViewBag.CurrentPage + 1) })
  8. }
  9. }

此邏輯是對 NerdDinner 應用程式教程中所用標記的一種修改,這個教程位於 nerddinnerbook.s3.amazonaws.com/Intro.htm 上。

現在當我運行應用程式時,我就可以從一頁客戶導般到下一頁客戶。

如果我在第一頁上時,我有一個可以導航到下一頁的連結,但是沒有導航到前一頁的連結,因為沒有前一頁(請參見圖 4)。

圖 4 第一頁客戶只有一個導航到下一頁的連結

當我單擊該連結並導航到下一頁時,您可以看到這裡分別有轉到上一頁和下一頁的連結(請參見圖 5)。

圖 5 具有導航到上一頁或下一頁客戶的導航連結的單個客戶頁

當然,下一步將是使用設計器使這個分頁更具吸引力。

工具箱中不可或缺的部分

總而言之,儘管有多種工具可以簡化用戶端分頁,如 jQuery DataTables 擴充和新的 ASP.NET MVC 3 WebGrid,您的應用程式需求卻不一定能從帶回大量資料中獲益。能夠執行有效伺服器端分頁是工具箱中不可或缺的部分。Entity Framework 和 ASP.NET MVC 共同協作,能夠提供優秀的使用者體驗,同時也能夠簡化您的開發工作單位,以至成功完成任務。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.