【发布时间】:2020-04-20 16:06:24
【问题描述】:
我在 Azure 中的 Web API 存在一些 TTFB(待处理、等待)问题,我找不到原因,因为这两个操作使用相同的机制来返回分页列表的结果
Controller "Post" => Posts as PagedList => ~ 70-100ms (~80ms in local dev environment)
Controller "Page" => Pages as PagedList => ~ 1.100-1.200ms (~80ms in local dev environment)
我在使用分页列表的所有 3 个 API 中都有这些问题。有些分页列表操作可以正常工作,有些则不行。运行速度快的调用总是相同的,运行缓慢的调用总是相同的。
PagedList,基于微软推荐:
public class PagedList<T> : List<T>
{
public int CurrentPage { get; set; }
public int TotalPages { get; set; }
public int PageSize { get; set; }
public int TotalCount { get; set; }
public PagedList(List<T> items, int count, int currentPage, int pageSize)
{
TotalCount = count;
PageSize = pageSize;
CurrentPage = currentPage;
TotalPages = (int)Math.Ceiling(count / (double)pageSize);
this.AddRange(items);
}
public bool HasPreviousPage
{
get { return (CurrentPage > 1); }
}
public bool HasNextPage
{
get { return (CurrentPage < TotalPages); }
}
public static async Task<PagedList<T>> CreateAsync(IQueryable<T> source, int pageIndex, int pageSize)
{
var count = await source.CountAsync();
var items = await source.Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
return new PagedList<T>(items, count, pageIndex, pageSize);
}
}
分页类
public class Pagination
{
private const int MaxPageSize = 50;
public int PageNumber { get; set; } = 1;
private int pageSize = 10;
public int PageSize
{
get { return pageSize; }
set { pageSize = (value > MaxPageSize) ? MaxPageSize : value; }
}
}
public class BlogListPagination : Pagination
{
public string SearchString { get; set; } = "";
public int CategoryId { get; set; }
public int TagId { get; set; }
}
public class PageListPagination : Pagination
{
public string SearchString { get; set; } = "";
}
后控制器
[HttpGet]
public async Task<ActionResult> PostsAsPagedList([FromQuery]BlogListPagination paginationParams)
{
var posts = await _postService.GetPostsAsPagedList(paginationParams);
var result = _mapper.Map<IEnumerable<PostShortListDto>>(posts);
Response.AddPagination(posts.CurrentPage, posts.PageSize, posts.TotalCount, posts.TotalPages);
if (posts == null)
return BadRequest();
else
return Ok(result);
}
发布Dto
public class PostShortListDto
{
public int Id { get; set; }
public string Slug { get; set; }
public string Title { get; set; }
public string Abstract { get; set; }
public string Author { get; set; }
public DateTime Created { get; set; }
public string Thumbnail { get; set; }
public ICollection<CategoryDto> PostCategories { get; set; }
}
邮政服务
public async Task<PagedList<Post>> GetPostsAsPagedList(BlogListPagination paginationParams)
{
IQueryable<Post> result;
var posts = _context.Posts
.Include(x => x.PostCategories)
.ThenInclude(pc => pc.Category)
.Include(x => x.PostTags)
.ThenInclude(pt => pt.Tag)
.AsQueryable();
if (!string.IsNullOrEmpty(paginationParams.SearchString))
{
var cleanSearch = StripInput(paginationParams.SearchString);
posts = posts.Where(x => x.Title.Contains(cleanSearch) || x.Content.Contains(cleanSearch));
}
if (paginationParams.CategoryId != 0)
{
posts = posts.Where(x => x.PostCategories.Any(y => y.CategoryId == paginationParams.CategoryId));
}
if (paginationParams.TagId != 0)
{
posts = posts.Where(x => x.PostTags.Any(y => y.TagId == paginationParams.TagId));
}
result = posts;
result = result.OrderByDescending(x => x.Created);
return await PagedList<Post>.CreateAsync(result, paginationParams.PageNumber, paginationParams.PageSize);
}
页面控制器
[HttpGet]
public async Task<ActionResult> PagesAsPagedList([FromQuery]PageListPagination paginationParams)
{
var pages = await _pageService.GetPagesAsPagedList(paginationParams);
var result = _mapper.Map<IEnumerable<PageShortDto>>(pages);
Response.AddPagination(pages.CurrentPage, pages.PageSize, pages.TotalCount, pages.TotalPages);
if (pages == null)
return BadRequest();
else
return Ok(result);
}
页面Dto
public class PageShortDto
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime Created { get; set; }
}
页面服务
public async Task<PagedList<Page>> GetPagesAsPagedList(PageListPagination paginationParams)
{
IQueryable<Page> result;
var pages = _context.Pages.AsQueryable();
if (!string.IsNullOrEmpty(paginationParams.SearchString))
{
var cleanSearch = StripInput(paginationParams.SearchString);
pages = pages.Where(x => x.Title.Contains(cleanSearch) || x.Content.Contains(cleanSearch));
}
result = pages;
result = result.OrderByDescending(x => x.Created);
return await PagedList<Page>.CreateAsync(result, paginationParams.PageNumber, paginationParams.PageSize);
}
启动
services.AddTransient<IPostService, PostService>();
services.AddTransient<IPageService, PageService>();
虽然 Post 控制器中的调用更复杂,但它的执行速度快了 10 倍以上。
在 Azure 诊断中一切正常,没有显示错误。资源也不错。
您可以在浏览器中使用这 2 个 GET 请求或例如在此处检查行为。邮递员:
https://kombasapicontent.azurewebsites.net/api/post
https://kombasapicontent.azurewebsites.net/api/page
目前我不知道可能是什么原因,因为请求的行为总是相同的。
编辑(2 个请求的调试输出):
帖子 => 数据库中的 33 个条目
Pages => 数据库中有 173 个条目
所以我们不谈论“海量”数据。
作为分页列表的帖子
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5011/api/post
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'komBAS.API.Content.Controllers.PostController.PostsAsPagedList (komBAS.API.Content)'
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "PostsAsPagedList", controller = "Post"}. Executing action komBAS.API.Content.Controllers.PostController.PostsAsPagedList (komBAS.API.Content)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method komBAS.API.Content.Controllers.PostController.PostsAsPagedList (komBAS.API.Content) with arguments (komBAS.API.Content.Models.Pagination.BlogListPagination) - Validation state: Valid
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core 2.2.0-rtm-35687 initialized 'DataContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (2ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT COUNT(*)
FROM [Posts] AS [x]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (3ms) [Parameters=[@__p_0='?' (DbType = Int32), @__p_1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [x].[Id], [x].[Abstract], [x].[Author], [x].[Content], [x].[Created], [x].[MetaDescription], [x].[MetaKeywords], [x].[PostType], [x].[ShowSocialSharing], [x].[Slug], [x].[Status], [x].[Thumbnail], [x].[Title]
FROM [Posts] AS [x]
ORDER BY [x].[Created] DESC, [x].[Id]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (2ms) [Parameters=[@__p_0='?' (DbType = Int32), @__p_1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [x.PostCategories].[PostId], [x.PostCategories].[CategoryId], [p.Category].[Id], [p.Category].[Description], [p.Category].[IsVisible], [p.Category].[Name]
FROM [PostCategories] AS [x.PostCategories]
INNER JOIN [Categories] AS [p.Category] ON [x.PostCategories].[CategoryId] = [p.Category].[Id]
INNER JOIN (
SELECT [x0].[Id], [x0].[Created]
FROM [Posts] AS [x0]
ORDER BY [x0].[Created] DESC, [x0].[Id]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
) AS [t] ON [x.PostCategories].[PostId] = [t].[Id]
ORDER BY [t].[Created] DESC, [t].[Id]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (1ms) [Parameters=[@__p_0='?' (DbType = Int32), @__p_1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [x.PostTags].[PostId], [x.PostTags].[TagId], [p.Tag].[Id], [p.Tag].[Name]
FROM [PostTags] AS [x.PostTags]
INNER JOIN [Tags] AS [p.Tag] ON [x.PostTags].[TagId] = [p.Tag].[Id]
INNER JOIN (
SELECT [x1].[Id], [x1].[Created]
FROM [Posts] AS [x1]
ORDER BY [x1].[Created] DESC, [x1].[Id]
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
) AS [t0] ON [x.PostTags].[PostId] = [t0].[Id]
ORDER BY [t0].[Created] DESC, [t0].[Id]
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method komBAS.API.Content.Controllers.PostController.PostsAsPagedList (komBAS.API.Content), returned result Microsoft.AspNetCore.Mvc.OkObjectResult in 58.0013ms.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'System.Collections.Generic.List`1[[komBAS.API.Content.Dtos.PostShortListDto, komBAS.API.Content, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action komBAS.API.Content.Controllers.PostController.PostsAsPagedList (komBAS.API.Content) in 72.2734ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'komBAS.API.Content.Controllers.PostController.PostsAsPagedList (komBAS.API.Content)'
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 89.6201ms 200 application/json; charset=utf-8
Pages as Pages List(这是 Azure 中速度较慢的一种)
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[1]
Request starting HTTP/1.1 GET https://localhost:5011/api/page
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[0]
Executing endpoint 'komBAS.API.Content.Controllers.PageController.PagesAsPagedList (komBAS.API.Content)'
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Route matched with {action = "PagesAsPagedList", controller = "Page"}. Executing action komBAS.API.Content.Controllers.PageController.PagesAsPagedList (komBAS.API.Content)
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[1]
Executing action method komBAS.API.Content.Controllers.PageController.PagesAsPagedList (komBAS.API.Content) with arguments (komBAS.API.Content.Models.Pagination.PageListPagination) - Validation state: Valid
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
Entity Framework Core 2.2.0-rtm-35687 initialized 'DataContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: None
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (11ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT COUNT(*)
FROM [Pages] AS [x]
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (2ms) [Parameters=[@__p_0='?' (DbType = Int32), @__p_1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SELECT [x].[Id], [x].[Content], [x].[ContentTag], [x].[Created], [x].[MetaDescription], [x].[MetaKeywords], [x].[NoIndex], [x].[PageType], [x].[Slug], [x].[Status], [x].[Title]
FROM [Pages] AS [x]
ORDER BY [x].[Created] DESC
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action method komBAS.API.Content.Controllers.PageController.PagesAsPagedList (komBAS.API.Content), returned result Microsoft.AspNetCore.Mvc.OkObjectResult in 34.3177ms.
info: Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor[1]
Executing ObjectResult, writing value of type 'System.Collections.Generic.List`1[[komBAS.API.Content.Dtos.PageShortDto, komBAS.API.Content, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'.
info: Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker[2]
Executed action komBAS.API.Content.Controllers.PageController.PagesAsPagedList (komBAS.API.Content) in 44.738ms
info: Microsoft.AspNetCore.Routing.EndpointMiddleware[1]
Executed endpoint 'komBAS.API.Content.Controllers.PageController.PagesAsPagedList (komBAS.API.Content)'
info: Microsoft.AspNetCore.Hosting.Internal.WebHost[2]
Request finished in 59.5634ms 200 application/json; charset=utf-8
更新:已解决
这个问题似乎和.NET Core 3.0中Nlog的使用有关。
虽然应用正在运行并且 Azure GUI 中未显示任何错误,但日志显示此错误:
2020-01-07 12:08:12.3741|11|FATAL|Microsoft.AspNetCore.Hosting.Diagnostics|Hosting startup assembly exception System.InvalidOperationException: Startup assembly Microsoft.AspNetCore.AzureAppServices.HostingStartup failed to execute. See the inner exception for more details.
---> System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.AspNetCore.AzureAppServices.HostingStartup, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
解决办法是添加
Microsoft.AspNetCore.AzureAppServices.HostingStartup
到您的项目,错误和延迟执行消失了。
我在所有 3 个 API 中都对此进行了测试,并且对所有这些 API 都有效。
【问题讨论】:
-
向数据库发送什么查询?看起来可能很贵。
-
我从本地环境中为 2 个调用添加了调试日志。第二个是 Azure 中的慢速。
标签: c# .net-core entity-framework-core asp.net-core-webapi