我有一个非常简单的案例,包括控制器和存储库。
控制器:
[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,如下所示,但我不确定如何将Property Names作为字符串从对象中获取(我尝试使用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);
}
我不想使用反射。对此类案例的最佳实践的想法或评论也表示赞赏。
答案 0 :(得分:4)
我所做的是肯定的,我可以像这样继续,但这样做 结果大量重复的逻辑。因为这是一个玩具项目,我 我正在寻找改善它的方法。而这样的项目,就是这样 矫枉过正我同意..
因此,避免破坏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);
}
答案 1 :(得分:0)
也许您可以使用测试函数和表达式对的值元组:
ProductQuery filter = ... // initialize here
var exprs = new List<(Func<ProductQuery, object>, Expression<Func<Product, bool>>)>() {
(f => f.CategoryId, p => p.CategoryId == filter.CategoryId),
(f => f.MinPrice, p => p.Price >= filter.MinPrice),
(f => f.MaxPrice, p => p.Price <= filter.MaxPrice)
};
foreach (var (test, expr) in exprs) {
if (test(filter) != null) {
products = products.Where(expr);
}
}
通过解析表达式树(例如p => p.CategoryId == filter.CategoryId
)并查看正在使用filter
的哪些成员(例如filter.CategoryId
),您可以更进一步。然后,只有当该成员具有值时才能应用该条件:
ProductQuery filter = ... // initialize here
var exprs = new List<Expression<Func<Product, bool>>>() {
p => p.CategoryId == filter.CategoryId,
p => p.Price >= filter.MinPrice,
p => p.Price <= filter.MaxPrice
};
foreach (var expr in exprs) {
var pi = ((expr.Body as BinaryExpression)
.Right as MemberExpression)
.Member as PropertyInfo;
if (pi.GetValue(filter) != null) {
products = products.Where(expr);
}
}
这样,您可以避免定义空检查测试。
解析表达式的代码应该更灵活 - 如果filter属性是表达式中的第一个怎么办?如果某处涉及转换怎么办?
我还建议将构建单个过滤器表达式的逻辑封装为ProductQuery
的属性:
public Expression<Product, bool> Filter => {
get {
// implementation at end of answer
}
}
然后你可以在没有任何循环的情况下调用它:
products = products.Where(filter.Filter);
您可以自己实现,但我强烈建议您使用LINQKit PredicateBuilder:
public Expression<Func<Product, bool>> Filter {
get {
var exprs = new List<Expression<Func<Product, bool>>>() {
p => p.CategoryId == this.CategoryId,
p => p.Price >= this.MinPrice,
p => p.Price <= this.MaxPrice
}.Where(expr => {
PropertyInfo mi = ((expr.Body as BinaryExpression)
.Right as MemberExpression)
.Member as PropertyInfo;
return mi.GetValue(this) != null;
});
var predicate = PredicateBuilder.True<Product>();
foreach (var expr in exprs) {
predicate = predicate.And(expr);
}
return predicate;
}
}