您现在的位置是:首页 >其他 >第16章 逻辑分页实现网站首页其他
第16章 逻辑分页实现
1 Core.Extensions.QueryableExtension
using System.Linq.Expressions;
namespace Core.Extensions
{
/// <summary>
/// 【Queryable扩展类--类】
/// <remarks>
/// 摘要:
/// “IQueryable”实例操作方法通过该类中相应方法成员进行了扩展,以提升 “IQueryable”实例操作方法的功能,
/// 这些被扩展的方法包括:LongSkip、 OrderBy、OrderByDescending。
/// </remarks>
/// </summary>
public static class QueryableExtension
{
///<typeparam name="TSource">泛型类型实例(1个指定类的类型实例)。</typeparam>
///<param name="source">以“IQueryable”实例进行存储的1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)。</param>
///<param name="LongCount">需要跳过项的总计值。</param>
/// <summary>
/// 【长整型跳过】
/// <remarks>
/// 摘要:
/// 由于“System.Linq.Queryable.Skip”方法只支持类型为“int”或“Int32”类型的需要跳过项的总计值,为了支持类型为“long”或“Int64”的需要跳过项的总计值,从而定义了该扩展方法。
/// </remarks>
/// <returns>
/// 返回:
/// “IQueryable”实例,该实例存储着1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)在执行过跳过项操作后所剩余的数据源。
/// </returns>
/// </summary>
public static IQueryable<TSource> LongSkip<TSource>(this IQueryable<TSource> source, long LongCount)
{
//“Math.DivRem()”方法在 C#中的用于除法和计算两个数的商:“quotients”(取整操作,“quotients”必须被定义为“long”或“Int64”类型,因为“quotients”的最大值远远大于“int.MaxValue”);
//并在输出参数中返回余数:“remainder”(取余,“remainder”可以转换为“int”或“Int32”类型,因为“remainder”的最大值一定小于“int.MaxValue”)。
long quotients = Math.DivRem(LongCount, int.MaxValue, out long remainder);
//先执行从数据源中跳过quotients*int.MaxValue项操作。
for (long i = 0; i < quotients; i += 1)
source = source.Skip(int.MaxValue);
//如果余数(“remainder”)不为:0,则在上一个操作的基础上,再执行跳过remainder项操作。
if (remainder != 0)
source = source.Skip((int)remainder);
return source;
}
///<typeparam name="TSource">泛型类型实例(1个指定类的类型实例)。</typeparam>
///<param name="source">以“IQueryable”实例进行存储的1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)。</param>
///<param name="propertyName">定类的指定属性成员名(或对应表中的相应字段名)。</param>
/// <summary>
/// 【正序排序】
/// <remarks>
/// 摘要:
/// 对“System.Linq.Queryable.OrderBy”方法进行扩展,使用支持以指定类的指定属性成员名(或对应表中的相应字段名)字符串进行“正序”排序操作。
/// </remarks>
/// <returns>
/// 返回:
/// “IQueryable”实例,该实例存储着1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)在执行过正序排序操作后的数据源。
/// </returns>
/// </summary>
public static IOrderedQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string propertyName)
{
//如果用于排序操作的参数实例(指定类的指定属性成员名(或对应表中的相应字段名)为空 ,则直接返回原数据源。
if (string.IsNullOrEmpty(propertyName))
{
return (IOrderedQueryable<TSource>)source;
}
//为指定类的排序操作构建相应的“lambda”表达式。
//“lambda”表达式的“指针”。
var param = Expression.Parameter(typeof(TSource), "p");
//“lambda”表达式的“指针”与指定类的指定属性成员(或对应表中的相应字段)实例的组合。
var property = Expression.Property(param, propertyName);
为指定类组合出完整的“lambda”表达式。
var exp = Expression.Lambda(property, param);
//委托方法参数实例,通过委托方法参数实例,生成一个指定的“正序”排序“lambda”表达式。
MethodCallExpression resultExpression = Expression.Call(typeof(Queryable), "OrderBy", new Type[] { source.ElementType, exp.Body.Type }, source.Expression, exp);
//返回经过“正序”排序后的数据源。
return (IOrderedQueryable<TSource>)source.Provider.CreateQuery<TSource>(resultExpression);
}
///<typeparam name="TSource">泛型类型实例(1个指定类的类型实例)。</typeparam>
///<param name="source">以“IQueryable”实例进行存储的1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)。</param>
///<param name="propertyName">定类的指定属性成员名(或对应表中的相应字段名)。</param>
/// <summary>
/// 【倒序排序】
/// <remarks>
/// 摘要:
/// 对“System.Linq.Queryable.OrderByDescending”方法进行扩展,使用支持以指定类的指定属性成员名(或对应表中的相应字段名)字符串进行“倒序”排序操作。
/// </remarks>
/// <returns>
/// 返回:
/// “IQueryable”实例,该实例存储着1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)在执行过倒序排序操作后的数据源。
/// </returns>
/// </summary>
public static IOrderedQueryable<TSource> OrderByDescending<TSource>(this IQueryable<TSource> source, string propertyName)
{
//如果用于排序操作的参数实例(指定类的指定属性成员名(或对应表中的相应字段名)为空 ,则直接返回原数据源。
if (string.IsNullOrEmpty(propertyName))
{
return (IOrderedQueryable<TSource>)source;
}
//为指定类的排序操作构建相应的“lambda”表达式。
//“lambda”表达式的“指针”。
var param = Expression.Parameter(typeof(TSource), "p");
//“lambda”表达式的“指针”与指定类的指定属性成员(或对应表中的相应字段)实例的组合。
var property = Expression.Property(param, propertyName);
为指定类组合出完整的“lambda”表达式。
var exp = Expression.Lambda(property, param);
//委托方法参数实例,通过委托方法参数实例,生成一个指定的“倒序”排序“lambda”表达式。
MethodCallExpression resultExpression = Expression.Call(typeof(Queryable), "OrderByDescending", new Type[] { source.ElementType, exp.Body.Type }, source.Expression, exp);
//返回经过“倒序”排序后的数据源。
return (IOrderedQueryable<TSource>)source.Provider.CreateQuery<TSource>(resultExpression);
}
}
}
2 Core.IPagedList<T>
namespace Core
{
///<typeparam name="T">泛型类型实例(1个指定类的类型实例)。</typeparam>
/// <summary>
/// 【(逻辑)分页列表--接口】
/// <remarks>
/// 摘要:
/// 通过继承该接口的具体实现类中的属性成员实例,只逻辑的从数据源中加载指定1页中的数据,存储到1(逻辑)页中,从而尽量少的从指定表加载数据,从而尽量降低内存的消耗。
/// 说明:
/// (逻辑)分页交互操作对象是当前程序与数据库的指定表。
/// </remarks>
/// </summary>
public interface IPagedList<T> : IList<T>
{
/// <summary>
/// 【当前页】
/// <remarks>
/// 摘要:
/// 获取/设置当前页,当前逻辑(内存)页面的长整型索引值,从“0”开始计算。
/// </remarks>
/// </summary>
long PageIndex { get; }
/// <summary>
/// 【页面大小】
/// <remarks>
/// 摘要:
/// 获取/设置页面大小,即1逻辑(内存)页面最多所存储的实例项的长整型个数值。
/// </remarks>
/// </summary>
int PageSize { get; }
/// <summary>
/// 【总计值】
/// <remarks>
/// 摘要:
/// 获取/设置数据源实例项的长整型总计值。
/// </remarks>
/// </summary>
long TotalCount { get; }
/// <summary>
/// 【总页数】
/// <remarks>
/// 摘要:
/// 获取/设置总页数,即数据源实例项,可以分为页数的总计值。
/// </remarks>
/// </summary>
long TotalPages { get; }
/// <summary>
/// 【有上1页?】
/// <remarks>
/// 摘要:
/// 获取1个值false(没有)/true(有),该值指示在逻辑(内存)分页操作中当前页是否有上1页(第1页之前没有上1页)。
/// </remarks>
/// </summary>
bool HasPreviousPage { get; }
/// <summary>
/// 【有下1页?】
/// <remarks>
/// 摘要:
/// 获取1个值false(没有)/true(有),该值指示在逻辑(内存)分页操作中当前页是否有下1页(最后1页之后没有下1页)。
/// </remarks>
/// </summary>
bool HasNextPage { get; }
}
}
3 Core.PagedList<T>
using Core.Extensions;
namespace Core
{
///<typeparam name="T">泛型类型实例(1个指定类的类型实例)。</typeparam>
/// <summary>
/// 【(逻辑)分页列表--类】
/// <remarks>
/// 摘要:
/// 通过该类及其属性成员实例,只逻辑的从数据源中加载指定1页中的数据,存储到1(逻辑)页中,从而尽量少的从指定表加载数据,从而尽量降低内存的消耗。
/// 说明:
/// (逻辑)分页交互操作对象是当前程序与数据库的指定表。
/// </remarks>
/// </summary>
[Serializable]
public partial class PagedList<T> : List<T>, IPagedList<T>
{
#region 拷贝构造方法
/// <param name="source">以列表接口实例进行存储的1个指定类型的数据源(包含:数组、列表、字典等)。</param>
/// <param name="pageIndex">分页操作中当前页的长整型索引值。</param>
/// <param name="pageSize">1逻辑(内存)页面最多所存储的实例项的长整型个数值。</param>
/// <param name="totalCount">数据源实例项的长整型总计值。</param>
/// <summary>
/// 【拷贝构造方法】
/// <remarks>
/// 摘要:
/// 通过拷贝构造方法为该类中的属性成员赋值实例化初始值,并获取1逻辑(内存)页面内的所有数据。
/// </remarks>
/// </summary>
public PagedList(IList<T> source, long pageIndex, int pageSize, long? totalCount = null)
{
//分页操作中每页最多显示实例的项(行)数值,“Math.Max”方法保证该参数实例的最小值必须>=1。
pageSize = Math.Max(pageSize, 1);
TotalCount = totalCount ?? source.Count;
TotalPages = TotalCount / pageSize;
if (TotalCount % pageSize > 0)
TotalPages++;
PageSize = pageSize;
PageIndex = pageIndex;
// 把1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)1内存页面内的所有项1次性加载到列表实例中,
//AddRange方法是1性把所有的数据加载的目录中,而不必像for或foreach依次加载数据。
AddRange(totalCount != null ? source : source.LongSkip(pageIndex * pageSize).Take(pageSize));
}
#endregion
#region 属性
/// <summary>
/// 【当前页】
/// <remarks>
/// 摘要:
/// 获取/设置当前页,当前逻辑(内存)页面的长整型索引值,从“0”开始计算。
/// </remarks>
/// </summary>
public long PageIndex { get; }
/// <summary>
/// 【页面大小】
/// <remarks>
/// 摘要:
/// 获取/设置页面大小,即1逻辑(内存)页面最多所存储的实例项的长整型个数值。
/// </remarks>
/// </summary>
public int PageSize { get; }
/// <summary>
/// 【总计值】
/// <remarks>
/// 摘要:
/// 获取/设置数据源实例项的长整型总计值。
/// </remarks>
/// </summary>
public long TotalCount { get; }
/// <summary>
/// 【总页数】
/// <remarks>
/// 摘要:
/// 获取/设置总页数,即数据源实例项,可以分为页数的总计值。
/// </remarks>
/// </summary>
public long TotalPages { get; }
/// <summary>
/// 【有上1页?】
/// <remarks>
/// 摘要:
/// 获取1个值false(没有)/true(有),该值指示在逻辑(内存)分页操作中当前页是否有上1页(第1页之前没有上1页)。
/// </remarks>
/// </summary>
public bool HasPreviousPage => PageIndex > 0;
/// <summary>
/// 【有下1页?】
/// <remarks>
/// 摘要:
/// 获取1个值false(没有)/true(有),该值指示在逻辑(内存)分页操作中当前页是否有下1页(最后1页之后没有下1页)。
/// </remarks>
/// </summary>
public bool HasNextPage => PageIndex + 1 < TotalPages;
#endregion
}
}
4 System.Linq. AsyncIQueryableExtensions(DataExtensionsAsyncIQueryableExtensions.cs)
using Core;
using Core.Extensions;
//Nuget
//Nuget--linq2db
using LinqToDB;
namespace System.Linq
{
/// <summary>
/// 【IQueryable异步扩展类--类】
/// <remarks>
/// 摘要:
/// “IQueryable”实例通过“linq2db”中间件中的异步方法,实现自身的异步操作,这些被扩展的异步方法包括:LongCountAsync、 ToListAsync、ToPagedListAsync。
/// </remarks>
/// </summary>
public static class AsyncIQueryableExtensions
{
/// <typeparam name="T">泛型类型实例(1个指定类的类型实例)。</typeparam>
/// <param name="source">以“IQueryable”实例进行存储的1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)。</param>
/// <param name="pageIndex">当前页的页数值(与“pageSize”结合,设定需要跳过指定实体实例的个数值)。</param>
/// <param name="pageSize">分页操作中每页最多显示实例的项(行)数值(与“pageIndex”结合,设定需要跳过指定实体实例的个数值)。 </param>
/// <param name="getOnlyTotalCount">指示数据源中否包含有实例,默认值:false,该方法使用的是非操作,即数据源中否包含有实例,如果无实例,则(new)新实例化1个逻辑分页实例。</param>
/// <summary>
/// 【异步分页列表】
/// <remarks>
/// 摘要:
/// 以异步操作方式,只从数据源中加载指定1逻辑(内存)页中的数据,存储到“IPagedList(逻辑分页)”的实例中,从而尽量少的从指定表加载数据,从而尽量降低内存的消耗。
/// </remarks>
/// <returns>
/// “IPagedList(逻辑分页)”的实例,该实例存储着数据源中(符合指定查询条件的)所有实例。
/// </returns>
/// </summary>
public static async Task<IPagedList<T>> ToPagedListAsync<T>(this IQueryable<T> source, long pageIndex, int pageSize, bool getOnlyTotalCount = false)
{
if (source == null)
return new PagedList<T>(new List<T>(), pageIndex, pageSize);
//分页操作中每页最多显示实例的项(行)数值,“Math.Max”方法保证该参数实例的最小值必须>=1。
pageSize = Math.Max(pageSize, 1);
var count = await source.CountAsync();
var data = new List<T>();
// 把1个指定类型的数据源(枚举数接口实例=长串型,包含:数组、列表、字典等)1内存页面内的所有项1次性加载到列表实例中,
//AddRange方法是1性把所有的数据加载的目录中,而不必像for或foreach依次加载数据。
if (!getOnlyTotalCount)
data.AddRange(await source.LongSkip(pageIndex * pageSize).Take(pageSize).ToListAsync());
return new PagedList<T>(data, pageIndex, pageSize, count);
}
}
}
5 Data.Repository<TEntity>.GetAllPagedAsync
/// <param name="func">1个具有返回值的泛型委托方法实例,该泛型委托方法实例用于获取1个指定实体的1/n个实例。</param>
/// <param name="pageIndex">当前页的页数值(与“pageSize”结合,设定需要跳过指定实体实例的个数值),默认值:0,即不跳过。</param>
/// <param name="pageSize">分页操作中每页最多显示实例的项(行)数值(与“pageIndex”结合,设定需要跳过指定实体实例的个数值),默认值:int.MaxValue,即跳过int.MaxValue行数。 </param>
/// <param name="getOnlyTotalCount">指示数据源中否包含有实例,默认值:false,该方法使用的是非操作,即数据源中否包含有实例,如果无实例,则(new)新实例化1个逻辑分页实例。</param>
/// <param name="includeDeleted">指示是否包含指定实体中所有的逻辑删除项,默认值:true,即指定实体的所有实例。</param>
/// <summary>
/// 【异步分页列表】
/// <remarks>
/// 摘要:
/// 以异步操作方式,只从数据源中加载指定1逻辑(内存)页中的数据,存储到“IPagedList(逻辑分页)”的实例中,从而尽量少的从指定表加载数据,从而尽量降低内存的消耗。
/// 说明:
/// 该方法没有定义缓存操作,更没有定义缓存的移除操作。
/// </remarks>
/// <returns>
/// “IPagedList(逻辑分页)”的实例,该实例存储着数据源中(符合指定查询条件的)所有实例。
/// </returns>
/// </summary>
public virtual async Task<IPagedList<TEntity>> GetAllPagedAsync(Func<IQueryable<TEntity>, IQueryable<TEntity>> func = null,
long pageIndex = 0, int pageSize = int.MaxValue, bool getOnlyTotalCount = false, bool includeDeleted = true)
{
var query = AddDeletedFilter(Table, includeDeleted);
query = func != null ? func(query) : query;
return await query.ToPagedListAsync(pageIndex, pageSize, getOnlyTotalCount);
}
/// <param name="func">1个具有返回值的泛型异步委托方法实例,该泛型异步委托方法实例用于获取1个指定实体的1/n个实例。</param>
/// <param name="pageIndex">当前页的页数值(与“pageSize”结合,设定需要跳过指定实体实例的个数值),默认值:0,即不跳过。</param>
/// <param name="pageSize">分页操作中每页最多显示实例的项(行)数值(与“pageIndex”结合,设定需要跳过指定实体实例的个数值),默认值:int.MaxValue,即跳过int.MaxValue行数。 </param>
/// <param name="getOnlyTotalCount">指示数据源中否包含有实例,默认值:false,该方法使用的是非操作,即数据源中否包含有实例,如果无实例,则(new)新实例化1个逻辑分页实例。</param>
/// <param name="includeDeleted">指示是否包含指定实体中所有的逻辑删除项,默认值:true,即指定实体的所有实例。</param>
/// <summary>
/// 【异步分页列表】
/// <remarks>
/// 摘要:
/// 以异步操作方式,只从数据源中加载指定1逻辑(内存)页中的数据,存储到“IPagedList(逻辑分页)”的实例中,从而尽量少的从指定表加载数据,从而尽量降低内存的消耗。
/// 说明:
/// 该方法没有定义缓存操作,更没有定义缓存的移除操作。
/// </remarks>
/// <returns>
/// “IPagedList(逻辑分页)”的实例,该实例存储着数据源中(符合指定查询条件的)所有实例。
/// </returns>
/// </summary>
public virtual async Task<IPagedList<TEntity>> GetAllPagedAsync(Func<IQueryable<TEntity>, Task<IQueryable<TEntity>>> func = null,
long pageIndex = 0, int pageSize = int.MaxValue, bool getOnlyTotalCount = false, bool includeDeleted = true)
{
var query = AddDeletedFilter(Table, includeDeleted);
query = func != null ? await func(query) : query;
return await query.ToPagedListAsync(pageIndex, pageSize, getOnlyTotalCount);
}
对以上功能更为具体实现和注释见230525_016ShopRazor(逻辑分页实现)。