自动过滤:用 AutoFilterer 实现高性能动态查询

🚀 自动过滤:用 AutoFilterer 实现高性能动态查询



🧩 项目场景

在中大型 .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();

🌱 初始化种子数据流程图

false
true
应用启动
创建 Scope
解析 DbContext
db.Books.Any?
执行 AddRange(Books)
SaveChanges()
种子数据完成

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 实现:请求 & 缓存流程图

hit
miss
客户端请求
BooksController
检查缓存
返回缓存结果
应用 ApplyFilter
应用动态排序
执行 CountAsync()
执行 Skip/Take & ToListAsync()
缓存结果
返回结果
[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);
                else
                    query = 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()
    {
        // Arrange
        var data = new[]
        {
            new Book { Title = "alice" },
            new Book { Title = "bob" }
        }.AsQueryable();

        var filter = new BookFilter { Title = "ali" };

        // Act
        var result = data.ApplyFilter(filter).ToList();

        // Assert
        Assert.Single(result);
        Assert.Equal("alice", result[0].Title);
    }
}

📊 最佳实践小结

维度建议
性能开启表达式缓存;合理设置缓存过期
安全性DTO 参数加 [Range] 限制;细粒度异常返回
可维护性提取 DbSeederPagedResult<T>IBookRepository 等;分层结构
文档体验Mermaid 图导出为图片;示例请求/响应;代码高亮
扩展性动态排序(System.Linq.Dynamic.Core);分布式/共享缓存方案
可测试性单元测试覆盖过滤逻辑;集成测试验证 API 行为

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kookoos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值