65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Linq 为 ObjectDataSource 实现分页。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.68/5 (11投票s)

2008年3月19日

CPOL

3分钟阅读

viewsIcon

56252

在这篇文章中,我将通过一个示例,展示如何将 GridView 控件绑定到 ObjectDataSource,并使用 Linq 查询来实现真正的分页。

引言

rownum

SQL Server 2005 发布时,我曾写过一篇关于 使用 ROW_NUMBER() 在 SQL Server 2005 中分页数据的文章。今天,我将探讨一种在同一主题上的替代方法。在这篇文章中,我将回顾使用绑定到内存中持久化 ObjectDataSource 的 GridView 来实现数据分页。

本文涵盖的主题

  • 将 GridView 控件绑定到 ObjectDataSource。
  • 单例集合。
  • 在 Asp.net AJAX UpdatePanel 中使用 GridView。
  • 使用 Linq 查询对象。
  • 在 Linq 中使用 Skip() 和 Take()。
  • 自定义数据分页。

数据分页

如果您不熟悉数据分页,首先需要了解的是,开箱即用的 Asp.net 控件会使用 UI 分页。简单来说,这意味着所有数据都会返回到您的控件,然后在 aspx 将 HTML 渲染到客户端浏览器之前进行过滤。在某些情况下这可能没问题,但通常对性能不利。使用数据分页技术,您只会返回控件将显示的数据。如果您已经查看过此项目的在线演示,您已经看到了与此相比,数据分页技术的性能要好得多。

背景

此项目是使用 Linq 查询 单例集合的一个示例。这基本上意味着该集合在 IIS 进程的应用程序级别持久化在内存中。这是通过使用静态或共享实例,或通过使用 .net web.caching,或通过其他第三方缓存机制(如 zcache 或 ncache)来实现的。如果您不想将数据持久化在内存中,那么 rownumber 是实现数据分页的最佳方式。

使用代码

下载的 zip 文件包含 Visual Studio 2008 项目和运行在线演示所需的源代码文件,以及一个包含邮政编码数据库的 csv 文件和一个用于加载数据并引用的存储过程。在导入邮政编码数据并运行存储过程脚本后,您需要更新 web.config 文件中的连接字符串以指向您的数据源。

关注点

使用 Linq 语法从您的集合中检索“子集合”是经典方法的绝佳替代方案。使用经典方法,您通常需要创建对象的第二个实例,循环遍历第一个对象,并将每个成员复制到新的新对象中。在此项目中,我仅使用以下方法返回一个子集合。

C#
        /// <summary /> 
        /// GetZipCodes 
        /// </summary /> 
        /// <param name="ResultsPerPage" /></param /> 
        /// <param name="PageNumber" /></param /> 
        /// <returns /> 
        /// IEnumerable(Of ZipCode) 
        /// </returns /> 
        /// <remarks /> 
        /// Page Clone of Instance Data Using Linq 
        /// </remarks /> 
        public static IEnumerable<ZipCodeObjects.ZipCode> GetZipCodes(int ResultsPerPage, int PageNumber)
        {

            //number of records to skip 
            int skip = (PageNumber - 1) * ResultsPerPage;

            //number of results per page. 
            int take = ResultsPerPage;

            //execute Linq query for result set
            IEnumerable result = (from zipcodes in ZipCodeInstance select zipcodes).Skip(skip).Take(ResultsPerPage);

            //return result 
            return result;
        }

Vb.net
        ''' <summary>
        ''' GetZipCodes
        ''' </summary>
        ''' <param name="ResultsPerPage"></param>
        ''' <param name="PageNumber"></param>
        ''' <returns>
        ''' IEnumerable(Of ZipCode)
        ''' </returns>
        ''' <remarks>
        ''' Page Clone of Instance Data Using Linq
        ''' </remarks>
        Public Shared Function GetZipCodes(ByVal ResultsPerPage As Integer, _
                                           ByVal PageNumber As Integer) As IEnumerable(Of ZipCode)

            'number of records to skip
            Dim skip As Integer = (PageNumber - 1) * ResultsPerPage

            'number of results per page.
            Dim take As Integer = ResultsPerPage

            'execute query for result set
            Dim result As IEnumerable(Of ZipCode) = _
                (From zipcodes In ZipCodeInstance).Skip(skip).Take(take)

            'return result
            Return result
        End Function

这是完整的类,为了确保集合只有一个实例,构造函数被设置为私有。此外,还使用了锁或同步锁来确保线程安全。

您集合的内存实例不会被分页或修改,修改此实例会影响所有用户。因此,我们在用户请求时返回对象的较小副本。

C#
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Linq;
using System.Collections.Generic;


namespace ZipCodeObjects
{
    /// <summary> 
    /// ZipCodeCollection 
    /// </summary> 
    /// <remarks> 
    /// Contains Collection of zip codes and meta. 
    /// </remarks> 
    public class ZipCodeCollection
    {

        /// <summary> 
        /// ReadLock 
        /// </summary> 
        /// <remarks> 
        /// Used By SyncLock to ensure Thread Safety 
        /// </remarks> 
        private static readonly object ReadLock = new object();

        /// <summary> 
        /// ZipCodeInstance 
        /// </summary> 
        /// <remarks> 
        /// ZipCodeInstance 
        /// Singleton Collection of Zip Codes and meta. 
        /// </remarks> 
        private static List<ZipCode> m_ZipCodeInstance;
        public static List<ZipCode> ZipCodeInstance
        {
            get
            {
                // initialize if not already done 
                if (m_ZipCodeInstance == null)
                {

                    //only allow 1 person to load data at once. 
                    lock (ReadLock)
                    {
                        if (m_ZipCodeInstance == null)
                        {
                            m_ZipCodeInstance = LoadData();
                        }
                    }

                }
                return m_ZipCodeInstance;
            }
        }

        /// <summary> 
        /// GetZipCodes 
        /// </summary> 
        /// <param name="ResultsPerPage"></param> 
        /// <param name="PageNumber"></param> 
        /// <returns> 
        /// IEnumerable(Of ZipCode) 
        /// </returns> 
        /// <remarks> 
        /// Page Clone of Instance Data Using Linq 
        /// </remarks> 
        public static IEnumerable<ZipCodeObjects.ZipCode> GetZipCodes(int ResultsPerPage, int PageNumber)
        {

            //number of records to skip 
            int skip = (PageNumber - 1) * ResultsPerPage;

            //number of results per page. 
            int take = ResultsPerPage;

            //execute Linq query for result set
            IEnumerable<ZipCodeObjects.ZipCode> result = (from zipcodes in ZipCodeInstance select zipcodes).Skip(skip).Take(ResultsPerPage);

            //return result 
            return result;
        }

        /// <summary> 
        /// SelectCount 
        /// </summary> 
        /// <returns> 
        /// Integer 
        /// </returns> 
        /// <remarks> 
        /// Returns total number of records in instance. 
        /// Uses Linq 
        /// </remarks> 
        public static int SelectCount()
        {
            return (from zipcodes in ZipCodeInstance select zipcodes).Count();
        }

        /// <summary> 
        /// LoadData 
        /// </summary> 
        /// <returns> 
        /// List(Of ZipCode) 
        /// </returns> 
        /// <remarks> 
        /// Load collection of zip codes from database. 
        /// </remarks> 
        private static List<ZipCode> LoadData()
        {

            //create new instance of zip code collection 
            List<ZipCode> ziplist = new List<ZipCode>();

            //setup database connection 
            System.Data.SqlClient.SqlConnection conn = new System.Data.SqlClient.SqlConnection();
            conn.ConnectionString = ConfigurationManager.ConnectionStrings["ProgrammersJournal"].ConnectionString;

            //open connection 
            conn.Open();

            try
            {

                //define sql command 
                SqlCommand cmd = new SqlCommand("pj_getallzipcodes", conn);

                //execute and loop through reader. 
                using (System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {

                        //add zip object to list. 
                        ziplist.Add(new ZipCode(reader.GetSqlString(0).ToString(), reader.GetSqlString(1).ToString(), reader.GetSqlString(2).ToString(), Convert.ToDecimal(reader.GetSqlString(3).ToString()), Convert.ToDecimal(reader.GetSqlString(4).ToString()), Convert.ToInt32(reader.GetSqlString(5).ToString()), Convert.ToInt16(reader.GetSqlString(6).ToString())));
                    }
                }
            }

            catch (Exception ex)
            {

                //bubble exception 
                throw new Exception(ex.Message);
            }

            finally
            {

                //close connection 
                if (conn.State != ConnectionState.Closed)
                {
                    conn.Close();
                }

                //instance is populated 
                //force garbage collection 
                GC.Collect();
                GC.WaitForPendingFinalizers();

            }

            //return new instance of zip code collection 
            return ziplist;
        }

        /// <summary> 
        /// New 
        /// </summary> 
        /// <remarks> 
        /// Conrtructor set to private to ensure 
        /// instances of the obect cannot be created 
        /// outside of this class. 
        /// </remarks> 
        private ZipCodeCollection()
        {

        }

    } 

    /// <summary> 
    /// ZipCode 
    /// </summary> 
    /// <remarks> 
    /// Single Zip code record and associated meta. 
    /// </remarks> 
    public class ZipCode
    {

        /// <summary> 
        /// Zip 
        /// </summary> 
        /// <remarks> 
        /// Zip Code 
        /// </remarks> 
        private string m_Zip;
        public string Zip
        {
            get { return m_Zip; }
        }

        /// <summary> 
        /// City 
        /// </summary> 
        /// <remarks> 
        /// City where zip code is located. 
        /// </remarks> 
        private string m_City;
        public string City
        {
            get { return m_City; }
        }

        /// <summary> 
        /// State 
        /// </summary> 
        /// <remarks> 
        /// State where zip code is located. 
        /// </remarks> 
        private string m_State;
        public string State
        {
            get { return m_State; }
        }

        /// <summary> 
        /// Latitude 
        /// </summary> 
        /// <remarks> 
        /// Latitude reference for this zip code. 
        /// </remarks> 
        private decimal m_Latitude;
        public decimal Latitude
        {
            get { return m_Latitude; }
        }

        /// <summary> 
        /// Longitude 
        /// </summary> 
        /// <remarks> 
        /// Longitude reference for this zip code. 
        /// </remarks> 
        private decimal m_Longitude;
        public decimal Longitude
        {
            get { return m_Longitude; }
        }

        /// <summary> 
        /// TimeZone 
        /// </summary> 
        /// <remarks> 
        /// TimeZone reference for this zip code. 
        /// </remarks> 
        private int m_TimeZone;
        public int TimeZone
        {
            get { return m_TimeZone; }
        }

        /// <summary> 
        /// Dst 
        /// </summary> 
        /// <remarks> 
        /// Dst reference for this zip code. 
        /// </remarks> 
        private short m_Dst;
        public short Dst
        {
            get { return m_Dst; }
        }

        /// <summary> 
        /// New 
        /// </summary> 
        /// <remarks> 
        /// parameterless constructor 
        /// </remarks> 
        public ZipCode()
        {

        }

        /// <summary> 
        /// New 
        /// </summary> 
        /// <param name="zip"></param> 
        /// <param name="city"></param> 
        /// <param name="state"></param> 
        /// <param name="latitude"></param> 
        /// <param name="longitude"></param> 
        /// <param name="timeZone"></param> 
        /// <param name="dst"></param> 
        /// <remarks> 
        /// Custom Contructor 
        /// </remarks> 
        public ZipCode(string zip, string city, string state, decimal latitude, decimal longitude, int timeZone, short dst)
        {

            this.m_Zip = zip;
            this.m_City = city;
            this.m_State = state;
            this.m_Latitude = latitude;
            this.m_Longitude = longitude;
            this.m_TimeZone = timeZone;
            this.m_Dst = dst;

        }

    } 
}

Vb.net
 
Imports System.Data
Imports System.Data.SqlClient
Imports System.Configuration
Imports System.Linq

Namespace ZipCodeObjects

    ''' <summary>
    ''' ZipCodeCollection
    ''' </summary>
    ''' <remarks>
    ''' Contains Collection of zip codes and meta.
    ''' </remarks>
    Public Class ZipCodeCollection

        ''' <summary>
        ''' ReadLock
        ''' </summary>
        ''' <remarks>
        ''' Used By SyncLock to ensure Thread Safety
        ''' </remarks>
        Private Shared ReadOnly ReadLock As New Object()

        ''' <summary>
        ''' ZipCodeInstance
        ''' </summary>
        ''' <remarks>
        ''' ZipCodeInstance
        ''' Singleton Collection of Zip Codes and meta.
        ''' </remarks>
        Private Shared m_ZipCodeInstance As List(Of ZipCode)
        Public Shared ReadOnly Property ZipCodeInstance() As List(Of ZipCode)
            Get
                ' initialize if not already done
                If m_ZipCodeInstance Is Nothing Then

                    'only allow 1 person to load data at once.
                    SyncLock ReadLock
                        If m_ZipCodeInstance Is Nothing Then
                            m_ZipCodeInstance = LoadData()
                        End If
                    End SyncLock

                End If
                Return m_ZipCodeInstance
            End Get
        End Property

        ''' <summary>
        ''' GetZipCodes
        ''' </summary>
        ''' <param name="ResultsPerPage"></param>
        ''' <param name="PageNumber"></param>
        ''' <returns>
        ''' IEnumerable(Of ZipCode)
        ''' </returns>
        ''' <remarks>
        ''' Page Clone of Instance Data Using Linq
        ''' </remarks>
        Public Shared Function GetZipCodes(ByVal ResultsPerPage As Integer, _
                                           ByVal PageNumber As Integer) As IEnumerable(Of ZipCode)

            'number of records to skip
            Dim skip As Integer = (PageNumber - 1) * ResultsPerPage

            'number of results per page.
            Dim take As Integer = ResultsPerPage

            'execute query for result set
            Dim result As IEnumerable(Of ZipCode) = _
                (From zipcodes In ZipCodeInstance).Skip(skip).Take(take)

            'return result
            Return result
        End Function

        ''' <summary>
        ''' SelectCount
        ''' </summary>
        ''' <returns>
        ''' Integer
        ''' </returns>
        ''' <remarks>
        ''' Returns total number of records in instance.
        ''' Uses Linq
        ''' </remarks>
        Public Shared Function SelectCount() As Integer
            Return (From zipcodes In ZipCodeInstance).Count()
        End Function

        ''' <summary>
        ''' LoadData
        ''' </summary>
        ''' <returns>
        ''' List(Of ZipCode)
        ''' </returns>
        ''' <remarks>
        ''' Load collection of zip codes from database.
        ''' </remarks>
        Private Shared Function LoadData() As List(Of ZipCode)

            'create new instance of zip code collection
            Dim ziplist As New List(Of ZipCode)

            'setup database connection
            Dim conn As New System.Data.SqlClient.SqlConnection()
            conn.ConnectionString = ConfigurationManager.ConnectionStrings("ProgrammersJournal").ConnectionString

            'open connection
            conn.Open()

            Try

                'define sql command
                Dim cmd As New SqlCommand("pj_getallzipcodes", conn)

                'execute and loop through reader.
                Using reader As SqlClient.SqlDataReader = cmd.ExecuteReader()
                    While reader.Read()

                        'add zip object to list.
                        ziplist.Add(New ZipCode(reader.GetSqlString(0).ToString(), _
                                                reader.GetSqlString(1).ToString(), _
                                                reader.GetSqlString(2).ToString(), _
                                                Convert.ToDecimal(reader.GetSqlString(3).ToString()), _
                                                Convert.ToDecimal(reader.GetSqlString(4).ToString()), _
                                                Convert.ToInt32(reader.GetSqlString(5).ToString()), _
                                                Convert.ToInt16(reader.GetSqlString(6).ToString())))
                    End While
                End Using

            Catch ex As Exception

                'bubble exception
                Throw New Exception(ex.Message)

            Finally

                'close connection
                If conn.State <> ConnectionState.Closed Then
                    conn.Close()
                End If

                'instance is populated
                'force garbage collection
                GC.Collect()
                GC.WaitForPendingFinalizers()

            End Try

            'return new instance of zip code collection
            Return ziplist
        End Function

        ''' <summary>
        ''' New
        ''' </summary>
        ''' <remarks>
        ''' Conrtructor set to private to ensure 
        ''' instances of the obect cannot be created
        ''' outside of this class.
        ''' </remarks>
        Private Sub New()

        End Sub

    End Class

    ''' <summary>
    ''' ZipCode
    ''' </summary>
    ''' <remarks>
    ''' Single Zip code record and associated meta.
    ''' </remarks>
    Public Class ZipCode

        ''' <summary>
        ''' Zip
        ''' </summary>
        ''' <remarks>
        ''' Zip Code
        ''' </remarks>
        Private m_Zip As String
        Public ReadOnly Property Zip() As String
            Get
                Return m_Zip
            End Get
        End Property

        ''' <summary>
        ''' City
        ''' </summary>
        ''' <remarks>
        ''' City where zip code is located.
        ''' </remarks>
        Private m_City As String
        Public ReadOnly Property City() As String
            Get
                Return m_City
            End Get
        End Property

        ''' <summary>
        ''' State
        ''' </summary>
        ''' <remarks>
        ''' State where zip code is located.
        ''' </remarks>
        Private m_State As String
        Public ReadOnly Property State() As String
            Get
                Return m_State
            End Get
        End Property

        ''' <summary>
        ''' Latitude
        ''' </summary>
        ''' <remarks>
        ''' Latitude reference for this zip code.
        ''' </remarks>
        Private m_Latitude As Decimal
        Public ReadOnly Property Latitude() As Decimal
            Get
                Return m_Latitude
            End Get
        End Property

        ''' <summary>
        ''' Longitude
        ''' </summary>
        ''' <remarks>
        ''' Longitude reference for this zip code.
        ''' </remarks>
        Private m_Longitude As Decimal
        Public ReadOnly Property Longitude() As Decimal
            Get
                Return m_Longitude
            End Get
        End Property

        ''' <summary>
        ''' TimeZone
        ''' </summary>
        ''' <remarks>
        ''' TimeZone reference for this zip code.
        ''' </remarks>
        Private m_TimeZone As Integer
        Public ReadOnly Property TimeZone() As Integer
            Get
                Return m_TimeZone
            End Get
        End Property

        ''' <summary>
        ''' Dst
        ''' </summary>
        ''' <remarks>
        ''' Dst reference for this zip code.
        ''' </remarks>
        Private m_Dst As Short
        Public ReadOnly Property Dst() As Short
            Get
                Return m_Dst
            End Get
        End Property

        ''' <summary>
        ''' New
        ''' </summary>
        ''' <remarks>
        ''' parameterless constructor
        ''' </remarks>
        Public Sub New()

        End Sub

        ''' <summary>
        ''' New
        ''' </summary>
        ''' <param name="zip"></param>
        ''' <param name="city"></param>
        ''' <param name="state"></param>
        ''' <param name="latitude"></param>
        ''' <param name="longitude"></param>
        ''' <param name="timeZone"></param>
        ''' <param name="dst"></param>
        ''' <remarks>
        ''' Custom Contructor
        ''' </remarks>
        Public Sub New(ByVal zip As String, _
                       ByVal city As String, _
                       ByVal state As String, _
                       ByVal latitude As Decimal, _
                       ByVal longitude As Decimal, _
                       ByVal timeZone As Integer, _
                       ByVal dst As Short)

            Me.m_Zip = zip
            Me.m_City = city
            Me.m_State = state
            Me.m_Latitude = latitude
            Me.m_Longitude = longitude
            Me.m_TimeZone = timeZone
            Me.m_Dst = dst

        End Sub

    End Class

End Namespace

使用 ObjectDataSource 可以非常轻松地将 GridView 绑定到您的集合。

<asp:ObjectDataSource ID="ZipCodeObjectDataSource" runat="server" SelectMethod="GetZipCodes"
    TypeName="ZipCodeObjects.ZipCodeCollection">
    <SelectParameters>
    <asp:SessionParameter DefaultValue="20" Name="ResultsPerPage" SessionField="ResultsPerPage"
        Type="Int32" />
    <asp:SessionParameter DefaultValue="1" Name="PageNumber" SessionField="PageNumber"
        Type="Int32" />
    </SelectParameters>
</asp:ObjectDataSource>

从这里开始,剩下的就是简单的分页机制和 UI。下载演示项目以获取完整的源代码和数据库。

历史

  • 2008/3/19:发布了代码样本和文章
  • 2008/3/19:上传了示例项目
  • 2008/3/20:仅复制编辑。

© . All rights reserved.