ASP.NET Core分页实现抛出InvalidOperationException错误排查
错误场景
加载https://localhost:7242/product页面时触发以下错误,页面需实现分页功能:
InvalidOperationException: 传入ViewDataDictionary的模型项类型为'System.Linq.Enumerable+
d__231 3[<>f__AnonymousType02[WebApplication3105.Models.Product,System.Collections.Generic.IEnumerable1[WebApplication3105.Models.Category]],WebApplication3105.Models.Category,WebApplication3105.Models.ProductCategory]',但此ViewDataDictionary实例需要类型为'X.PagedList.IPagedList1[WebApplication3105.Models.ProductCategory]'的模型项。
相关代码
模型类
namespace WebApplication3105.Models { public class Product { public int ProductId { get; set; } public string ProductName { get; set; } public int CategoryId { get; set; } public Category Category { get; set; } } } namespace WebApplication3105.Models { public class Category { public int CategoryId { get; set; } public string CategoryName { get; set; } public ICollection<Product> Product { get; set; } } } namespace WebApplication3105.Models { public class ProductCategory { public Product product { get; set; } public Category category { get; set; } } }
控制器代码
using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using PagedList.Mvc; using WebApplication3105.Data; using WebApplication3105.Models; using X.PagedList; namespace WebApplication3105.Controllers { public class ProductController : Controller { private readonly MVCDbContext dbContext; public ProductController(MVCDbContext dbContext) { this.dbContext = dbContext; } [HttpGet] public async Task<IActionResult> Index(int? page) { List<Product> products = dbContext.Product.ToList(); List<Category> categories = dbContext.Category.ToList(); int recordsPerPage = 2; var data = from p in products join c in categories on p.CategoryId equals c.CategoryId into table1 from c in table1.ToList().ToPagedList(page ?? 1, recordsPerPage) select new ProductCategory { product = p, category = c }; return View(data); } [HttpGet] public IActionResult AddProduct() { return View(); } [HttpPost] public async Task<IActionResult> AddProduct(Product productRequest) { var product = new Product() { ProductName = productRequest.ProductName, CategoryId = productRequest.CategoryId }; await dbContext.Product.AddAsync(product); await dbContext.SaveChangesAsync(); return RedirectToAction("Index"); } } }
视图页面代码
@* For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 * @{} @using X.PagedList.Mvc.Core; @using X.PagedList @model IPagedList<WebApplication3105.Models.ProductCategory> <h1 class="text-center text-uppercase"> List of products </h1> <table class="table table-striped table-bordered"> <thead class="bg-dark text-white"> <tr> <th>Product Id</th> <th>Product Name</th> <th>Category Id</th> <th>Category Name</th> </tr> </thead> <tbody> @foreach (var data in Model) { <tr> <th>@Html.DisplayFor(modelItem =>data.product.ProductId)</th> <th>@Html.DisplayFor(modelItem =>data.product.ProductName)</th> <th>@Html.DisplayFor(modelItem =>data.product.CategoryId)</th> <th>@Html.DisplayFor(modelItem =>data.category.CategoryName)</th> </tr> } </tbody> </table> <div> <div class="pagedList"> @Html.PagedListPager(Model, page => Url.Action("Index", new { page })) </div> </div>
错误原因
控制器中分页逻辑错误:在join后的table1上调用ToPagedList,导致最终返回的data是Linq的SelectManyIterator类型,而非视图所需的IPagedList<ProductCategory>。同时,提前调用ToList()会把所有数据加载到内存,性能低下。
修复方案
修改控制器的Index方法,先正确关联Product和Category,再对整体结果应用分页:
[HttpGet] public async Task<IActionResult> Index(int? page) { int recordsPerPage = 2; int pageNumber = page ?? 1; // 直接通过EF关联查询,避免提前加载所有数据到内存 var productCategories = await dbContext.Product .Join(dbContext.Category, p => p.CategoryId, c => c.CategoryId, (p, c) => new ProductCategory { product = p, category = c }) .ToPagedListAsync(pageNumber, recordsPerPage); return View(productCategories); }
额外优化
也可以利用EF的导航属性简化查询,代码更简洁:
[HttpGet] public async Task<IActionResult> Index(int? page) { int recordsPerPage = 2; int pageNumber = page ?? 1; var productCategories = await dbContext.Product .Include(p => p.Category) // 加载关联的Category .Select(p => new ProductCategory { product = p, category = p.Category }) .ToPagedListAsync(pageNumber, recordsPerPage); return View(productCategories); }
修改后,返回给视图的就是IPagedList<ProductCategory>类型的模型,与视图要求一致,同时分页逻辑正确,还能利用EF的查询优化减少内存占用。
内容的提问来源于stack exchange,提问作者ashish yadav

