自动过滤:用 AutoFilterer 实现高性能动态查询
🚀 自动过滤:用 AutoFilterer 实现高性能动态查询
📚 目录
- 🚀 自动过滤:用 AutoFilterer 实现高性能动态查询
- 🧩 项目场景
- 🌟 AutoFilterer 核心优势
- 🎯 项目结构流程图
- 🛠 快速集成
- 1️⃣ 安装依赖
- 2️⃣ 注册服务(Program.cs)
- 🌱 初始化种子数据流程图
- 3️⃣ 实体与过滤 DTO
- 4️⃣ Controller 实现:请求 & 缓存流程图
- 🌐 示例请求与响应
- 🧪 单元测试示例(xUnit)
- 📊 最佳实践小结
🧩 项目场景
在中大型 .NET Web API 项目中,数据查询过滤是高频且重复度极高的任务。传统方式下:
- 每个字段都需写
Where
条件; - 多字段组合查询冗长、易错;
- Swagger 接口测试缺乏一致性与自动生成文档;
因此,引入 AutoFilterer —— 一个属性驱动的 LINQ 表达式生成器,支持动态查询和 OpenAPI 自动展示,全面提升代码质量与开发效率。
🌟 AutoFilterer 核心优势
🧩 特性 | ✨ 描述 |
---|---|
属性驱动 | DTO 上声明过滤规则,自动生成表达式 |
无需手写 | 摒弃手工 LINQ 代码 |
多条件支持 | 字符串、区间、布尔、枚举、日期等 |
OpenAPI 3.0 | 与 Swagger UI 无缝集成 |
高性能 | 表达式缓存、延迟执行、支持 EF Core |
支持分页 | 内置 PaginationFilterBase |
🎯 项目结构流程图
🛠 快速集成
1️⃣ 安装依赖
dotnet add package AutoFilterer.Extensions.Microsoft.DependencyInjection --version 3.1.0
dotnet add package AutoFilterer.Swagger --version 3.1.0
dotnet add package System.Linq.Dynamic.Core --version 1.6.5
2️⃣ 注册服务(Program.cs)
using AutoFilterer.Swagger;
using Microsoft.EntityFrameworkCore;
using System.Text.Json;var builder = WebApplication.CreateBuilder(args);// 1. AutoFilterer + 表达式缓存
builder.Services.AddAutoFilterer(options =>
{options.EnableExpressionCache = true;
});// 2. Swagger 集成 AutoFilterer 参数
builder.Services.AddSwaggerGen(c =>
{c.UseAutoFiltererParameters();c.SwaggerDoc("v1", new() { Title = "Book API", Version = "v1" });
});// 3. EF Core:演示用 InMemory,生产可切换 SQL Server
builder.Services.AddDbContext<AppDbContext>(opt =>
{opt.UseInMemoryDatabase("BooksDb");// 生产示例:// opt.UseSqlServer(builder.Configuration.GetConnectionString("Default"));
});// 4. MVC + 全局 camelCase JSON 命名
builder.Services.AddControllers().AddJsonOptions(opts =>{opts.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;});// 5. 内存缓存,用于结果缓存
builder.Services.AddMemoryCache();var app = builder.Build();// Seed 数据
using (var scope = app.Services.CreateScope())
{var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();DbSeeder.Seed(db);
}// 中间件
app.UseSwagger();
app.UseSwaggerUI();
app.MapControllers();
app.Run();
🌱 初始化种子数据流程图
3️⃣ 实体与过滤 DTO
public class Book
{public Guid Id { get; set; }public string Title { get; set; }public string Author { get; set; }public DateTime PublishedDate { get; set; }public int PageCount { get; set; }
}
using AutoFilterer.Attributes;
using AutoFilterer.Types;
using System.ComponentModel.DataAnnotations;public class BookFilter : PaginationFilterBase
{[ToLowerContainsComparison]public string? Title { get; set; }[ToLowerContainsComparison]public string? Author { get; set; }[CompareTo(nameof(Book.PublishedDate), FilterType.GreaterThanOrEqual)]public DateTime? FromDate { get; set; }[CompareTo(nameof(Book.PublishedDate), FilterType.LessThanOrEqual)]public DateTime? ToDate { get; set; }[CompareTo(nameof(Book.PageCount), FilterType.GreaterThan)]public int? MinPages { get; set; }/// <summary>/// 支持动态排序,格式示例:"PublishedDate desc"/// </summary>public string? SortBy { get; set; }[Range(1, 100)]public override int PageSize { get; set; } = 10;
}
4️⃣ Controller 实现:请求 & 缓存流程图
[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{private readonly AppDbContext _db;private readonly ILogger<BooksController> _logger;private readonly IMemoryCache _cache;public BooksController(AppDbContext db,ILogger<BooksController> logger,IMemoryCache cache){_db = db;_logger = logger;_cache = cache;}[HttpGet]public async Task<ActionResult<PagedResult<Book>>> Get([FromQuery] BookFilter filter){string cacheKey = $"books_{filter.GetCacheKey()}_{filter.SortBy}";try{if (!_cache.TryGetValue(cacheKey, out PagedResult<Book> result)){// 1. 应用过滤器var query = _db.Books.ApplyFilter(filter);// 2. 动态排序(依赖 System.Linq.Dynamic.Core)if (!string.IsNullOrWhiteSpace(filter.SortBy))query = query.OrderBy(filter.SortBy);elsequery = query.OrderBy("PublishedDate desc");// 3. 分页 + 总数var total = await query.CountAsync();var items = await query.Skip((filter.PageNumber - 1) * filter.PageSize).Take(filter.PageSize).ToListAsync();result = new PagedResult<Book>(total, items);// 缓存 5 分钟_cache.Set(cacheKey, result, TimeSpan.FromMinutes(5));}return Ok(result);}catch (ArgumentException ex){_logger.LogWarning(ex, "Invalid filter parameter");return BadRequest("请求参数错误");}catch (DbUpdateException ex){_logger.LogError(ex, "Database update failure");return StatusCode(503, "服务暂不可用");}catch (Exception ex){_logger.LogError(ex, "Unexpected error");return StatusCode(500, "查询失败");}}
}// 分页结果模型
public record PagedResult<T>(int Total, List<T> Items);
🌐 示例请求与响应
GET /api/books?Author=alice&MinPages=110&SortBy=PageCount%20desc&PageSize=5&PageNumber=1
{"total": 25,"items": [{ "id": "guid", "title": "Book 48", "author": "Alice", "publishedDate": "2025-05-02T00:00:00", "pageCount": 148 },...]
}
🧪 单元测试示例(xUnit)
public class BookFilterTests
{[Fact]public void ApplyFilter_ShouldFilterByTitle(){// Arrangevar data = new[]{new Book { Title = "alice" },new Book { Title = "bob" }}.AsQueryable();var filter = new BookFilter { Title = "ali" };// Actvar result = data.ApplyFilter(filter).ToList();// AssertAssert.Single(result);Assert.Equal("alice", result[0].Title);}
}
📊 最佳实践小结
维度 | 建议 |
---|---|
性能 | 开启表达式缓存;合理设置缓存过期 |
安全性 | DTO 参数加 [Range] 限制;细粒度异常返回 |
可维护性 | 提取 DbSeeder 、PagedResult<T> 、IBookRepository 等;分层结构 |
文档体验 | Mermaid 图导出为图片;示例请求/响应;代码高亮 |
扩展性 | 动态排序(System.Linq.Dynamic.Core);分布式/共享缓存方案 |
可测试性 | 单元测试覆盖过滤逻辑;集成测试验证 API 行为 |