【问题标题】:Avoiding duplicates in hierarchical parent-child relational collection避免分层父子关系集合中的重复
【发布时间】:2014-08-12 01:41:50
【问题描述】:

我希望为一个简单的集合场景编写linq 语句。我试图避免基于父子关系的集合中的重复项目。数据结构和示例代码如下

public class Catalog
{
    public int CatalogId { get; set; }
    public int ParentCatalogId { get; set; }
    public string CatalogName { get; set; }
}

public class Model
{
     public int CatalogId { get; set; }
     public string ItemName { get; set; }
        ...
}

List<Catalog> Catalogs:包含与所有目录的任何级别的父子关系的完整列表以及带有ParentCatalogid=null的根目录

List<Model> CollectionA :包含特定 catalogId 的所有子目录以及父目录的所有项目(直到它的根目录)。

我需要从 CollectionA 创建一个 CollectionB ,它将包含提供的 catalogId 的项目,包括所有父母的所有项目,这样如果项目存在于子目录中,我需要忽略父目录中的相同项目。这样,如果子项和父项中都有相同的项,则不会有任何重复项。

就代码而言,我正在尝试实现这样的目标

while (catalogId!= null)
{                           
    CollectionB.AddRange(
        CollectionA.Where(x => x.CatalogId == catalogId &&
                               !CollectionB.Select(y => y.ItemName).Contains(x.ItemName))); 
    // Starting from child to parent and ignoring items that are already in CollectionB

    catalogId = Catalogs.
        Where(x => x.Id == catalogId).
        Select(x => x.ParentCatalogId).
        FirstOrDefault();
 }

我知道上面语句中 linq 中的 Contains 子句将不起作用,但只是将该语句用于解释我要做什么。我可以使用foreach 循环来做到这一点,但我只想使用linq。我正在寻找正确的 linq 语句来执行此操作。下面给出了示例数据,如果我能得到一些帮助,将不胜感激

    Catalog

    ID ParenId    CatalogName
    1    null      CatalogA
    2      1       Catalogb
    3      1       CatalogC
    4      2       CatalogD
    5      4       CatalogE

    CollectionA

    CatalogId    ItemName
    5            ItemA
    5            ItemB
    4            ItemA
    4            ItemC
    2            ItemA
    2            ItemC
    1            ItemD

    Expected output
    CollectionB
    5    ItemA
    5    ItemB
    4    ItemC
    1    ItemD

【问题讨论】:

    标签: c# linq duplicates hierarchical-data relation


    【解决方案1】:

    LINQ 不是为遍历分层数据结构而设计的,因为它已经在以下方面进行了考虑:

    但是如果你可以得到从子目录到根目录的层次结构,那么问题可以通过joindistinct - LINQ's Distinct() on a particular property 来解决:

    var modelsForE = (from catalog in flattenedHierarchyOfCatalogE
                      join model in models
                          on catalog.CatalogId equals model.CatalogId
                      select model).
                      GroupBy(model => model.ItemName).
                      Select(modelGroup => modelGroup.First()).
                      Distinct();
    

    甚至更好 - 适应 Jon Skeet's answer 以区分。

    它解决了重复问题,但给我们留下了另一个问题:如何获得flattenedHierarchyOfCatalogE

    纯 LINQ 解决方案:

    这不是一件容易的事,但使用纯 LINQ 并非完全不可能。适应How to search Hierarchical Data with Linq 我们得到:

    public static class LinqExtensions
    {
        public static IEnumerable<T> Flatten<T>(this T source, Func<T, IEnumerable<T>> selector)
        {
            return selector(source).SelectMany(c => Flatten(c, selector))
                                   .Concat(new[] { source });
        }
    }
    
    //...    
    
    var catalogs = new Catalog[] 
    {
        new Catalog(1, 0, "CatalogA"),
        new Catalog(2, 1, "Catalogb"),
        new Catalog(3, 1, "CatalogC"),
        new Catalog(4, 2, "CatalogD"),
        new Catalog(5, 4, "CatalogE")
    };
    
    var models = new Model[]
    {
        new Model(5, "ItemA"),
        new Model(5, "ItemB"),
        new Model(4, "ItemA"),
        new Model(4, "ItemC"),
        new Model(2, "ItemA"),
        new Model(2, "ItemC"),
        new Model(1, "ItemD")
    };
    
    var catalogE = catalogs.SingleOrDefault(catalog => catalog.CatalogName == "CatalogE");
    
    var flattenedHierarchyOfCatalogE = catalogE.Flatten((source) =>
        catalogs.Where(catalog => 
            catalog.CatalogId == source.ParentCatalogId));
    

    然后将flattenedHierarchyOfCatalogE 从问题的开头输入查询

    警告:我已经为你的类添加了构造函数,所以之前的 sn-p 可能无法在你的项目中编译:

    public Catalog(Int32 catalogId, Int32 parentCatalogId, String catalogName)
     {
          this.CatalogId = catalogId;
          this.ParentCatalogId = parentCatalogId;
          this.CatalogName = catalogName;
     } //...
    

    需要考虑的事项

    以前的解决方案没有任何问题(好吧,我个人可能考虑过使用不太广泛使用 LINQ 的东西,例如 Recursive Hierarchy - Recursive Query using Linq),但是无论您喜欢哪种解决方案,您都可能遇到一个问题:它有效,但它没有'不使用任何优化的数据结构——它只是直接搜索和选择。如果您的目录增长并且查询将更频繁地执行,那么性能可能会成为问题。

    但是,即使性能不是问题,您的类的易用性也是如此。 ID、外键对关系数据库很好,但在 OO 系统中非常笨拙。您可能想为您的类考虑可能的object relational mapping(或创建它们的包装器(镜像),看起来像:

    public class Catalog
    {
        public Catalog Parent { get; set; }
    
        public IEnumerable<Catalog> Children { get; set; }
    
        public string CatalogName { get; set; }
    }
    
    public class Model
    {
         public Catalog Catalog { get; set; }
         public string ItemName { get; set; }   
    }
    

    这样的类更加独立,更易于使用和遍历它们的层次结构。我不知道您的系统是否是数据库驱动的,但您仍然可以查看一些 object-relational mapping 示例和技术。

    P.S.: LINQ 不是 .NET 武器库中的绝对工具。毫无疑问,它是适用于多种情况的非常有用的工具,但并非在所有可能的情况下都适用。如果工具不能帮助您解决问题,那么应该修改或暂时搁置它。

    【讨论】:

      【解决方案2】:

      您很可能正在寻找SelectMany() 扩展名。下面是一个简短的示例,说明如何使用它来选择所有子项进行比较(以避免重复):

      var col = new[] { 
          new { name = "joe", children = new [] { 
              new { name = "billy", age=1 },
              new { name = "sally", age=4 }
          }},
          new { name = "bob", children = new [] {
              new { name = "megan", age=10 },
              new { name = "molly", age=7  }
          }}
      };
      
      col.SelectMany(c => c.children).Dump("kids");
      

      有关此扩展的堆栈溢出的更多信息,当然您可以阅读the actual msdn documentation

      【讨论】:

      • 它究竟对处理分层数据结构有何帮助?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-17
      • 2010-09-23
      • 2014-04-29
      • 1970-01-01
      • 2021-12-02
      • 2014-09-28
      相关资源
      最近更新 更多