[英]Dynamic Filtering in 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);
}
我不想使用反射。 同時也贊賞具有這種情況的最佳實踐的想法或意見。
我所做的工作是可以的,我可以像這樣繼續下去,但這將導致大量重復的邏輯。 因為這是一個玩具項目,所以我正在尋找改進它的方法。 這樣的項目,我同意這太過分了。
因此,避免破壞DRY原理的最佳方法是在ProductQuery
類中創建一個Filters
屬性,如下所示:
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);
}
也許您可以使用測試函數和表達式對的值元組:
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
),您可以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;
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.