【问题标题】:Using a PagedList with a ViewModel ASP.Net MVC将 PagedList 与 ViewModel ASP.Net MVC 一起使用
【发布时间】:2014-09-27 07:39:46
【问题描述】:

我正在尝试在我的 ASP.Net 应用程序中使用 PagedList,我在 Microsoft 网站 http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/sorting-filtering-and-paging-with-the-entity-framework-in-an-asp-net-mvc-application 上找到了这个示例

如何在使用 ViewModel 的复杂情况下使用 PagedList?我正在尝试将 PagedList 添加到此处发布的 Instructor 示例中,但未成功:http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/reading-related-data-with-the-entity-framework-in-an-asp-net-mvc-application

问题是 ViewModel 是由类而不是简单的字段组成的,所以我无法使用 ToPageList() 方法转换结果。

这是 ViewModel 结构:

using System.Collections.Generic;
using ContosoUniversity.Models;

namespace ContosoUniversity.ViewModels
{
    public class InstructorIndexData
    {
        public IEnumerable<Instructor> Instructors { get; set; }
        public IEnumerable<Course> Courses { get; set; }
        public IEnumerable<Enrollment> Enrollments { get; set; }
    }
}

我需要将三个表加入到 ViewModel 中,并在视图中显示结果。

【问题讨论】:

    标签: c# asp.net-mvc asp.net-mvc-viewmodel pagedlist


    【解决方案1】:

    您使用的是视图模型这一事实与此无关。使用PagedList 的标准方法是将“一页项目”存储为ViewBag 变量。您所要做的就是确定哪些集合构成了您将要分页的内容。你不能同时在逻辑上分页多个集合,所以假设你选择了Instructors

    ViewBag.OnePageOfItems = myViewModelInstance.Instructors.ToPagedList(pageNumber, 10);
    

    然后,标准代码的其余部分照常工作。

    【讨论】:

    • 我会加入 ViewModel 中的表格并使用 PagedList 在 View 中显示结果
    • 不确定你的意思。
    • 我用实体框架映射了三个表:Instructors、Courses 和 Enrollments。我需要显示来自 Instructors 的一些字段和来自 Courses 的一些字段,并使用 PagedList 在视图中显示结果
    【解决方案2】:

    正如 Chris 所说,您使用 ViewModel 的原因并不会阻止您使用 PagedList。 您需要形成一个 ViewModel 对象的集合,这些对象需要发送到视图以进行分页。

    这里是关于如何将PagedList 用于视图模型数据的分步指南。

    您的视图模型(为了简洁起见,我举了一个简单的例子,您可以轻松修改它以满足您的需求。

    public class QuestionViewModel
    {
            public int QuestionId { get; set; }
            public string QuestionName { get; set; }
    }
    

    您的控制器的 Index 方法将类似于

    public ActionResult Index(int? page)
    {
         var questions = new[] {
               new QuestionViewModel { QuestionId = 1, QuestionName = "Question 1" },
               new QuestionViewModel { QuestionId = 1, QuestionName = "Question 2" },
               new QuestionViewModel { QuestionId = 1, QuestionName = "Question 3" },
               new QuestionViewModel { QuestionId = 1, QuestionName = "Question 4" }
         };
    
         int pageSize = 3;
         int pageNumber = (page ?? 1);
         return View(questions.ToPagedList(pageNumber, pageSize));
    }
    

    还有你的索引视图

    @model PagedList.IPagedList<ViewModel.QuestionViewModel>
    @using PagedList.Mvc; 
    <link href="/Content/PagedList.css" rel="stylesheet" type="text/css" />
    
    
    <table>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.QuestionId)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.QuestionName)
            </td>
        </tr>
    }
    
    </table>
    
    <br />
    
    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    @Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )
    

    这里是SO link 和我的答案,其中包含有关如何使用PageList 的分步指南

    【讨论】:

    • 你能修改示例并使用我的 ViewModel 吗?因为问题是我无法使用该 ViewModel 进行分页。使用更简单的 ViewModel 即可。
    • @Itedone 我真的帮不上忙,除非您为您发布InstructorsCoursesEnrollments 类的成员属性。
    【解决方案3】:

    我修改代码如下:

    视图模型

    using System.Collections.Generic;
    using ContosoUniversity.Models;
    
    namespace ContosoUniversity.ViewModels
    {
        public class InstructorIndexData
        {
         public PagedList.IPagedList<Instructor> Instructors { get; set; }
         public PagedList.IPagedList<Course> Courses { get; set; }
         public PagedList.IPagedList<Enrollment> Enrollments { get; set; }
        }
    }
    

    控制器

    public ActionResult Index(int? id, int? courseID,int? InstructorPage,int? CoursePage,int? EnrollmentPage)
    {
     int instructPageNumber = (InstructorPage?? 1);
     int CoursePageNumber = (CoursePage?? 1);
     int EnrollmentPageNumber = (EnrollmentPage?? 1);
     var viewModel = new InstructorIndexData();
     viewModel.Instructors = db.Instructors
        .Include(i => i.OfficeAssignment)
        .Include(i => i.Courses.Select(c => c.Department))
        .OrderBy(i => i.LastName).ToPagedList(instructPageNumber,5);
    
     if (id != null)
     {
        ViewBag.InstructorID = id.Value;
        viewModel.Courses = viewModel.Instructors.Where(
            i => i.ID == id.Value).Single().Courses.ToPagedList(CoursePageNumber,5);
     }
    
     if (courseID != null)
     {
        ViewBag.CourseID = courseID.Value;
        viewModel.Enrollments = viewModel.Courses.Where(
            x => x.CourseID == courseID).Single().Enrollments.ToPagedList(EnrollmentPageNumber,5);
     }
    
     return View(viewModel);
    }
    

    查看

    <div>
       Page @(Model.Instructors.PageCount < Model.Instructors.PageNumber ? 0 : Model.Instructors.PageNumber) of @Model.Instructors.PageCount
    
       @Html.PagedListPager(Model.Instructors, page => Url.Action("Index", new {InstructorPage=page}))
    
    </div>
    

    希望对你有帮助!!

    【讨论】:

    • 如果 Instructors 有 100,000 条记录怎么办?它们都将被加载到内存中,以防万一需要加载课程? PageCount 是如何设置的?这些模型被用作某种内存数据库。没有设计。
    【解决方案4】:

    我想出了如何做到这一点。我正在构建一个与您在原始问题中讨论的示例/教程非常相似的应用程序。

    这是对我有用的代码的 sn-p:

            int pageSize = 4;
            int pageNumber = (page ?? 1);
            //Used the following two formulas so that it doesn't round down on the returned integer
            decimal totalPages = ((decimal)(viewModel.Teachers.Count() /(decimal) pageSize));     
            ViewBag.TotalPages = Math.Ceiling(totalPages);  
            //These next two functions could maybe be reduced to one function....would require some testing and building
            viewModel.Teachers = viewModel.Teachers.ToPagedList(pageNumber, pageSize);
            ViewBag.OnePageofTeachers = viewModel.Teachers;
            ViewBag.PageNumber = pageNumber;
    
            return View(viewModel);
    

    我加了

    using.PagedList;
    

    按照教程所述发送到我的控制器。

    现在在我看来,我的 using 语句等位于顶部,注意我没有更改我的 using 模型语句。

    @model CSHM.ViewModels.TeacherIndexData
    @using PagedList;
    @using PagedList.Mvc;
    <link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
    

    然后在底部构建我的分页列表,我使用了以下内容,它似乎有效。我还没有内置用于当前排序、显示相关数据、过滤器等的功能,但我认为这不会那么困难。

    Page @ViewBag.PageNumber of @ViewBag.TotalPages
    
    @Html.PagedListPager((IPagedList)ViewBag.OnePageofTeachers, page => Url.Action("Index", new { page }))
    

    希望对你有用。让我知道它是否有效!

    【讨论】:

    • 我应该补充一下,我发现这个链接真的很有帮助:github.com/TroyGoode/PagedList
    • 最有帮助的评论,谢谢伙计,我在添加你在那里写的这些标题时遇到了困难:)
    【解决方案5】:

    对于试图在不修改 ViewModel 并且不从数据库中加载所有记录的情况下尝试执行此操作的任何人。

    存储库

        public List<Order> GetOrderPage(int page, int itemsPerPage, out int totalCount)
        {
            List<Order> orders = new List<Order>();
            using (DatabaseContext db = new DatabaseContext())
            {
                orders = (from o in db.Orders
                          orderby o.Date descending //use orderby, otherwise Skip will throw an error
                          select o)
                          .Skip(itemsPerPage * page).Take(itemsPerPage)
                          .ToList();
                totalCount = db.Orders.Count();//return the number of pages
            }
            return orders;//the query is now already executed, it is a subset of all the orders.
        }
    

    控制器

        public ActionResult Index(int? page)
        {
            int pagenumber = (page ?? 1) -1; //I know what you're thinking, don't put it on 0 :)
            OrderManagement orderMan = new OrderManagement(HttpContext.ApplicationInstance.Context);
            int totalCount = 0;
            List<Order> orders = orderMan.GetOrderPage(pagenumber, 5, out totalCount);
            List<OrderViewModel> orderViews = new List<OrderViewModel>();
            foreach(Order order in orders)//convert your models to some view models.
            {
                orderViews.Add(orderMan.GenerateOrderViewModel(order));
            }
            //create staticPageList, defining your viewModel, current page, page size and total number of pages.
            IPagedList<OrderViewModel> pageOrders = new StaticPagedList<OrderViewModel>(orderViews, pagenumber + 1, 5, totalCount);
            return View(pageOrders);
        }
    

    查看

    @using PagedList.Mvc;
    @using PagedList; 
    
    @model IPagedList<Babywatcher.Core.Models.OrderViewModel>
    
    @{
        ViewBag.Title = "Index";
    }
    
    <h2>Index</h2>
    <div class="container-fluid">
        <p>
            @Html.ActionLink("Create New", "Create")
        </p>
        @if (Model.Count > 0)
        {
    
    
            <table class="table">
              <tr>
                <th>
                    @Html.DisplayNameFor(model => model.First().orderId)
                </th>
                <!--rest of your stuff-->
            </table>
    
        }
        else
        {
            <p>No Orders yet.</p>
        }
        @Html.PagedListPager(Model, page => Url.Action("Index", new { page }))
    </div>
    

    奖金

    先做上面,再用这个!

    由于这个问题是关于(查看)模型的,我将为您提供一个小解决方案,它不仅对分页有用,而且如果您想将实体分开,对应用程序的其余部分也有用,仅在存储库中使用,并让应用程序的其余部分处理模型(可用作视图模型)。

    存储库

    在您的订单存储库中(在我的例子中),添加一个静态方法来转换模型:

    public static OrderModel ConvertToModel(Order entity)
    {
        if (entity == null) return null;
        OrderModel model = new OrderModel
        {
            ContactId = entity.contactId,
            OrderId = entity.orderId,
        }
        return model;
    }
    

    在您的存储库类下方,添加以下内容:

    public static partial class Ex
    {
        public static IEnumerable<OrderModel> SelectOrderModel(this IEnumerable<Order> source)
        {
            bool includeRelations = source.GetType() != typeof(DbQuery<Order>);
            return source.Select(x => new OrderModel
            {
                OrderId = x.orderId,
                //example use ConvertToModel of some other repository
                BillingAddress = includeRelations ? AddressRepository.ConvertToModel(x.BillingAddress) : null,
                //example use another extension of some other repository
                Shipments = includeRelations && x.Shipments != null ? x.Shipments.SelectShipmentModel() : null
            });
        }
    }
    

    然后在你的GetOrderPage 方法中:

        public IEnumerable<OrderModel> GetOrderPage(int page, int itemsPerPage, string searchString, string sortOrder, int? partnerId,
            out int totalCount)
        {
            IQueryable<Order> query = DbContext.Orders; //get queryable from db
            .....//do your filtering, sorting, paging (do not use .ToList() yet)
    
            return queryOrders.SelectOrderModel().AsEnumerable();
            //or, if you want to include relations
            return queryOrders.Include(x => x.BillingAddress).ToList().SelectOrderModel();
            //notice difference, first ToList(), then SelectOrderModel().
        }
    

    让我解释一下:

    静态ConvertToModel 方法可以被任何其他存储库访问,如上所述,我使用来自一些AddressRepositoryConvertToModel

    扩展类/方法允许您将实体转换为模型。 这可以是 IQueryable 或任何其他列表、集合

    现在神奇的地方来了:如果您在调用 SelectOrderModel() 扩展之前执行了查询,则扩展中的 includeRelations 将是真的,因为 source 不是数据库查询类型(不是 linq-to-sql IQueryable)。如果这是真的,扩展可以在整个应用程序中调用其他方法/扩展来转换模型。

    现在另一方面:您可以先执行扩展,然后继续执行 LINQ 过滤。 过滤最终会在数据库中进行,因为您还没有执行.ToList(),扩展只是处理您的查询的一层。 Linq-to-sql 最终会知道在数据库中应用什么过滤。 inlcudeRelations 将为 false,因此它不会调用 SQL 不理解的其他 c# 方法。

    起初看起来很复杂,扩展可能是新事物,但它确实很有用。最终,当您为所有存储库设置了此设置后,只需添加一个 .Include() 额外内容即可加载关系。

    【讨论】:

    • 绝对是我迄今为止找到的最佳解决方案。所有其他的,包括微软的教程,都没有像在真实的商业环境中那样使用分层架构,而是从控制器直接进入存储库,并将 PagedList 作为模型中的唯一对象传回..
    • 谢谢,我已经用了一段时间了,还有一些额外的排序和过滤。当您只需要将页面放在那里时,它非常适合简单的应用程序。 #2017 确认 ;)
    • 实际上,ToList() 执行您的 LINQ 查询,因此您的第一个 sn-p 似乎是错误的。相反,您应该在return 语句中移动.Skip(...).ToList(),因此totalCount 将返回查询执行前的页数,这是您的目的。这个答案应该更正。不过,我 +1 是因为它对我有帮助。
    • 嗨,Alex,我之所以这样做是因为大多数解决方案都需要加载整个集合,然后使用分页。该解决方案将应在存储库中完成的操作保存在存储库中:)。当然可以稍后进行选择。在此示例中,它不进行任何过滤,因此计算所有内容不是问题。然而,我确实有这个不同,但为了便于解释,我想我会保持现在这样。
    • 我知道这篇文章很旧,但我确实喜欢这个解决方案,它非常有帮助,尤其是奖励信息,这就是为什么我把它提高了一个档次 - 太糟糕了,我不能再提高档次了;据我了解,Entity Framework 是一种存储库模式,因此将它放在控制器中 - 有点像调用代码以针对存储库执行。如果我的模型发生更改,我肯定有更多的东西要更改,而不仅仅是存储库代码,但是我认为在检索、保存、更新、删除代码的位置方面考虑到 EF 的语义更多。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-12
    • 2013-02-02
    • 1970-01-01
    • 2010-10-04
    • 2010-09-09
    • 2011-06-26
    • 2019-11-15
    相关资源
    最近更新 更多