首页 > 解决方案 > foreach 中的动态过滤(ASP.NET 和 EF)

问题描述

我有一个非常简单的案例,有一个控制器和一个存储库。

控制器:

    [HttpGet]
    public async Task<IActionResult> GetProductList(ProductQuery queryparams)
    {
        var products = await uow.ProductRepo.GetProductsWithQuery(queryparams);

        var productsToReturn = mapper.Map<IEnumerable<ProductForListDto>>(products);

        return Ok(productsToReturn);
    }

存储库:

    public async Task<AbstractPagedList<Product>>GetProductsWithQuery(ProductQuery qp)
    {
        var products = DorianContext.Products
            .Include(p => p.Category)
            .Include(p => p.PriceOffers)
            .AsQueryable();

        // if (filter.CategoryId.HasValue)
        //     products = products.Where(p => p.CategoryId == filter.CategoryId);
        // if (filter.MinPrice.HasValue)
        //     products = products.Where(p => p.Price >= filter.MinPrice);
        // if (filter.MaxPrice.HasValue)
        //     products = products.Where(p => p.Price <= filter.MaxPrice);

        return await PagedList<Product>.CreateAsync(products, qp.PageNumber, qp.PageSize);
    }

模型:

    public class ProductQuery
    {
        public int? CategoryId { get; set; }
        public decimal? MinPrice { get; set; }
        public decimal? MaxPrice { get; set; }
    }

代替无聊的注释部分,我们如何构建动态/通用逻辑来过滤 CategoryId、MinPrice 和 MaxPrice。(例如在 ProductQuery 的属性列表的 foreach 块中)

也许我们可以像下面这样使用字典对象和 foreach,但我不太确定如何从对象中获取属性名称作为字符串(我尝试使用 NewtonSoft.JObject 但没有成功)

        var filterMap = new Dictionary<string, Expression<Func<Product, bool>>>()
        {
            ["categoryId"] = (v => v.CategoryId == filter.CategoryId),
            ["collectionId"] = (v => v.ProductCollectionId == filter.CollectionId),
            ["minPrice"] = (v => v.Price >= filter.MinPrice),
            ["maxPrice"] = (v => v.Price <= filter.MaxPrice)
        };

        foreach (var key in filterMap)
        {
                products = products.Where(key.Value);
        }

我不想使用反射。还赞赏具有此类案例最佳实践的想法或评论。

标签: c#entity-framework

解决方案


我所做的工作是的,我可以像这样继续,但这会导致大量重复的逻辑。因为这是一个玩具项目,我正在寻找改进它的方法。这样一个项目,我同意这有点过头了。

因此,避免破坏 DRY 原则的最佳方法是在类中创建一个Filters属性,ProductQuery如下所示:

public class ProductQuery
{
    public int? CategoryId { get; set; }
    public decimal? MinPrice { get; set; }
    public decimal? MaxPrice { get; set; }

    public IEnumerable<Expression<Func<Product, bool>>> Filters
    {
        get 
        {
            var filters = new List<Expression<Func<Product, bool>>>();

            if (this.CategoryId.HasValue)
                filters.Add(p => p.CategoryId == this.CategoryId);
            if (this.MinPrice.HasValue)
                filters.Add((p => p.Price >= this.MinPrice);
            if (this.MaxPrice.HasValue)
                filters.Add(p => p.Price <= this.MaxPrice);

            return filters;
        }
    }
}

因此,在您的代码中,您可以像下面这样使用它:

public async Task<AbstractPagedList<Product>>GetProductsWithQuery(ProductQuery qp)
{
    var products = DorianContext.Products
        .Include(p => p.Category)
        .Include(p => p.PriceOffers)
        .AsQueryable();

    foreach(var filter in qp.Filters)
    {
        products = products.Where(filter);
    }

    return await PagedList<Product>.CreateAsync(products, qp.PageNumber, qp.PageSize);
}

推荐阅读