目录
介绍
满足在海量数据源上通过REST API进行记录分页的需求。因此,我们使用REST API提出了这个解决方案,希望它也能对您的工作有所帮助。其目的是执行特定和多列搜索、排序、动态页面大小调整、具有优雅的代码结构、最少的编码工作,以及最重要的性能。
先决条件
为了学习本文,您需要对MVC框架有一定的了解。如果您认为自己有足够的专业知识,那么您就可以轻松地进一步阅读本文了。
抽象
该解决方案使用Swagger作为文档和LINQKit包。LINQKit用于构建灵活且高性能的查询。
客户请求
我们使用Postman查询我们的REST API,json请求结构如下:
{
"CurrentPage": 1,
"PageSize": 1,
"SearchText": "",
"SortColumn": "Id",
"SortDirection": "Desc",
"HasPagination": true,
"Columns": [
]
}
属性 |
描述 |
CurrentPage |
请求的页码应该在这个属性中定义,例如,要有第3页的数组响应,那么3应该被分配给它。 |
PageSize |
它定义了arraylist请求的返回大小或页面大小的大小。 |
SearchText |
要搜索所有文本列,应填充此属性,例如,在这种情况下,abbot将搜索所有文本列ContactPerson、Phone、City、Region和Country |
SortColumn |
我们需要用哪一列来排序我们的数据,例如按联系人排序,传递的值应该是 ContactPerson |
SortDirection |
对于升序排序,Asc 应该被传递,对于降序,Desc |
HasPagination |
要将这个查询产生的每一个数据返回为json,它需要是false,否则true 应该被传递。 |
Columns[] |
它是用于特定搜索的列的json数组列表,它包含Column对象数组。 |
Column |
这个json对象有两个属性,ColumnName和ColumnSearchText, 这些搜索提供了特定的列搜索功能,例如: 示例 1
它将返回余额小于等于50的所有客户。 示例 2
它将 仅返回来自国家/地区Pakistan的所有客户: 示例 3 您也可以进行多个场景搜索,例如:
上述json查询应返回customer余额小于等于50且 客户所在国家/地区仅为Pakistan。 |
通过Postman Client使用REST API
为了使用这个REST API,我们使用了postman。你可以从他们的网站下载。打开客户端后,将请求类型设置为Post ,在body选项卡中,选择下面选择的raw 并将请求内容类型设置为位于combobox最右边的JSON。
上述API请求返回余额大于5000000的客户,并为第1页分页
快照显示了来自国家/地区oman的所有客户。
上面的快照按客户ID编号按降序显示了记录。
REST API服务器端
在服务器端,我们有LINQKit来实现我们的愿望。在我们上面的例子中,它的控制器操作方法之一设置对象PaginationRequest,该对象将由客户端作为定义json对象传递。
[HttpPost]
[SwaggerResponseExample(HttpStatusCode.OK, typeof(CustomerPaginationResponseExample))]
[SwaggerRequestExample(typeof(PaginationRequest<customerpaginationgridcolumns>),
typeof(CustomerPaginationRequestExample), jsonConverter: typeof(StringEnumConverter))]
[ResponseType(typeof(PaginationResponse<customerpaginationmodel>))]
[HelperMethods.DeflateCompression]
[ValidateModelState]
[CheckModelForNull]
[SwaggerConsumes("application/json")]
[SwaggerProduces("application/json")]
[SwaggerResponse(HttpStatusCode.NotFound,
"No customer found", typeof(GenericResponseModel))]
[Route("")]
public async Task<system.web.http.ihttpactionresult>
Get(PaginationRequest<customerpaginationgridcolumns> request)
{
BusinessLayer.Entity.Customer obj = new BusinessLayer.Entity.Customer(this);
PaginationResponse<customerpaginationmodel> response = obj.Get(request).Result;
if (response.Items == null)
{
return APIResponse(HttpStatusCode.InternalServerError,
$"Error: {obj.errorMessage}");
}
else
if (response.Items.Count() == 0)
{
return APIResponse(HttpStatusCode.NotFound, $"No customer found");
}
return Ok(response);
}
深入研究我们的请求结构化的详细业务层方法如下。它使用LINQKit和实体框架作为我们的ORM动态地组成查询。它将所有列解析为与此业务实体关联的定义,并动态组装查询并同时实现分页。
public async Task<paginationresponse<customerpaginationmodel>>
Get(PaginationRequest<common.enum.customerpaginationgridcolumns> paginationRequest)
{
try
{
BusinessEntity.CustomerDBEntities obj =
new BusinessEntity.CustomerDBEntities();
records = (from cus in obj.customers.AsNoTracking()
// join count in obj.countries on
// cus.countryId equals count.countryId
select new CustomerPaginationModel
{
Id = cus.customerID,
ContactPerson = cus.contactPerson,
Phone = cus.phone,
Fax = cus.phone,
City = cus.city,
Region = cus.region,
Country = cus.countryName,
CountryId = cus.countryId,
Balance = cus.balance
}).AsQueryable();
if (paginationRequest.SortColumn != CustomerPaginationGridColumns.None)
{
InitSorting(paginationRequest);
}
else
{
paginationRequest.SortColumn = CustomerPaginationGridColumns.Id;
InitSorting(paginationRequest);
}
genericSearchText = paginationRequest.SearchText == null ?
null : paginationRequest.SearchText.Trim(); // set generic
// search value
ColumnParameter<common.enum.customerpaginationgridcolumns> column =
new ColumnParameter<customerpaginationgridcolumns>() { };
// Iterate through filter grid column to construct query predicate
// foreach (ColumnParameter<common.enum.customerpaginationgridcolumns>
// column in paginationRequest.Columns)
foreach (CustomerPaginationGridColumns columnParse in Enum.GetValues
(typeof(CustomerPaginationGridColumns)))
{
if (!string.IsNullOrEmpty(genericSearchText))
{
// these is no specific column search
if (paginationRequest.Columns.Where
(x => x.ColumnName == columnParse).Count() == 0)
{
column = new ColumnParameter<customerpaginationgridcolumns>()
{ ColumnName = columnParse, ColumnSearchText = "" };
}
else
{
column = paginationRequest.Columns.Where
(x => x.ColumnName == columnParse).FirstOrDefault();
}
}
else
{
column = paginationRequest.Columns.Where
(x => x.ColumnName == columnParse).FirstOrDefault();
}
if (column == null)
{
continue;
}
searchColumnText =
(column.ColumnSearchText ?? "").Trim(); // set current column
// search value
switch (column.ColumnName)
{
case Common.Enum.CustomerPaginationGridColumns.Balance:
EvaluateNumericComparisonFilter(paginationRequest, column,
searchColumnText,
"Balance",
x => x.Balance
);
break;
case Common.Enum.CustomerPaginationGridColumns.City:
EvaluateFilter(paginationRequest, column,
x => x.City.StartsWith(searchColumnText),
x => x.City.StartsWith(genericSearchText),
x => x.City
);
break;
case Common.Enum.CustomerPaginationGridColumns.ContactPerson:
EvaluateFilter(paginationRequest, column,
x => x.ContactPerson.StartsWith(searchColumnText),
x => x.ContactPerson.StartsWith(genericSearchText),
x => x.ContactPerson
);
break;
case Common.Enum.CustomerPaginationGridColumns.Country:
EvaluateFilter(paginationRequest, column,
x => x.Country.StartsWith(searchColumnText),
x => x.Country.StartsWith(genericSearchText),
x => x.Country
);
break;
case Common.Enum.CustomerPaginationGridColumns.CountryId:
if (!IsNumber(searchColumnText))
{
continue;
}
string type = searchColumnText;
EvaluateFilter(paginationRequest, column,
x => x.CountryId == type,
null,
x => x.CountryId
);
break;
case Common.Enum.CustomerPaginationGridColumns.Fax:
EvaluateFilter(paginationRequest, column,
x => x.Fax.StartsWith(searchColumnText),
x => x.Fax.StartsWith(genericSearchText),
x => x.Fax
);
break;
case Common.Enum.CustomerPaginationGridColumns.Phone:
EvaluateFilter(paginationRequest, column,
x => x.Phone.StartsWith(searchColumnText),
x => x.Phone.StartsWith(genericSearchText),
x => x.Phone
);
break;
case Common.Enum.CustomerPaginationGridColumns.Region:
EvaluateFilter(paginationRequest, column,
x => x.Region.StartsWith(searchColumnText),
x => x.Region.StartsWith(genericSearchText),
x => x.Region
);
break;
}
}
PaginationResponse<customerpaginationmodel> response =
new PaginationResponse<customerpaginationmodel>();
IQueryable<customerpaginationmodel> countQuery = records;
response.Items = ForgeGridData(paginationRequest, x => x.ContactPerson).Result;
response.RecordsTotal = totalRows;
// Generating data
return response;
}
catch (Exception exp)
{
CompileExceptionHandleMessage(exp);
return new PaginationResponse<customerpaginationmodel>() { Items = null };
}
finally
{
records = null;
}
}
在解决方案错误的情况下
如果解决方案源代码拒绝编译,则重建解决方案,之后,如果您有运行时错误,请在主项目的包管理控制台中运行以下命令,然后就可以了。
Update-Package Microsoft.CodeDom.Providers.DotNetCompilerPlatform -r
https://www.codeproject.com/Articles/5297265/High-Performance-Pagination-REST-API-Query-Builder