EntityFramework / 从数据库中获取两个价格最低的对象

EntityFramework / Get two objects from database with lowest price

我正在处理网络 api 项目。我正在使用 依赖注入 ,所以我在我的 ProductRepository class 中创建了我所有的方法。具体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.)

您的代码示例似乎不必要地混搭了一些 techniques/practices。

首发:

public  IQueryable<Product> GetTwoWithLowestPrice()

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

public IQueryable<Product> GetProducts()

甚至:

public IQueryable<Product> GetProductById(int productId)

采用这种方法的原因可能是为了集中有关数据的基本规则。例如 return 默认情况下仅使用活动产品,或执行基本级别规则(例如强制用户只能接收适用于他们的产品)。 return IQueryable<Product> 甚至对于预期 return 单个产品的方法来说似乎很奇怪,但它允许调用者简化查询:

产品存在吗?

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 将实体及其相关项向下投影到您需要的值。

鉴于您的方法仅设计用于 return 2 种最便宜的产品,IQueryable 并不是真正有用的 return 类型。我只是 return 一个 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 return ALL 产品,而您只想要前两个。 Linq 和 EF 可以通过 Take.

来解决这个问题
public  IEnumerable<Product> GetTwoWithLowestPrice()
{
    var products = db.Products
        .Include(p => p.Category)
        .OrderBy(p => p.Price)
        .Take(2)
        .ToList();

    return products;
}

就是这样。这将 return 0-2 最便宜的产品,包括它们的类别,查询将仅 return 那些 0-2 行。

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

因此,在存储库中使用之前的 IQueryable<Product> 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) 而不是加载 所有 列来自产品及其相关类别。 (请注意,使用 Select 时不需要 .Include)我强烈建议使用仅包含视图所需信息的 ViewModel,而不是 returning 整个实体,尤其是与视图相关的实体.它节省内存,导致更快的查询,并且更快地通过网络传输。它还会减少向潜在攻击者公开您的数据结构的信息。

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