Usando una PagedList con un ViewModel ASP.Net MVC

Intento utilizar una PagedList en mi aplicación ASP.Net y encontré este ejemplo en el sitio web de 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

¿Cómo es posible usar una PagedList en una situación compleja que utiliza un ViewModel? Intento agregar una PagedList sin éxito al ejemplo del instructor publicado aquí: 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

El problema es que ViewModel está compuesto por clases y no por campos simples, por lo que no puedo convertir el resultado con el método ToPageList ().

Esta es la estructura de ViewModel:

using System.Collections.Generic; using ContosoUniversity.Models; namespace ContosoUniversity.ViewModels { public class InstructorIndexData { public IEnumerable Instructors { get; set; } public IEnumerable Courses { get; set; } public IEnumerable Enrollments { get; set; } } } 

Necesito unir las tres tablas en ViewModel y mostrar el resultado en una Vista.

Para cualquiera que intente hacerlo sin modificar sus ViewModels Y no cargar todos sus registros de la base de datos.

Repositorio

  public List GetOrderPage(int page, int itemsPerPage, out int totalCount) { List orders = new List(); 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. } 

Controlador

  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 orders = orderMan.GetOrderPage(pagenumber, 5, out totalCount); List orderViews = new List(); 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 pageOrders = new StaticPagedList(orderViews, pagenumber + 1, 5, totalCount); return View(pageOrders); } 

Ver

 @using PagedList.Mvc; @using PagedList; @model IPagedList @{ ViewBag.Title = "Index"; } 

Index

@Html.ActionLink("Create New", "Create")

@if (Model.Count > 0) {
@Html.DisplayNameFor(model => model.First().orderId)
} else {

No Orders yet.

} @Html.PagedListPager(Model, page => Url.Action("Index", new { page }))

Prima

Haz lo de arriba primero, ¡entonces tal vez uses esto!

Dado que esta pregunta es sobre modelos (de vistas), voy a regalarle una pequeña solución que no solo será útil para la búsqueda, sino también para el rest de su aplicación si desea mantener sus entidades separadas, solo usadas en el repository, y el rest de la aplicación se ocupa de los modelos (que pueden usarse como modelos de vista).

Repositorio

En su repository de pedidos (en mi caso), agregue un método estático para convertir un modelo:

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

Debajo de tu clase de repository, agrega esto:

 public static partial class Ex { public static IEnumerable SelectOrderModel(this IEnumerable source) { bool includeRelations = source.GetType() != typeof(DbQuery); 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 }); } } 

Y luego en su método GetOrderPage :

  public IEnumerable GetOrderPage(int page, int itemsPerPage, string searchString, string sortOrder, int? partnerId, out int totalCount) { IQueryable 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(). } 

Dejame explicar:

El método estático ConvertToModel puede ser accedido por cualquier otro repository, como se usa arriba, yo uso ConvertToModel desde algún AddressRepository .

La clase / método de extensión le permite convertir una entidad a un modelo. Esto puede ser IQueryable o cualquier otra lista, colección .

Ahora viene la magia: si ha ejecutado la consulta ANTES de llamar a la extensión SelectOrderModel() , includeRelations dentro de la extensión será verdadera porque la source NO es un tipo de consulta de base de datos (no un IQueryable de linq-to-sql). Cuando esto sea cierto, la extensión puede llamar a otros métodos / extensiones a través de su aplicación para convertir modelos.

Ahora en el otro lado: primero puede ejecutar la extensión y luego continuar haciendo el filtrado LINQ. El filtrado eventualmente ocurrirá en la base de datos , porque aún no ha hecho un .ToList() , la extensión es solo una capa de tratamiento de sus consultas. Linq-to-sql eventualmente sabrá qué filtro aplicar en la Base de Datos. Las inlcudeRelations serán falsas para que no invoquen otros métodos c # que SQL no comprenda.

Parece complicado al principio, las extensiones pueden ser algo nuevo, pero es realmente útil. Finalmente, cuando haya configurado esto para todos los repositorys, simplemente un .Include() extra cargará las relaciones.

Como Chris sugirió que la razón por la que está usando ViewModel no le impide usar PagedList . Debe formar una colección de sus objetos de ViewModel que debe enviarse a la vista para que los pague.

Aquí hay una guía paso a paso sobre cómo puede usar PagedList para sus datos de viewmodel.

Su modelo de vista ( he tomado un ejemplo simple para abreviarlo y puede modificarlo fácilmente para adaptarlo a sus necesidades ) .

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

y el método de índice de su controlador será algo así como

 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)); } 

Y su vista de índice

 @model PagedList.IPagedList @using PagedList.Mvc;   @foreach (var item in Model) {  } 
@Html.DisplayFor(modelItem => item.QuestionId) @Html.DisplayFor(modelItem => item.QuestionName)

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

Aquí está el enlace SO con mi respuesta que tiene la guía paso a paso sobre cómo puede usar PageList

Modifiqué el código de la siguiente manera:

ViewModel

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

Controlador

 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); } 

Ver

 
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}))

¡Espero que esto te ayude!

Descubrí cómo hacer esto. Estaba construyendo una aplicación muy similar al ejemplo / tutorial que discutiste en tu pregunta original.

Aquí hay un fragmento del código que funcionó para mí:

  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); 

yo añadí

 using.PagedList; 

a mi controlador como dice el tutorial.

Ahora, en mi opinión, mis declaraciones de uso, etc. en la parte superior, NOTA, no cambié mi statement de modelo de uso.

 @model CSHM.ViewModels.TeacherIndexData @using PagedList; @using PagedList.Mvc;  

y luego en la parte inferior para construir mi lista de paginación utilicé lo siguiente y parece funcionar. Todavía no he incorporado la funcionalidad para el tipo actual, que muestra datos relacionados, filtros, etc. pero no creo que sea tan difícil.

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

Espero que eso te funcione. ¡¡Déjame saber si funciona!!

El hecho de que estés usando un modelo de vista no tiene importancia. La forma estándar de usar PagedList es almacenar “una página de elementos” como una variable ViewBag . Todo lo que tiene que determinar es qué colección constituye qué va a revisar. No puede hacer una búsqueda lógica de varias colecciones al mismo tiempo, por lo tanto, suponiendo que elija Instructors :

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

Entonces, el rest del código estándar funciona como siempre.