Фильтр / Поиск с использованием нескольких полей - ASP.NET MVC
Я использую ASP.NET MVC с EF 6.
У меня есть биржевая страница, на которой показана вся информация о товарах со склада. Теперь я хочу фильтровать записи тоже.
На картинке ниже у меня есть 3 варианта. Я мог бы фильтровать по каждому варианту, по одному за раз или по комбинации двух или со всеми тремя.
Я думал о написании linq-запроса для каждой выбранной опции. Но это не будет возможно, если увеличится опция фильтра. Есть лучший способ сделать это.
Спасибо!
Это то, что я сделал в своем контроллере (в настоящее время выпадающий имеет две опции, исключая: " - выберите один - ")
public ActionResult StockLevel(string option, string batch, string name)
{
if (option != "0" && batch == "" && name == "")
{
if(option == "BelowMin")
{
List<Stock> stk = (from s in db.Stocks
where s.Qty < s.Item.AlertQty
select s).ToList();
return View(stk);
}
else
{
List<Stock> stk = (from s in db.Stocks
where s.Qty == s.InitialQty
select s).ToList();
return View(stk);
}
}
if (option == "0" && batch != "" && name == "")
{
List<Stock> stk = (from s in db.Stocks
where s.BatchNo == batch
select s).ToList();
return View(stk);
}
if (option == "0" && batch == "" && name != "")
{
List<Stock> stk = (from s in db.Stocks
where s.Item.Name.StartsWith(""+name+"")
select s).ToList();
return View(stk);
}
return View(db.Stocks.ToList());
}
3 ответа
Я рекомендую вам разделить проблемы и использовать подход, чтобы код в вашем контроллере был таким, простым, красивым и расширяемым:
public ActionResult Index(ProductSearchModel searchModel)
{
var business = new ProductBusinessLogic();
var model = business.GetProducts(searchModel);
return View(model);
}
Выгоды:
- Вы можете положить все, что вам нужно в вашем
ProductSearchModel
основываясь на ваших требованиях. - Вы можете написать любую логику в
GetProducts
на основании требований. Там нет ограничений. - Если вы добавите новое поле или опцию для поиска, ваши действия и контроллер останутся нетронутыми.
- Если логика вашего поиска изменится, ваши действия и контроллер останутся нетронутыми.
- Вы можете повторно использовать логику поиска везде, где вам нужно искать товары, контроллеры или даже другую бизнес-логику.
- Имея такой
ProductSearchModel
Вы можете использовать его как модельProductSearch
частичный вид и вы можете подать заявкуDataAnnotations
к нему, чтобы улучшить проверку модели и помочь пользовательскому интерфейсу визуализировать его с помощьюDisplay
или другие атрибуты. - В этот класс бизнес-логики вы можете добавить другую бизнес-логику, связанную с вашим продуктом.
- Следуя этому пути, вы можете получить более организованное приложение.
Пример реализации:
Предположим, у вас есть Product
учебный класс:
public class Product
{
public int Id { get; set; }
public int Price { get; set; }
public string Name { get; set; }
}
Вы можете создать ProductSearchModel
Класс и поместите некоторые поля, которые вы хотите искать на основе них:
public class ProductSearchModel
{
public int? Id { get; set; }
public int? PriceFrom { get; set; }
public int? PriceTo { get; set; }
public string Name { get; set; }
}
Тогда вы можете поместить свою логику поиска в ProductBusinessLogic
Класс таким образом:
public class ProductBusinessLogic
{
private YourDbContext Context;
public ProductBusinessLogic()
{
Context = new YourDbContext();
}
public IQueryable<Product> GetProducts(ProductSearchModel searchModel)
{
var result = Context.Products.AsQueryable();
if (searchModel != null)
{
if (searchModel.Id.HasValue)
result = result.Where(x => x.Id == searchModel.Id);
if (!string.IsNullOrEmpty(searchModel.Name))
result = result.Where(x => x.Name.Contains(searchModel.Name));
if (searchModel.PriceFrom.HasValue)
result = result.Where(x => x.Price >= searchModel.PriceFrom);
if (searchModel.PriceTo.HasValue)
result = result.Where(x => x.Price <= searchModel.PriceTo);
}
return result;
}
}
Тогда в вашем ProductController
Вы можете использовать этот способ:
public ActionResult Index(ProductSearchModel searchModel)
{
var business = new ProductBusinessLogic();
var model = business.GetProducts(searchModel);
return View(model);
}
Важная заметка:
В реальной реализации, пожалуйста, подумайте о реализации подходящего Dispose
шаблон для вашего бизнес-класса, чтобы избавиться от контекста БД при необходимости. Для получения дополнительной информации взгляните на реализацию метода Dispose или шаблона Dispose.
Условная фильтрация
.ToList()
, .First()
, .Count()
и несколько других методов выполняют окончательный запрос LINQ. Но перед его выполнением вы можете применить фильтры следующим образом:
var stocks = context.Stocks.AsQueryable();
if (batchNumber != null) stocks = stocks.Where(s => s.Number = batchNumber);
if (name != null) stocks = stocks.Where(s => s.Name.StartsWith(name));
var result = stocks.ToList(); // execute query
Где расширение LINQ
просто WhereIf
Можно существенно упростить код:
var result = db.Stocks
.WhereIf(batchNumber != null, s => s.Number == batchNumber)
.WhereIf(name != null, s => s.Name.StartsWith(name))
.ToList();
Где реализация. Это простой метод расширения для IQueryable
:
public static class CollectionExtensions
{
public static IQueryable<TSource> WhereIf<TSource>(
this IQueryable<TSource> source,
bool condition,
Func<TSource, bool> predicate)
{
if (condition)
return source.Where(predicate).AsQueryable();
else
return source;
}
}
Не в тех случаях, когда LINQ способ (рекомендуется)
WhereIf
обеспечивает более декларативный способ, если вы не хотите использовать расширения, вы можете просто отфильтровать так:
var result = context.Stocks
.Where(batchNumber == null || stock.Number == batchNumber)
.Where(name == null || s => s.Name.StartsWith(name))
.ToList();
Это дает точно такой же эффект, как WhereIf
и он будет работать быстрее, так как во время выполнения потребуется создать только одно ExpressionTree вместо построения нескольких деревьев и их объединения.
Я написал несколько расширений, чтобы сделать это проще. https://www.nuget.org/packages/LinqConditionalExtensions/
Это не изобретать велосипед. Некоторые из расширений уже были рекомендованы. Вы можете переписать свою логику следующим образом.
var results = db.Stocks
.If(option != "0", stocks => stocks
.IfChain(option == "BelowMin", optionStocks => optionStocks
.Where(stock => stock.Qty < stock.Item.AlertQty))
.Else(optionStocks => optionStocks
.Where(stock => stock.Qty == stock.InitialQty)))
.WhereIf(!string.IsNullOrWhiteSpace(batch), stock => stock.BatchNo == batch)
.WhereIf(!string.IsNullOrWhiteSpace(name), stock => stock.Item.Name.StartsWith("" + name + ""))
.ToList();
return results;
В основном, начальный If()
Метод применит переданную цепочку if, если условие истинно. IfChain()
ваше вложенное выражение if-else. IfChain()
позволяет связать несколько IfElse()
и заканчивается Else()
,
WhereIf()
будет только условно применять ваше условие where, если условие истинно.
Если вы заинтересованы в библиотеке, https://github.com/xKloc/LinqConditionalExtensions имеет readme.
Самый простой и мощный подход - использовать OData. Вы можете начать работу с https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/supporting-odata-query-options Применяйте только фильтр, а затем отображайте и форматируйте результаты по своему усмотрению, я бы рекомендовал раздел "Прямой вызов параметров запроса" из приведенной выше ссылки.
public ActionResult Index(string searchid)
{
var personTables = db.PersonTables.Where(o => o.Name.StartsWith(searchid) )|| o.CombanyTable.ComName.StartsWith(searchid) ).Include(k => k.CombanyTable);
return View(personTables.ToList());
}