Introducción

Entity Framework es la mejor alternativa de Microsoft para hacer el mapeo de una base de datos relacional a objetos, y ya existe un soporte muy completo en ASP.NET para poblar el control GridView y habilitar sus funciones de paginación y ordenamiento directamente por medio del objeto LinqDatasource, sin embargo, al usar esta alternativa no tenemos la posibilidad de desacoplar la consulta de Linq en una capa de reglas de negocio separada, lo cual puede ser un problema si tenemos esos requerimientos. La paginación debe hacerse en la base de datos para que sea una consulta más eficiente.

image

Cómo hacerlo

Hice este ejemplo para mostrar una alternativa donde se habilita el control GridView para ordernar y paginar el query por medio de una capa de reglas de negocio y otra capa de utilería en una arquitectura de n capas (5 en este caso).

image

En este caso, yo uso la base de datos AdventureWorks y solo agrego al diagrama de Entity Framework la tabla Products para este ejemplo, y renombro sus propiedades Entity name y Entity Set.

image 

En la capa de reglas de negocio, he creado los métodos básicos de “Traer” que usaré para todo tipo de consultas.

El Método GetBy(Expression<Func<ProductInfo, bool>> predicate, ContextRequest contextRequest) es extensible, pues permite construir una consulta por medio de un predicado, y agregar más especificaciones como paginación y ordenamiento por medio del objeto ContextRequest, así pues, este método puede servir para muchas variedades de consultas.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using AdventureWorksModel;
using System.Linq.Expressions;
using DemoDynLinqEFPagSort.Utils;
using LinqKit;
using DemoDynLinqEFPagSort.Utils.Linq;

namespace DemoDynLinqEFPagSort.BR
{
    public class ProductsBR
    {
        AdventureWorksEntities context = null;
        public ProductsBR()
        {
            context = new AdventureWorksEntities();
        }
        /// <summary>
        /// Obtain a  entity list apliying the predicate filter and pagging functions
        /// </summary>
        /// <param name="predicate">Filter of ProductInfo</param>
        /// <param name="contextRequest">Extra params how pagging and sort</param>
        /// <returns></returns>
        public List<ProductInfo> GetBy(Expression<Func<ProductInfo, bool>> predicate, ContextRequest contextRequest)
        {
            List<ProductInfo> result = new List<ProductInfo>();
            if (predicate == null) predicate = PredicateBuilder.True<ProductInfo>();
            var query = context.ProductSet.Where(predicate);
            query = ContextQueryBuilder<ProductInfo>.ApplyContextQuery(query, contextRequest);
            return query.ToList<ProductInfo>();

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="predicate"></param>
        /// <returns></returns>
        public int TotalNumberOfProducts(Expression<Func<ProductInfo, bool>> predicate)
        {
            if (predicate == null) predicate = PredicateBuilder.True<ProductInfo>();
            return context.ProductSet.Count(predicate);        
        }
        
    }
}

Observa: query = ContextQueryBuilder<ProductInfo>.ApplyContextQuery(query, contextRequest), donde ContextQueryBuilder es una clase Genérica que tiene algunos métodos para ayudarnos a aplicar la lógica requerida para validar si en el parámetro extensible contextRequest  vienen especificadas las opciones de paginación y ordenamiento, y si es así las agrega al query de Linq.

// ---------------------------------------------------------------
// Emir Treviño http://emir.com.mx
// ---------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DemoDynLinqEFPagSort.Utils;
using System.Linq.Dynamic;

namespace DemoDynLinqEFPagSort.Utils.Linq
{
    /// <summary>
    /// Help  to build the linq query
    /// </summary>
    /// <typeparam name="TEntity">Type of entity</typeparam>
    public class ContextQueryBuilder<TEntity> where TEntity : class
    {
        /// <summary>
        /// Aplica al query las opciones de paginación y ordenamiento que contiene el objeto contextRequest
        /// </summary>
        /// <param name="query">query inicializado previamente, de preferencia debe contener ya el Where</param>
        /// <param name="contextRequest">Opciones de paginación y otras</param>
        /// <returns></returns>
        public static IQueryable<TEntity> ApplyContextQuery(IQueryable<TEntity> query, ContextRequest contextRequest)
        {
            if (contextRequest != null)
            {
                if (contextRequest.CustomQuery != null)
                {
                    query = ApplyContextQuery(query, contextRequest.CustomQuery);
                }
            }
            return query;
        }
        public static IQueryable<TEntity> ApplyContextQuery(IQueryable<TEntity> query, CustomQuery customQuery)
        {
            if (!string.IsNullOrEmpty(customQuery.OrderBy))
            {

                query = query.OrderBy(customQuery.OrderBy + " " + customQuery.SortDirection);
            }
            if (customQuery.Page != null && customQuery.PageSize != null)
            {
                int skip = 0;
                if (string.IsNullOrEmpty(customQuery.OrderBy))
                {
                    query = query.OrderBy(customQuery.DefaultOrderByColumn);
                }
                if ((int)customQuery.Page > 1)
                {
                    skip = ((int)customQuery.PageSize * (int)customQuery.Page) - (int)customQuery.PageSize + 1;
                }
                query = query.Skip((int)skip).Take((int)customQuery.PageSize);

            }
            return query;
        }

    }
}

En la clase anterior se hace uso de dos utilerías extra: LinqKit y Linq Dynamic (Las cuales vienen incluídas en el proyecto de ejemplo) para facilitar el uso de predicados dinámicos y simplifica las propiedades de ordenamiento y paginación con strings.

Existe una capa de MVC, pero solo se ha agregado el “Controller” en este ejemplo, para ilustrar cual es su función con relación a la capa de presentación y la de reglas de negocio: funciona como intermediario, y es donde se crea el parámetro extensible ContextRequest.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DemoDynLinqEFPagSort.BR;
using AdventureWorksModel;
using DemoDynLinqEFPagSort.Utils;

namespace DemoDynLinqEFPagSort.MVC
{
    public class ProductsController
    {
        private static ProductsBR _BR = null;
        private static ProductsBR BR {
            get {
                if (_BR == null)
                    _BR = new ProductsBR();
                return _BR;
            }
        
        }

        public List<ProductInfo> GetAll(int maximumRows,
      int startRowIndex, string SortExpression)
        {
            int page = startRowIndex /maximumRows;
            if (page == 0 ) page=1;

            return BR.GetBy(null, new ContextRequest()
                {
                    CustomQuery = new CustomQuery()
                    {
                        DefaultOrderByColumn = "Name",
                         OrderBy = SortExpression,
                        PageSize = maximumRows,
                        Page = page
                    }
                });

        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="maximumRows"></param>
        /// <param name="startRowIndex"></param>
        /// <param name="SortExpression"></param>
        /// <returns></returns>
        public List<ProductInfo> GetAll(string SortExpression)
        {

          
            return BR.GetBy(null, new ContextRequest()
            {
                CustomQuery = new CustomQuery()
                {
                    DefaultOrderByColumn = "Name",
                     OrderBy = SortExpression ,
                    PageSize = 10,
                    Page = 1
                }
            });

        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public int TotalNumberOfProducts() {
            return BR.TotalNumberOfProducts(null);
        }
    }
}

Por último, en la capa de presentación observa  que no existe código en el archivo Default.aspx.cs, solo contiene la declaración de controles  en Default.aspx, lo mínimo necesario para lograr que el objeto ObjectDatasource sea capaz de paginar e integrar esta paginación al GridView con las reglas de negocio que se exponene a través del objeto ProductsController.

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="DemoDynLinqEFPagSort.Web._Default" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    
        <asp:ObjectDataSource ID="productsDataSource" runat="server" 
            SelectMethod="GetAll" TypeName="DemoDynLinqEFPagSort.MVC.ProductsController"
            SortParameterName="SortExpression"
           EnablePaging="True" 
            SelectCountMethod="TotalNumberOfProducts"
            >
        </asp:ObjectDataSource>
        
         <asp:GridView ID="productsGridView" AllowPaging="True" 
         BorderColor="White" BorderStyle="Ridge"
            CellSpacing="1" CellPadding="3" GridLines="None" 
            BackColor="White" BorderWidth="2px"
            AutoGenerateColumns="False" 
            DataSourceID="productsDataSource" 
            Runat="server" AllowSorting="True">
            <FooterStyle ForeColor="Black" BackColor="#C6C3C6"></FooterStyle>
             <Columns>
                 <asp:BoundField DataField="ProductID" HeaderText="ID" 
                     SortExpression="ProductID" />
                 <asp:BoundField DataField="Name" HeaderText="Name" SortExpression="Name" />
                 <asp:BoundField DataField="ProductNumber" HeaderText="Number" 
                     SortExpression="ProductNumber">
                     <ItemStyle Wrap="False" />
                 </asp:BoundField>
             </Columns>
            <PagerStyle ForeColor="Black" HorizontalAlign="Left" 
             BackColor="#C6C3C6"></PagerStyle>
            <HeaderStyle ForeColor="#E7E7FF" Font-Bold="True" 
             BackColor="#4A3C8C" Wrap="False"></HeaderStyle>
            <SelectedRowStyle ForeColor="White" 
               Font-Bold="True" 
               BackColor="#9471DE"></SelectedRowStyle>
            <RowStyle ForeColor="Black" BackColor="#DEDFDE"></RowStyle>
        </asp:GridView>
        <i>You are viewing page
        <%=productsGridView.PageIndex + 1%>
        of
        <%=productsGridView.PageCount%>
        </i>

    
    </div>
    </form>
</body>
</html>

Conclusión

En una arqitectura de “n” capas Entity Framework  permite ser implementado sin afectar a las demás capas ni comprometer la capa de presentación de manera acoplada. Entity Framework garantiza en el futuro poder resolver la portabilidad de manejadores de bases de datos, mientras se escribe la menor cantidad de código posible en la capa de presentación sin tener que resolver el acceso a datos en la misma, y lo más importante es que la paginación se lleva a cabo en el servidor de base de datos, no en la capa de presentación.

Descarga de aqui el ejemplo!

kick it on DotNetKicks.com

E-mail | Permalink | 1 Comentarios| Trackback