Files
sharp-basic/test/Data.Test/05_PaginationTests.cs
T
2025-12-30 09:17:25 +08:00

405 lines
13 KiB
C#

using Common.ExpandFun;
using Microsoft.EntityFrameworkCore;
using Sharp.Data;
using Sharp.Data.Context;
namespace Data.Test;
/// <summary>
/// 演示 05: 分页查询
///
/// 本测试类演示:
/// 1. 基础分页查询
/// 2. 带条件的分页
/// 3. 带排序的分页
/// 4. 分页结果的使用
/// 5. 复用 Common 模块的 PagedResult
/// </summary>
[TestClass]
public class _05_PaginationTests
{
private IDataContext _db = null!;
[TestInitialize]
public async Task Setup()
{
var builder = new DataContextBuilder<TestDbContext>();
builder.UseInMemoryDatabase($"PaginationDemo_{Guid.NewGuid()}");
_db = builder.Build();
// 添加测试数据:100 个产品
var repo = _db.Repository<Product>();
var products = new List<Product>();
for (var i = 1; i <= 100; i++)
products.Add(new Product
{
Name = $"产品{i:D3}",
Price = (i % 10 + 1) * 100m,
Stock = i * 5,
Description = i % 2 == 0 ? "热门产品" : "普通产品"
});
await repo.AddRangeAsync(products);
await _db.SaveChangesAsync();
Console.WriteLine("✓ 初始化完成:创建了 100 个测试产品\n");
}
[TestCleanup]
public void Cleanup()
{
_db?.Dispose();
}
/// <summary>
/// 演示: 基础分页查询
/// </summary>
[TestMethod]
public async Task Demo_BasicPagination()
{
var repo = _db.Repository<Product>();
// 查询第 1 页,每页 10 条
var page1 = await repo.GetPagedAsync(
1,
10);
Console.WriteLine($"第 1 页:");
Console.WriteLine($" 当前页: {page1.PageIndex}");
Console.WriteLine($" 每页数量: {page1.PageSize}");
Console.WriteLine($" 总记录数: {page1.TotalCount}");
Console.WriteLine($" 总页数: {page1.TotalPages}");
Console.WriteLine($" 是否有上一页: {page1.HasPreviousPage}");
Console.WriteLine($" 是否有下一页: {page1.HasNextPage}");
// 验证
Assert.AreEqual(1, page1.PageIndex);
Assert.AreEqual(10, page1.PageSize);
Assert.AreEqual(100, page1.TotalCount);
Assert.AreEqual(10, page1.TotalPages);
Assert.AreEqual(10, page1.Items.Count);
Assert.IsFalse(page1.HasPreviousPage);
Assert.IsTrue(page1.HasNextPage);
Console.WriteLine($"\n产品列表:");
foreach (var product in page1.Items) Console.WriteLine($" - {product.Name}: {product.Price:C}");
}
/// <summary>
/// 演示: 遍历所有页
/// </summary>
[TestMethod]
public async Task Demo_IterateAllPages()
{
var repo = _db.Repository<Product>();
var pageSize = 15;
var currentPage = 1;
Console.WriteLine($"=== 遍历所有页(每页 {pageSize} 条)===\n");
PagedResult<Product> pageResult;
do
{
pageResult = await repo.GetPagedAsync(currentPage, pageSize);
Console.WriteLine($"第 {currentPage} 页: {pageResult.Items.Count} 条记录");
Console.WriteLine(
$" 范围: {(currentPage - 1) * pageSize + 1} - {Math.Min(currentPage * pageSize, pageResult.TotalCount)}");
// 处理当前页数据
foreach (var product in pageResult.Items)
{
// 业务逻辑...
}
currentPage++;
} while (pageResult.HasNextPage);
Console.WriteLine($"\n✓ 总共遍历了 {currentPage - 1} 页");
Assert.AreEqual(7, currentPage - 1); // 100 / 15 = 7 页
}
/// <summary>
/// 演示: 带条件的分页查询
/// </summary>
[TestMethod]
public async Task Demo_PaginationWithFilter()
{
var repo = _db.Repository<Product>();
// 查询价格 > 500 的产品,第 1 页,每页 10 条
var result = await repo.GetPagedAsync(
1,
10,
p => p.Price > 500m);
Console.WriteLine($"查询条件: 价格 > 500");
Console.WriteLine($" 总记录数: {result.TotalCount}");
Console.WriteLine($" 总页数: {result.TotalPages}");
Console.WriteLine($" 当前页记录数: {result.Items.Count}");
Console.WriteLine($"\n产品列表:");
foreach (var product in result.Items)
{
Console.WriteLine($" - {product.Name}: {product.Price:C}");
Assert.IsTrue(product.Price > 500m);
}
}
/// <summary>
/// 演示: 带排序的分页查询
/// </summary>
[TestMethod]
public async Task Demo_PaginationWithOrdering()
{
var repo = _db.Repository<Product>();
// 按价格降序排列
var result = await repo.GetPagedAsync(
1,
10,
null,
q => q.OrderByDescending(p => p.Price));
Console.WriteLine($"排序: 按价格降序");
Console.WriteLine($"\n前 10 个最贵的产品:");
foreach (var product in result.Items) Console.WriteLine($" - {product.Name}: {product.Price:C}");
// 验证排序
for (var i = 0; i < result.Items.Count - 1; i++)
Assert.IsTrue(result.Items[i].Price >= result.Items[i + 1].Price);
}
/// <summary>
/// 演示: 复杂分页查询 - 组合条件、排序、分页
/// </summary>
[TestMethod]
public async Task Demo_ComplexPagination()
{
var repo = _db.Repository<Product>();
// 查询热门产品,价格 > 300,按库存降序,第 2 页,每页 5 条
var result = await repo.GetPagedAsync(
2,
5,
p => p.Description == "热门产品" && p.Price > 300m,
q => q.OrderByDescending(p => p.Stock));
Console.WriteLine($"复杂查询:");
Console.WriteLine($" 条件: 热门产品 AND 价格>300");
Console.WriteLine($" 排序: 按库存降序");
Console.WriteLine($" 分页: 第 2 页,每页 5 条");
Console.WriteLine($"\n结果:");
Console.WriteLine($" 总记录数: {result.TotalCount}");
Console.WriteLine($" 总页数: {result.TotalPages}");
Console.WriteLine($" 当前页码: {result.PageIndex}");
Console.WriteLine($"\n产品列表:");
foreach (var product in result.Items)
{
Console.WriteLine($" - {product.Name}: {product.Price:C}, 库存: {product.Stock}");
Assert.AreEqual("热门产品", product.Description);
Assert.IsTrue(product.Price > 300m);
}
}
/// <summary>
/// 演示: 分页辅助信息的使用
/// </summary>
[TestMethod]
public async Task Demo_PaginationHelpers()
{
var repo = _db.Repository<Product>();
// 第 5 页,每页 10 条
var result = await repo.GetPagedAsync(5, 10);
Console.WriteLine($"=== 分页辅助信息 ===\n");
Console.WriteLine($"当前页: {result.PageIndex}");
Console.WriteLine($"每页数量: {result.PageSize}");
Console.WriteLine($"总记录数: {result.TotalCount}");
Console.WriteLine($"总页数: {result.TotalPages}");
Console.WriteLine($"是否第一页: {result.PageIndex == 1}");
Console.WriteLine($"是否最后一页: {result.PageIndex == result.TotalPages}");
Console.WriteLine($"有上一页: {result.HasPreviousPage}");
Console.WriteLine($"有下一页: {result.HasNextPage}");
// 计算记录范围
var startIndex = (result.PageIndex - 1) * result.PageSize + 1;
var endIndex = Math.Min(result.PageIndex * result.PageSize, result.TotalCount);
Console.WriteLine($"\n记录范围: {startIndex} - {endIndex}");
// 验证
Assert.AreEqual(5, result.PageIndex);
Assert.AreEqual(41, startIndex);
Assert.AreEqual(50, endIndex);
Assert.IsTrue(result.HasPreviousPage);
Assert.IsTrue(result.HasNextPage);
}
/// <summary>
/// 演示: 空结果的分页
/// </summary>
[TestMethod]
public async Task Demo_EmptyPageResult()
{
var repo = _db.Repository<Product>();
// 查询不存在的条件
var result = await repo.GetPagedAsync(
1,
10,
p => p.Price > 99999m);
Console.WriteLine($"查询不存在的记录:");
Console.WriteLine($" 总记录数: {result.TotalCount}");
Console.WriteLine($" 总页数: {result.TotalPages}");
Console.WriteLine($" 当前页记录数: {result.Items.Count}");
Assert.AreEqual(0, result.TotalCount);
Assert.AreEqual(0, result.TotalPages);
Assert.AreEqual(0, result.Items.Count);
Assert.IsFalse(result.HasPreviousPage);
Assert.IsFalse(result.HasNextPage);
}
/// <summary>
/// 演示: 超出范围的页码
/// </summary>
[TestMethod]
public async Task Demo_OutOfRangePage()
{
var repo = _db.Repository<Product>();
// 请求第 999 页(超出实际页数)
var result = await repo.GetPagedAsync(999, 10);
Console.WriteLine($"请求超出范围的页码 (第 999 页):");
Console.WriteLine($" 总记录数: {result.TotalCount}");
Console.WriteLine($" 总页数: {result.TotalPages}");
Console.WriteLine($" 当前页码: {result.PageIndex}");
Console.WriteLine($" 当前页记录数: {result.Items.Count}");
// 超出范围返回空列表
Assert.AreEqual(0, result.Items.Count);
Assert.AreEqual(999, result.PageIndex);
Assert.IsFalse(result.HasNextPage);
}
/// <summary>
/// 演示: 在 UI 中使用分页
///
/// 模拟 Web API 或 WPF 应用的分页场景
/// </summary>
[TestMethod]
public async Task Demo_PaginationInUI()
{
var repo = _db.Repository<Product>();
// 模拟用户请求
var requestedPage = 3;
var pageSize = 20;
var searchTerm = "产品0"; // 用户搜索
Console.WriteLine($"=== UI 分页场景 ===");
Console.WriteLine($"用户请求: 第 {requestedPage} 页, 每页 {pageSize} 条");
Console.WriteLine($"搜索词: {searchTerm}\n");
// 执行分页查询
var result = await repo.GetPagedAsync(
requestedPage,
pageSize,
string.IsNullOrEmpty(searchTerm)
? null
: p => p.Name.Contains(searchTerm),
q => q.OrderBy(p => p.Name));
// 构建返回给前端的数据
var uiData = new
{
Success = true,
Data = new
{
Items = result.Items.Select(p => new
{
p.Id,
p.Name,
p.Price,
p.Stock
}).ToList(),
Pagination = new
{
result.PageIndex,
result.PageSize,
result.TotalCount,
result.TotalPages,
result.HasPreviousPage,
result.HasNextPage
}
}
};
Console.WriteLine($"返回数据:");
Console.WriteLine($" 记录数: {uiData.Data.Items.Count}");
Console.WriteLine($" 分页信息: {uiData.Data.Pagination.PageIndex}/{uiData.Data.Pagination.TotalPages}");
Console.WriteLine($"\n✓ 可以直接序列化为 JSON 返回给前端");
}
/// <summary>
/// 演示: PagedResult 来自 Common 模块
///
/// Sharp.Data 复用 Common.ExpandFun.PagedResult<T>,避免重复定义
/// </summary>
[TestMethod]
public async Task Demo_PagedResultFromCommonModule()
{
var repo = _db.Repository<Product>();
var result = await repo.GetPagedAsync(1, 10);
// PagedResult<T> 来自 Common.ExpandFun 命名空间
Console.WriteLine($"✓ PagedResult<T> 的类型信息:");
Console.WriteLine($" 类型: {result.GetType().FullName}");
Console.WriteLine($" 命名空间: {result.GetType().Namespace}");
Console.WriteLine($"\n✓ 复用 Common 模块,避免重复造轮子");
Assert.AreEqual("Common.ExpandFun", result.GetType().Namespace);
}
/// <summary>
/// 演示: 自定义扩展方法进行分页
/// </summary>
[TestMethod]
public async Task Demo_CustomPaginationExtension()
{
// 使用 EF Core 的 IQueryable 直接分页
var query = _db.Context.Set<Product>()
.Where(p => p.Price > 500m)
.OrderBy(p => p.Name);
// 手动实现分页逻辑
var page = 2;
var pageSize = 10;
var totalCount = await query.CountAsync();
var items = await query
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// 构建 PagedResult
var result = new PagedResult<Product>
{
Items = items,
TotalCount = totalCount,
PageIndex = page,
PageSize = pageSize
};
Console.WriteLine($"自定义分页:");
Console.WriteLine($" 总记录数: {result.TotalCount}");
Console.WriteLine($" 当前页: {result.PageIndex}");
Console.WriteLine($" 记录数: {result.Items.Count}");
Console.WriteLine($"\n✓ 可以直接使用 IQueryable 和 PagedResult 进行自定义分页");
}
}