【问题标题】:EntityFramework / Get two objects from database with lowest priceEntityFramework / 从数据库中以最低价格获取两个对象
【发布时间】:2020-11-28 20:55:54
【问题描述】:

我正在开发一个 web api 项目。我正在使用依赖注入,所以我在ProductRepository 类中创建了我所有的方法。具体public IQueryable<Product> GetTwoWithLowestPrice()方法。

我需要实现的是从数据库中返回 2 个价格最低的产品。

我试图从数据库中获取所有产品并升序排序,这意味着2个价格最低的产品在开头 这个sorted 列表。因此,我将 index 位置 0 和 1(前两个)上的产品添加到我的 retVal 列表中......我可能不允许如此轻松地将列表投射到不同的集合中而不会丢失数据(我不熟悉每个集合在那里)。代码:

 public  IQueryable<Product> GetTwoWithLowestPrice()
        {
            List<Product> retVal = new List<Product>();

            var sorted = db.Products.Include(p => p.Category).OrderBy(p => p.Price);

            var list = sorted as List<Product>;

            retVal.Add(list[0]);
            retVal.Add(list[1]);


            return retVal as IQueryable<Product>;

        }

这会引发异常

('Object reference not set to an instance of an object.'   list was null.)

【问题讨论】:

  • 虽然您的思维过程最初可能会起作用(从数据库中获取记录,然后将它们排序以获得最低价格),但如果您将来有大量产品,您可能不得不返回并排序一个大型数据集。使用数据库查询检索两个最低价格几乎总是更有效。
  • @Leroy 我想到了这一点,但我正在寻找一种替代/更快的方式(使用 EntitiyFramework)。但无论如何,你会怎么做呢?使用 string 查询、SqlDataReaderSqlCommand ?到目前为止,这是我从数据库中获取数据的唯一方法。
  • 这个问题的答案取决于您选择的数据库,因为 SQL 查询会因此而改变。一个简单的例子是:SELECT TOP 2 product FROM products ORDER BY price ASC 当然,上面的 SQL 表/字段名称应该更改以匹配您的数据库包含的内容。
  • @Leroy 明白了。虽然我需要知道这是否可以使用 EntityFramework 以某种方式完成
  • 使用 Take 和 ToList。但总的来说:尝试了解您在做什么,而不是反复试验。

标签: database entity-framework methods repository


【解决方案1】:

您的代码示例看起来是在不必要地混合了许多技术/实践。

首先:

public  IQueryable<Product> GetTwoWithLowestPrice()

IQueryable 是一个以 Linq 可以查询的方式公开数据的接口。它是一种非常强大的类型,可以让您简化返回类型,尤其是 EF 实体,以便理解数据的调用者可以通过多种方式进一步减少或使用它。通常,这将用于类似 Repository 模式的方法,例如:

public IQueryable<Product> GetProducts()

甚至:

public IQueryable<Product> GetProductById(int productId)

采用这种方法的原因可能是为了集中有关数据的基本规则。例如默认只返回活动产品,或者执行基本级别的规则,例如强制用户只能接收适用于他们的产品。即使对于预期返回单个产品的方法,返回 IQueryable&lt;Product&gt; 也可能看起来很奇怪,但它允许调用者简化查询:

产品是否存在?

bool exists = repository.GetProductById(productId).Any();

而不是使用 EF 加载整个实体以便稍后检查它是否为 Null。

它还允许您使用Select 投影数据,以将查询简化为只选择您关心的数据。 IE。如果您要填充产品列表并且只需要产品 ID、名称和价格:

var productList = repository.GetProducts()
    .Select(x => new ProductSummaryViewModel
    {
        ProductId = x.ProductId,
        Name = x.Name,
        Price = x.Price
    }).ToList();

添加对排序、分页等的支持。这同样适用于“ById”,您可以利用IQueryable 将实体及其亲属投影到您需要的值。

鉴于您有一个设计为只返回 2 种最便宜的产品的方法,IQueryable 并不是一个真正有用的返回类型。我会返回一个IEnumerable

public  IEnumerable<Product> GetTwoWithLowestPrice()
{

接下来的事情是利用 Linq 订购和获取两种最便宜的产品。对于您的代码:

var list = sorted as List<Product>;

retVal.Add(list[0]);
retVal.Add(list[1]);

至少需要:

var list = sorted.ToList();

retVal.Add(list[0]);
retVal.Add(list[1]);

但是,这是一个代价高昂的错误,因为您告诉 EF 退回所有产品,而您只需要前两个产品。 Linq 和 EF 可以通过 Take 来解决这个问题。

public  IEnumerable<Product> GetTwoWithLowestPrice()
{
    var products = db.Products
        .Include(p => p.Category)
        .OrderBy(p => p.Price)
        .Take(2)
        .ToList();

    return products;
}

就是这样。这将返回 0-2 个最便宜的产品,包括它们的类别,并且查询将只返回这 0-2 行。

总的来说,这是一种非常具体的场景方法,如果这是一个存储库,我可能会考虑将具体的场景逻辑留在控制器中,而不是在数据转换层中使用越来越多的具体方法。这需要将 DbContext 或工作单元提升到 Controller/Presenter。

因此在存储库中使用之前的IQueryable&lt;Product&gt; GetProducts(),想要最便宜的两种产品的控制器代码如下所示:

var products = _repository.GetProducts()
    .OrderBy(x => x.Price)
    .Select(x => new ProductSummaryViewModel
    {
        ProductId = x.ProductId,
        Name = x.Name,
        Price = x.Price,
        CategoryName = x.Category.Name
    }).Take(2)
    .ToList();

这样做的好处是投影 (Select) 将构建一个查询,该查询仅检索必要的字段,包括相关字段 (x.Category.Name),而不是加载 all 列产品及其关联的类别。 (注意,使用Select 时不需要.Include)我强烈建议使用仅包含视图所需信息的ViewModel,而不是返回整个实体,尤其是与视图相关的实体。它可以节省内存,加快查询速度,并且可以更快地通过网络传输。它还更少地向潜在的攻击者公开您的数据结构。

关于如何利用 EF 和 Linq 获取所需的数据集,这有点失败。

【讨论】:

  • 谢谢,这解释了很多。我知道我的方法肯定很糟糕。问题:我还将有其他方法,例如 GetAllWithPriceLowerThan(decimal price)GetAllByCategoryId(int categoryId) 。是否使用 IEnumerable&lt;Product&gt; 作为 return value 用于提到的方法?
  • 您可以使用其中任何一种,但值得考虑您的系统最终将拥有多少数据。如果它在 100 或可能低 1000 的产品中,那么 IEnumerable 是好的,但随着系统的增长,您可能会考虑为查询添加分页。在较低级别采用IQueryable 可以帮助解决问题,而不是试图通过页面#、页面大小、急切加载的详细信息、排序顺序等标准。
  • 因此 IEnumerable 更常用于遍历列表,而如果使用 dbSets/context 则使用 IQueryable 会很好,因为它在过滤之前不会真正获取不必要的数据。我也在构建 web api 单页应用程序,因此没有视图。话虽如此,当您说select (x =&gt; ProductSunmaryViewModel ... 时,我应该将其视为我需要在 Models 文件夹中创建的数据传输对象,对吧?我还需要返回带有其类别属性的产品。然后,我只需将 Category 对象作为 prooerty 添加到我的“vm”又名 DTO,然后在 select 部分中定义信息?
  • 是的,这就是它的要点。 :) 实体反映了存储的数据模型。 ViewModel 反映了视图需要的模型。投影是可以与 EF 的 IQueryable 一起使用的粘合剂,以使用实体映射来构建有效的查询来填充视图的模型。 Automapper 有 ProjectTo 链接到 IQueryable 以构建合适的查询并在配置后返回您的 ViewModel。
猜你喜欢
  • 2018-10-12
  • 2017-03-16
  • 2018-08-21
  • 2018-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-05
  • 2019-08-15
相关资源
最近更新 更多