使用LINQ基于多个参数进行过滤?

时间:2016-07-08 22:49:21

标签: c# asp.net asp.net-mvc entity-framework linq

我正在使用ASP.NET MVC 5,EF6和LINQ开发电子商务网站。我的数据库中有产品表。在我的UI中,我有多个参数来过滤我的产品:

  • 不同类别的复选框
  • 最低和最高价格
  • 不同颜色的复选框

我写了这个动作方法:

[HttpPost]
public PartialViewResult FilterProducts(int[] categoriesIds, decimal minPrice, decimal? maxPrice, string[] colors)
{
        if (categoriesIds == null)
        {
            var randomProducts = db.Products
                .OrderBy(p => Guid.NewGuid());
            return PartialView("_LoadProducts", randomProducts.ToList());
        }
        else
        {
            var filteredProducts = db.Products
                .Where(p => categoriesIds.Contains(p.CategoryId)
                    && (p.DiscountedPrice >= minPrice
                        && p.DiscountedPrice <= maxPrice || maxPrice == null));

            if (colors != null)
            {
                filteredProducts = filteredProducts
                    .Where(p => colors.Contains(p.Color));
            }
            return PartialView("_LoadProducts", filteredProducts.ToList());
        }
}

这很好用。但我很困惑这是否可以改善?它包含了很多if / else语句,只是为了过滤产品。我的整体设计有问题吗?有没有最佳做法? 任何帮助将受到高度赞赏。感谢。

2 个答案:

答案 0 :(得分:1)

您可以将业务逻辑提取到帮助器或服务类(例如ProductsService),并将每个实体的数据库查询提取到存储库层(例如ProductsRepository,UsersRepository等等)

public class ProductsRepository : BaseRepository<Product> // You can have base implementation for basic CRUD operations
{
     MyDbContext db;
     public ProductsRepository(MyDbContext db)
     {
         this.db = db;
     }

     public IQueryable<Product> GetAll()
     {
          return db.Products;
     }
}

public class ProductsService : BaseService<Product> // again here....
{
     ProductsRepository repo;

     public ProductsService()
     {
          repo = new ProductsRepository(new MyDbContext()); // Intentionally not using Dependency Injection here for simplicity....
     }

     public List<Product> FilterBy(int[] categoriesIds, decimal minPrice, decimal? maxPrice, string[] colors){
     {
         if (categoriesIds == null)
         {
             var randomProducts = repo.GetAll().OrderBy(p => Guid.NewGuid());
             return randomProducts.ToList();
         }
         else
         {
          var filteredProducts = repo.GetAll()
            .Where(p => categoriesIds.Contains(p.CategoryId)
                && (p.DiscountedPrice >= minPrice
                    && p.DiscountedPrice <= maxPrice || maxPrice == null));

        if (colors != null)
        {
            filteredProducts = filteredProducts
                .Where(p => colors.Contains(p.Color));
        }
        return filteredProducts.ToList();
    }
}

MVC

ProductsService productsService;
public MyController() // constructor
{
   productsService = new ProductsService(); // Again, no Dependency Injection here for simplicity
}

[HttpPost]
public PartialViewResult FilterProducts(int[] categoriesIds, decimal minPrice, decimal? maxPrice, string[] colors)
{
     List<Product> products = productsService.FilterBy(categoriesIds, minPrice, maxPrice, colors);

     return PartialView("_LoadProducts", products);
}

那么这里有什么意义......正如您所看到的,您拥有的代码比平时多,但这种方法可以使您的代码更好地分离并且可以轻松重用。您可以反复使用应用中任意位置的FilterBy ProductsService方法,而无需多次重复查询。此外,您的控制器更轻,易读,这是最好的方法 - 您不应该直接在控制器中进行繁重的业务逻辑/数据库操作。这就是为什么将数据库查询和业务逻辑分离到单独的文件并尽可能重用它们的原因。这导致更少的错误。这是SOLID principles的第一条规则 - 一个类或方法应该只有一个责任 - 这意味着如果你的方法要从数据库中返回项目,它应该只做那个而不是更多。

希望我有用!

答案 1 :(得分:1)

添加到@GeorgeFindulov的答案

将您的输入作为标准的预定义对象

SELECT COUNT(Orders.EmployeeID) 
FROM Orders 
WHERE (Orders.EmployeeID IS NULL) 
  AND (IN(SELECT Orders.EmployeeID
          FROM Orders
          RIGHT JOIN Employees ON Orders.EmployeeID = Employees.EmployeeID)) 
GROUP BY Orders.EmplyoeeID;

这将使您的控制器保持清洁,并且对于新的输入保持不变。如果将来有一些新的过滤器,则只需在类FilterD中添加一个新属性即可保持action方法不变。 您可以使用自动映射器将此DTO转换为业务对象。

public class FilterDto
{
    public int[] Categories { get; set; }
    public int? MinPrice{ get; set; }
    public int? MaxPrice{ get; set; }
    //...
}

注意:您的服务应始终返回比特定模型更大的业务模型,然后您的控制器将转换为DTO或视图模型。