【问题标题】:Is there a way to use Any() in this LINQ query?有没有办法在这个 LINQ 查询中使用 Any() ?
【发布时间】:2021-12-29 21:05:48
【问题描述】:

我有以下 XML 文件:

<?xml version="1.0" encoding="utf-8"?>

<Catalog>
    <Device>
        <Identity>
            <Key>
                <VendorID Name="Generic Vendor">123</VendorID>
                <ProductType Name="Something">26</ProductType>
                <ProductCode>78</ProductCode>
                <Revs>
                    <MajorRev Number="17" DefaultMinorRev="128" />
                    <MajorRev Number="18" DefaultMinorRev="128" />
                    <MajorRev Number="19" DefaultMinorRev="128" />
                </Revs>
            </Key>
        </Identity>
    </Device>
    <Device>
        <Identity>
            <Key>
                <VendorID Name="Generic Vendor">123</VendorID>
                <ProductType Name="Something">26</ProductType>
                <ProductCode>78</ProductCode>
                <Revs>
                    <MajorRev Number="17" DefaultMinorRev="129" />
                    <MajorRev Number="18" DefaultMinorRev="129" />
                    <MajorRev Number="19" DefaultMinorRev="129" />
                </Revs>
            </Key>
        </Identity>
    </Device>
</Catalog>

我使用以下 C# 程序阅读它:

using System;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.IO;

namespace XMLSample
{
    class Program
    {
        static void Main(string[] args)
        {
            var xmlFile = new StreamReader("c:\\misc\\minimumxmlsample.xml");

            XElement catSvcsData = XElement.Load(xmlFile);

            // A device has only one vendor ID, product type, product code and revs element.
            var matchingDeviceKeys =
                from dev in catSvcsData.Elements("Device")
                where dev.Descendants("VendorID").First().Value == "123" && // device.Vendor.ToString(CultureInfo.InvariantCulture) &&
                dev.Descendants("ProductType").First().Value == "26" && // device.ProductType.ToString(CultureInfo.InvariantCulture) &&
                dev.Descendants("ProductCode").First().Value == "78" // device.ProductCode.ToString(CultureInfo.InvariantCulture)
                from rev in dev.Descendants("Revs")
                where rev.Element("MajorRev").Attribute("Number").Value == "17" && // device.MajorRevision.ToString(CultureInfo.InvariantCulture) &&
                rev.Element("MajorRev").Attribute("DefaultMinorRev").Value == "129" // device.MinorRevision.ToString(CultureInfo.InvariantCulture)
                select dev; // dev.Element("Schema").Element("Capabilities").Element("DuplexSupported");

            //var matchingDeviceKeys =
            //    catSvcsData.Elements("RADevice").Where(dev => dev.Descendants("VendorID").First().Value == "65532" &&
            //                                                    dev.Descendants("ProductType").First().Value == "26" &&
            //                                                    dev.Descendants("ProductCode").First().Value == "78")
            //                                    .Any(dev => dev.Descendants("MajorRev").Attribute("Number").Value == "17" &&
            //                                                dev.Descendants("DefaultMinorRev") == "129");


            Console.WriteLine("Hello World!");
        }
    }
}

程序需要找到设备元素,其中一个 MajorRev 元素具有给定的主要修订号和默认的次要修订号。未注释的查询有效,但两个“from”子句对我来说看起来很奇怪,很可能是因为我更习惯于使用 Where() 函数和类似的东西,而不是使用这种形式的 LINQ 查询。在我看来,应该可以使用 Any() 来查看是否有任何 MajorRev 元素与目标匹配。注释掉的查询不起作用,因为我需要使用 First() 来指定我从中获取属性的 MajorRev 元素。有没有更好的方法来编写这个查询?

【问题讨论】:

  • 首先将您的 Linq 查询从关键字语法转换为扩展方法语法,否则阅读起来很痛苦。

标签: c# linq


【解决方案1】:

将您的查询语法直接转换为 lambda 语法,将最终的 where 转换为 Any 产生:

var m2 = catSvcsData.Elements("Device")
            .Where(dev => dev.Descendants("VendorID").First().Value == "123" &&
                          dev.Descendants("ProductType").First().Value == "26" &&
                          dev.Descendants("ProductCode").First().Value == "78")
            .Where(dev => dev.Descendants("Revs")
                            .Any(rev => rev.Element("MajorRev").Attribute("Number").Value == "17" &&
                                        rev.Element("MajorRev").Attribute("DefaultMinorRev").Value == "129"));

请注意,这不是 C# 转换为 lambda 形式的方式:C# 使用 SelectManydev 与每个 rev 配对,然后在 rev 上过滤配对并返回 dev。如果多个revwhere 过滤器匹配,则这可能会多次返回单个dev。我的查询只返回一次dev

LINQPad 是一个非常好的工具,可以查看编译器如何将查询语法转换为 lambda 语法。在这种情况下,只需将 .AsQueryable 添加到您的第一个 from 源即可。

我想如果我正在写这篇文章,当你知道结构时,我会很想使用Element 而不是Descendants,并使用Selectlet 的 lambda 转换)来减少重复:

var m2 = catSvcsData.Elements("Device")
            .Select(dev => new { dev, key = dev.Element("Identity").Element("Key") })
            .Where(dk => dk.key.Element("VendorID").Value == "123" &&
                         dk.key.Element("ProductType").Value == "26" &&
                         dk.key.Element("ProductCode").Value == "78")
            .Select(dk => dk.dev)
            .Where(dev => dev.Descendants("Revs")
                             .Select(rev => rev.Element("MajorRev"))
                             .Any(mrev => mrev.Attribute("Number").Value == "17" &&
                                          mrev.Attribute("DefaultMinorRev").Value == "129"));

【讨论】:

  • 非常感谢您花时间这么详细地回答!我认为 Descendants 比 Elements 更可取,因为它会使代码不易受到更改的影响。我保证供应商 ID、产品类型和产品代码元素将始终存在且是唯一的,并且始终存在至少一个具有 Number 和 DefaultMinorRev 属性的 MajorRev 元素,但我不能保证它们会在哪里在结构中。
猜你喜欢
  • 2013-01-01
  • 2023-03-26
  • 1970-01-01
  • 1970-01-01
  • 2011-05-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多