如何对对象集合和每个对象内部的集合运行 LINQ 查询

本文关键字:集合 对象 运行 查询 内部 LINQ | 更新日期: 2023-09-27 18:30:31

我有一个对象的集合,每个对象也有一个集合。这样:

public class Product
{
    public int Id { get; set; }
    public List<Tuple<string, double>> Sales { get; set; }
}

我想运行 LINQ 查询来检查产品实体是否存在,如果存在,请检查它的 Sales 集合以查看特定字符串值(来自元组)是否也存在。如果是这样,我想返回相应的双精度(也来自元组)。

我知道我可以用几行代码做到这一点,如下所示:

saleAmount = String.Empty;                      
product = Model.Products.SingleOrDefault(p => p.Id == product.Id);
if(product != null)
{
    productSale = product.Sales.SingleOrDefault(i => i.Item1 == sale.Id);
    if(productSale != null)
    {
        saleAmount = productSale.Item2.ToString();
    }
}

是否可以在一行中执行此操作?

如何对对象集合和每个对象内部的集合运行 LINQ 查询

这里的关键是,在实际定义完整个查询之前,不要通过使用SingleOrDefault来实际实现查询。 改用Where,然后在最后使用SingleOrDefault

var query = (from product in Model.Products
                where product.Id == someProductId
                let sale = product.Sales.SingleOrDefault(i => i.Item1 == sale.Id)
                where sale != null
                select new
                {
                    product,
                    saleAmount = sale.Item2,
                })
            .SingleOrDefault();

是否可以在一行中完成。

我相信您可以通过将检查组合到第二个销售数组中来将代码提炼成更少的行,例如

var products = Model.Products.Where(p => p.Id == product.Id
                                              &&
                                         p.Sales.Any(i => i.Item1 == sale.Id) );
var saleAmount = (products != null && products.Any()) 
                                   ? products.First().Sales.First().Item2.ToString()
                                   : string.Empty;

使用默认值

此解决方案使用默认的创建Product的帮助,以便在找不到时使用。在扩展方法 DefaultIfEmpty 中使用它,该方法确定是否返回了空投影,在这种情况下,它将返回假实例。之后,我们可以安全地提取一个将要string.empty的值并将其分配给最终的字符串productSale

下面我使用硬编码1.5作为销售价格,以便于阅读示例。

// Our default will set saleAmount to string.Empty if nothing is found in Products.
var defProduct = new Product() 
                      { Id    = -1, 
                        Sales = new List<Tuple<string, double>>()
                                  { new Tuple<string,double>(string.Empty, 0.0) }};
var productSale =
Products.Where(p => p.Id == product.Id && p.Sales.Any (s => s.Item2 == 1.5 ) )
        .DefaultIfEmpty( defProduct )
        .First ()
        .Sales.First()
        .Item1;

productSale是字符串。如果未找到值或具有要使用的实际值,则为空。


LinqPad 中的整个测试项目,使用 1.5 模拟失败。使用 1.6 表示成功。

void Main()
{
    var targetSalePrice = 1.5;
    var targetProductId = 2;
    var Products = new List<Product>() { new Product()
                                           { Id = 2,
                                             Sales = new List<Tuple<string, double>>()
                                            { new Tuple<string,double>("actual", 1.6) } }
                                        };

// Our default will set saleAmount to string.Empty if nothing is found in Products.
var defProduct = new Product() { Id = -1, Sales = new List<Tuple<string, double>>()
                                  { new Tuple<string,double>("faux string.Empty", 0.0) }};
var productSale =
Products.Where(p => p.Id == targetProductId 
                   && p.Sales.Any (s => s.Item2 == targetSalePrice ) )
        .DefaultIfEmpty( defProduct )
        .First ()
        .Sales.First ()
        .Item1;
    productSale.Dump(); // outputs the string "faux string.Empty" from the faux default.
}
// Define other methods and classes here

public class Product
{
    public int Id { get; set; }
    public List<Tuple<string, double>> Sales { get; set; }
}