如何实现分页和排序以产生良好的性能
如何使用.NETCoreRazor页面,WebAPI和实体框架实现分页和排序以产生良好的性能。
该项目的特点是:
选择页面大小
页面导航
显示记录号
列排序
核心课程第一件事是定义用户可以要求应用程序获取的内容:
页面大小
页码
字段排序
排序方向
代码如下所示:
public class PageSortParam
{
public int PageSize { get; set; } = 10; //default page size
public int CurrentPage { get; set; } = 1;
public string SortField { get; set; } = null;
public SortDirection SortDir { get; set; }
}
public enum SortDirection
{
Ascending = 0, //default as ascending
Decending
}1234567891011121314复制代码类型:[html]
接下来,我们定义应用程序应返回的内容,如下所示:
记录总数
总页数
前一页编号-用户导航到前一页时的编号
下一页编号-用于导航到下一页
当前页面上的第一个记录号
当前页面上的最后一条记录号
代码如下所示:
public class PageSortResult
{
public int TotalCount { get; set; } = 0;
public int TotalPages { get; set; } = 1;
public int? PreviousPage { get; set; }
public int? NextPage { get; set; }
public int FirstRowOnPage { get; set; }
public int LastRowOnPage { get; set; }
}123456789复制代码类型:[html]
使用user参数和定义的结果,我们创建PageList<T>继承自的类,List<T>以便将结果存储在中List。该类将使用参数并找到结果。
下面显示了代码以及该GetData()方法所需的逻辑。从数据库获取记录的行是对的调用ToListAsync(),它将跳过调用不需要的记录,Skip()而仅通过调用获取所需的记录Take():
public class PageList<T> : List<T>
{
public PageSortParam Param { get; }
public PageSortResult Result { get; }
public PageList(PageSortParam param)
{
Param = param;
Result = new PageSortResult();
}
public async Task GetData(IQueryable<T> query)
{
//get the total count
Result.TotalCount = await query.CountAsync();
//find the number of pages
Result.TotalPages = (int)Math.Ceiling(Result.TotalCount / (double)Param.PageSize);
//find previous and next page number
if (Param.CurrentPage - 1 > 0)
Result.PreviousPage = Param.CurrentPage - 1;
if (Param.CurrentPage 1 <= Result.TotalPages)
Result.NextPage = Param.CurrentPage 1;
//find first row and last row on the page
if (Result.TotalCount == 0) //if no record found
Result.FirstRowOnPage = Result.LastRowOnPage = 0;
else
{
Result.FirstRowOnPage = (Param.CurrentPage - 1) * Param.PageSize 1;
Result.LastRowOnPage =
Math.Min(Param.CurrentPage * Param.PageSize, Result.TotalCount);
}
//if has sorting criteria
if (Param.SortField != null)
query = query.OrderBy(Param.SortField
(Param.SortDir == SortDirection.Ascending ? " ascending" : " descending"));
List<T> list = await query.Skip((Param.CurrentPage - 1) *
Param.PageSize).Take(Param.PageSize).ToListAsync();
AddRange(list); //add the list of items
}
}123456789101112131415161718192021222324252627282930313233343536373839404142复制代码类型:[html]
数据层
的定义Customer在数据层项目中定义:
public class Customer
{
[Required]
public int CustomerId { get; set; }
[Required, StringLength(80)]
public string FirstName { get; set; }
[Required, StringLength(80)]
public string LastName { get; set; }
}12345678910复制代码类型:[html]
我们将定义常规CRUD的接口和实现,唯一的区别是该Get()方法将使用PageList<T>该类来仅获取所需的记录,从而通过将工作推送到数据库来提高性能。下面是界面:
public interface ICustomerData
{
Task<PageList<Customer>> Get(PageSortParam pageSort);
Task<Customer> GetCustomerById(int customerId);
Task<Customer> Update(int customerId, Customer customer);
Task<Customer> Add(Customer customer);
Task<int> Delete(int customerId);
}12345678复制代码类型:[html]
并执行:
public class SqlCustomerData : ICustomerData
{
public StarterDbContext DbContext { get; }
public SqlCustomerData(StarterDbContext dbContext)
{
DbContext = dbContext;
}
public async Task<Customer> Add(Customer customer)
{
DbContext.Add(customer);
await DbContext.SaveChangesAsync();
return customer;
}
public async Task<int> Delete(int customerId)
{
Customer c = await this.GetCustomerById(customerId);
if (c != null)
{
this.DbContext.Remove(c);
await DbContext.SaveChangesAsync();
return customerId;
}
return -1;
}
public async Task<PageList<Customer>> Get(PageSortParam pageSortParam)
{
PageList<Customer> list = new PageList<Customer>(pageSortParam);
await list.GetData(DbContext.Customer);
return list;
}
public async Task<Customer> GetCustomerById(int customerId)
{
Customer c = await this.DbContext.Customer.FindAsync(customerId);
if (c != null)
return c;
return null;
}
public async Task<Customer> Update(int customerId, Customer customer)
{
Customer c = await GetCustomerById(customerId);
if (c != null)
{
c.FirstName = customer.FirstName;
c.LastName = customer.LastName;
await DbContext.SaveChangesAsync();
return c;
}
return null;
}
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657复制代码类型:[html]
在DbContext从实体框架是简单的:
public class StarterDbContext : DbContext
{
public DbSet<Customer> Customer { get; set; }
public StarterDbContext(DbContextOptions<StarterDbContext> options)
: base(options)
{
}
}123456789复制代码类型:[html]
API
在WebAPI项目中,我们定义GetCustomers()将接受PageSortParam作为参数的Get()方法,在数据层中调用该方法,从PageSortResult响应标头中添加来自的元数据(例如记录总数,页面总数等)。,并在响应正文中提供实际记录:
[Route("api/[controller]")]
[ApiController]
public class CustomerController : ControllerBase
{
public ICustomerData CustomerData { get; }
public CustomerController(ICustomerData customerData)
{
CustomerData = customerData;
}
// GET: api/Customer
[HttpGet]
public async Task<ActionResult<IEnumerable<Customer>>>
GetCustomers([FromQuery] PageSortParam pageSortParam)
{
PageList<Customer> list = await this.CustomerData.Get(pageSortParam);
//return result metadata in the header
Response.Headers.Add("X-PageSortResult", JsonSerializer.Serialize(list.Result));
return Ok(list);
}
// GET: api/Customer/5
[HttpGet("{customerId}")]
public async Task<ActionResult<Customer>> GetCustomer(int customerId)
{
return Ok(await this.CustomerData.GetCustomerById(customerId));
}
// PUT: api/Customer/5
[HttpPut("{customerId}")]
public async Task<ActionResult<Customer>>
PutCustomer(int customerId, Customer customer)
{
return Ok(await this.CustomerData.Update(customerId, customer));
}
// POST: api/Customer
[HttpPost]
public async Task<ActionResult<Customer>> PostCustomer(Customer customer)
{
return Ok(await this.CustomerData.Add(customer));
}
// DELETE: api/Customer/5
[HttpDelete("{customerId}")]
public async Task<ActionResult<int>> DeleteCustomer(int customerId)
{
return Ok(await this.CustomerData.Delete(customerId));
}
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051复制代码类型:[html]
剃刀页
我们定义用户可以选择的页面大小:
public IEnumerable<SelectListItem> PageSizeList { get; set; } =
new SelectList(new List<int> { 5, 10, 25, 50 });12复制代码类型:[html]
然后,我们[BindProperty(SupportsGet=true)]将值传递给HTML页面,然后又将值取回。对于每次旅行,我们都需要从HTML页面传递以下属性:
PageSize–用户请求的页面大小
PageNumber–用户当前所在的页码
SortField–用户要求进行排序的列
SortDir–应该排序的方向
SortDirNext–用户单击列链接时的下一个排序方向
[BindProperty(SupportsGet = true)]
public int? PageSize { get; set; }
[BindProperty(SupportsGet = true)]
public int PageNumber { get; set; } = 1;
[BindProperty(SupportsGet = true)]
public string SortField { get; set; }
[BindProperty(SupportsGet = true)]
public SortDirection SortDir { get; set; }
//for the next sort direction when the user clicks on the header
[BindProperty(SupportsGet = true)]
public SortDirection? SortDirNext { get; set; }123456789101112131415复制代码类型:[html]
该OnGet()方法将获取每个属性的值,构建参数并将其传递给API,然后显示记录并显示有关页面的元数据。以下是完整的代码:
public class ListModel : PageModel
{
public IEnumerable<Dto.Customer> CustomerList { get; set; }
private readonly IConfiguration config;
public IEnumerable<SelectListItem>
PageSizeList { get; set; } = new SelectList(new List<int> { 5, 10, 25, 50 });
public PageSortParam PageSortParam { get; set; } = new PageSortParam();
public PageSortResult PageSortResult { get; set; }
[BindProperty(SupportsGet = true)]
public int? PageSize { get; set; }
[BindProperty(SupportsGet = true)]
public int PageNumber { get; set; } = 1;
[BindProperty(SupportsGet = true)]
public string SortField { get; set; }
[BindProperty(SupportsGet = true)]
public SortDirection SortDir { get; set; }
//for the next sort direction when the user clicks on the header
[BindProperty(SupportsGet = true)]
public SortDirection? SortDirNext { get; set; }
public ListModel(IConfiguration config)
{
this.config = config;
}
public async Task OnGet()
{
if (PageSize.HasValue)
PageSortParam.PageSize = (int)PageSize;
PageSortParam.CurrentPage = PageNumber;
//if never sorted
if (SortField == null)
SortDir = new SortDirection();
else if (SortDirNext != null) //if requested new sort direction
SortDir = (SortDirection)SortDirNext;
//SortDirNext will be the reverse of SortDir
SortDirNext = SortDir == SortDirection.Ascending ?
SortDirection.Decending : SortDirection.Ascending;
PageSortParam.SortField = SortField;
PageSortParam.SortDir = SortDir;
HttpResponseMessage response = await new HttpClient().GetAsync
(this.config["APIurl"] "Customer?PageSize=" PageSortParam.PageSize
"&CurrentPage=" PageSortParam.CurrentPage
"&SortField=" PageSortParam.SortField
"&SortDir=" PageSortParam.SortDir);
//display the list of customers
if (response.IsSuccessStatusCode)
CustomerList = await response.Content.ReadAsAsync<IEnumerable<Dto.Customer>>();
//get the paging meta data from the header
IEnumerable<string> headerValue;
if (response.Headers.TryGetValues("X-PageSortResult", out headerValue))
{
PageSortResult = JsonConvert.DeserializeObject<PageSortResult>
(headerValue.First());
}
}
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869复制代码类型:[html]
html页面将通过使用http提交表单get或单击链接来接受用户的输入。请注意,参数是在每个动作中传递的。在列标题链接中仅指定排序列的名称和方向:
<div>
<div>
<table class="table table-bordered table-hover table-sm w-auto">
<caption>Items @Model.PageSortResult.FirstRowOnPage
to @Model.PageSortResult.LastRowOnPage</caption>
<thead class="thead-light">
<tr>
<th scope="col">
<a asp-page="./Edit" asp-route-customerId="0">
<i class="material-icons icon">add_box</i>
</a>
</th>
<th scope="colgroup" colspan="4" class="text-right">
<form method="get">
Page Size:
@Html.DropDownListFor(m => m.PageSize, Model.PageSizeList,
"-Select-", new { onchange = "submit()" })
<input type="hidden" name="PageNumber" value="1" />
<input type="hidden" name="SortField" value="@Model.SortField" />
<input type="hidden" name="SortDir" value="@Model.SortDir" />
</form>
</th>
</tr>
<tr>
<th scope="col" class="pl-2 pr-2">
<a asp-page="./List"
asp-route-SortField="CustomerId"
asp-route-SortDir="@Model.SortDir"
asp-route-SortDirNext="@Model.SortDirNext"
asp-route-PageSize="@Model.PageSize"
asp-route-PageNumber="@Model.PageNumber">
Customer ID
</a>
</th>
<th scope="col" class="pl-2 pr-2">
<a asp-page="./List"
asp-route-SortField="FirstName"
asp-route-SortDir="@Model.SortDir"
asp-route-SortDirNext="@Model.SortDirNext"
asp-route-PageSize="@Model.PageSize"
asp-route-PageNumber="@Model.PageNumber">
First Name
</a>
</th>
<th scope="col" class="pl-2 pr-2">
<a asp-page="./List"
asp-route-SortField="LastName"
asp-route-SortDir="@Model.SortDir"
asp-route-SortDirNext="@Model.SortDirNext"
asp-route-PageSize="@Model.PageSize"
asp-route-PageNumber="@Model.PageNumber">
Last Name
</a>
</th>
<th scope="col"></th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@foreach (var c in Model.CustomerList)
{
<tr>
<td class="pl-2 pr-2">@c.CustomerId</td>
<td class="pl-2 pr-2">@c.FirstName</td>
<td class="pl-2 pr-2">@c.LastName</td>
<td class="td-center pl-2 pr-2">
<a asp-page="./Edit" asp-route-customerId="@c.CustomerId">
<i class="material-icons icon">edit</i>
</a>
</td>
<td class="td-center pl-2 pr-2">
<a asp-page="./Delete" asp-route-customerId="@c.CustomerId">
<i class="material-icons icon">delete</i>
</a>
</td>
</tr>
}
</tbody>
</table>
</div>
</div>
<div>
@{
var prev = Model.PageSortResult.PreviousPage.HasValue ? "" : "disabled";
var next = Model.PageSortResult.NextPage.HasValue ? "" : "disabled";
var first = Model.PageNumber != 1 ? "" : "disabled";
var last = Model.PageNumber != Model.PageSortResult.TotalPages ? "" : "disabled";
}
</div>
<a asp-page="./List"
asp-route-pageNumber="1"
asp-route-PageSize="@Model.PageSize"
asp-route-SortField="@Model.SortField"
asp-route-SortDir="@Model.SortDir"
class="btn @first">
<i class="material-icons icon">first_page</i>
</a>
<a asp-page="./List"
asp-route-pageNumber="@Model.PageSortResult.PreviousPage"
asp-route-PageSize="@Model.PageSize"
asp-route-SortField="@Model.SortField"
asp-route-SortDir="@Model.SortDir"
class="btn @prev">
<i class="material-icons icon">chevron_left</i>
</a>
Page @Model.PageNumber of @Model.PageSortResult.TotalPages
<a asp-page="./List"
asp-route-pageNumber="@Model.PageSortResult.NextPage"
asp-route-PageSize="@Model.PageSize"
asp-route-SortField="@Model.SortField"
asp-route-SortDir="@Model.SortDir"
class="btn @next">
<i class="material-icons icon">chevron_right</i>
</a>
<a asp-page="./List"
asp-route-pageNumber="@Model.PageSortResult.TotalPages"
asp-route-PageSize="@Model.PageSize"
asp-route-SortField="@Model.SortField"
asp-route-SortDir="@Model.SortDir"
class="btn @last">
<i class="material-icons icon">last_page</i>
</a>123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122复制代码类型:[html]
就这样!希望您发现这对构建页面调度和排序应用程序很有用。