【问题标题】:Distinct Dictionary Using LINQ使用 LINQ 的不同字典
【发布时间】:2013-03-14 06:28:22
【问题描述】:

我正在尝试使用 LINQ 获取不同值的字典。 我试过用这个:

var roleRefList = 
    xDocument.Root.Descendants()
             .Where(x => x.Name.LocalName.Equals("roleRef") && 
                         !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && 
                         !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
             .Select(l => new {
                  roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                  href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
              })
             .Distinct()
             .ToDictionary(a => a.roleUri);

这里的问题是,当roleUri 中有重复条目时,就会发生错误。 我正在解析 XML 文档并制作 xElement 属性字典 roleUriroleref(如果它们存在于 xElement 中)。

另一种解决方法是使用 for 循环:

Dictionary<string, string> roleRefList = new Dictionary<string, string>();
            foreach (XElement element in xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef")))
            {
                string roelUri = Convert.ToString(element.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")));
                string href = Convert.ToString(element.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")));
                if (!string.IsNullOrEmpty(roelUri) && !string.IsNullOrEmpty(href) && !roleRefList.ContainsKey(roelUri))
                {
                    roleRefList.Add(roelUri, href);
                }
            }

但我想使用 LINQ 来实现。

【问题讨论】:

  • 如果你有工作代码,为什么要使用 Linq 来实现这个?
  • 使用节点本地名称的原因是什么?您的 xml 声明了不同的命名空间?你能展示你正在解析的xml的例子吗?
  • 所以根据您的循环:如果roleUri 存在不止一次,您只需要字典中的第一个实例(及其roleRef)吗?我做对了吗?
  • @Marteen 因为 linq 更快,所以需要这样做。
  • @lazyberezovsky 如果我尝试使用命名空间但它给了我错误。所以我使用 name.Localname

标签: c# xml linq linq-to-xml


【解决方案1】:

您可以编写自己的Distinct 方法,将Func&lt;T,TKey&gt; 作为参数。你可以在这里找到例子:Distinct list of objects based on an arbitrary key in LINQ

用那个方法你应该可以写:

var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
                           .Select(l => new
                           {
                               roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                               href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
                           }).Distinct(l => l.roleUri).ToDictionary(a => a.roleUri);

更新

或者你可以使用分组:

var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
                           .Select(l => new
                           {
                               roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                               href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
                           })
                           .GroupBy(l => l.roleUri)
                           .ToDictionary(g => g.Key, g => g.FirstOrDefault());

【讨论】:

  • +1。是否可以使用 g.First 而不是 g.FirstOrDefault?我希望该小组始终至少有一名成员。
  • 是的,你也可以使用First
【解决方案2】:

在 XML 中不允许重复属性。如果roleRef 元素中有两个roleURI 属性,那么在XDocument 加载期间会出现异常:

'roleURI' 是重复的属性名称。第 42 行,第 42 位。

所以,实际上你的代码应该是这样的:

var xdoc = XDocument.Load("foo.xml");
XNamespace ns = "http://www.adventure-works.com"; // put your namespace here

Dictionary<string, string> roleRefList = 
   xdoc.Root.Descendants(ns + "roleRef")
       .Select(r => new {
             Uri = (string)r.Attribute("roleURI"),
             Href = (string)r.Attribute("href")
       })
       .Where(r => !String.IsNullOrEmpty(r.Uri) && !String.IsNullOrEmpty(r.Href))
       .ToDictionary(r => r.Uri, r => r.Href);

结果将与您的 for 循环相同。示例 xml:

<root xmlns="http://www.adventure-works.com">
  <roleRef/>
  <roleRef roleURI=""/>
  <roleRef href=""/>
  <roleRef roleURI="" href=""/>
  <roleRef roleURI="a" />
  <roleRef roleURI="" href="b"/>
  <roleRef roleURI="c" href="d"/>
</root>

【讨论】:

  • 当我给出命名空间值时它给了我错误但是它适用于 name.localname
  • @lok​​endrajayaswal 如果您没有命名空间,则保持原样(或从代码中删除 ns)。如果您定义了命名空间(例如&lt;root xmlns="http://www.adventure-works.com"&gt;),则创建XNamespace ns = "http://www.adventure-works.com";。在这里查看 linq 中的 xml 命名空间msdn.microsoft.com/en-us/library/…
【解决方案3】:

实现这一目标的几种方法:

1) 您可以使用自定义相等比较器调用 distinct 方法。为了实现这一点,您需要先编写一个包含 roleUri 和 href 字段的类。比如:

    public class AttributePair 
    {
      public string RoleUri {get; set;}
      public string Href {get; set;}
    }

下一步是为您的班级编写相等比较器:

    public class AttributePairComparer : IEqualityComparer<AttributePair>
    {
        public bool Equals(AttributePair x, AttributePair y)
        {
            return x.RoleUri.Equals(y.RoleUri);
        }

        public int GetHashCode(AttributePair obj)
        {
            return obj.RoleUri.GetHashCode();
        }
    }

然后,我们只需将相等比较器的实例传递给 distinct 方法:

 var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
                       .Select(l => new AttributePair
                       {
                           RoleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                           Href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
                       }).Distinct(new AttributePairComparer()).ToDictionary(a => a.RoleUri, a => a.Href);

我同意这个解决方案相当复杂,但可以说这是实现所需结果的经典方式。

2) 另一种解决方案是来自MoreLinqDistinctBy 方法。您可以将 lambda 表达式作为参数传递的地方:

 var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
                       .Select(l => new
                       {
                           roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
                           href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
                       }).DistinctBy(p => p.roleUri).ToDictionary(a => a.roleUri, a => a.href);

3) C# 的首席架构师 Anders Hejlsberg 提出了使用 GroupBy 的解决方案。你可以阅读它here.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-15
    • 1970-01-01
    相关资源
    最近更新 更多