簡體   English   中英

Linq查詢結合2個列表

[英]Linq Query To Combine 2 Lists

我有2個列表,我需要組合A和B的連接值,但也包括A和B中與連接不匹配的值。

class TypeA
{
    public string Key { get; set; }
    public int ValueA { get; set; }
}

class TypeB
{
    public string Key { get; set; }
    public int ValueB { get; set; }
}

class TypeAB
{
    public string Key { get; set; }
    public int ValueA { get; set; }
    public int ValueB { get; set; }
}

var listA = new List<TypeA>
{
    new TypeA { Key = "one", Value = 1 },
    new TypeA { Key = "two", Value = 2 },
};

var listB = new List<TypeB>
{
    new TypeB { Key = "two", Value = 2 },
    new TypeB { Key = "three", Value = 3 },
};

我希望這些列表組合起來等於:

var listAB = new List<TypeAB>
{
    new TypeAB { Key = "one", ValueA = 1, ValueB = null },
    new TypeAB { Key = "two", ValueA = 2, ValueB = 2 },
    new TypeAB { Key = "three", ValueA = null, ValueB = 3 },
};

什么是Linq聲明會這樣做? 我一直在玩,不能完全到達那里。 我可以通過A到B和Union上的左外連接來到達那里,在B到A的左外連接,但我得到重復的交點值。

更新

以下是我根據喬治的回答所做的事情:

var joined =
    ( from a in listA
      join b in listB
        on a.Key equals b.Key
        into listBJoin
      from b in listBJoin.DefaultIfEmpty( new TypeB() )
      select new TypeAB
      {
        Key = a.Key,
        ValueA = a.ValueA,
        ValueB = b.ValueB,
      } ).Union(
        from b in listB
        where !listA.Any( d => d.Key == b.Key )
        select new TypeAB
        {
            Key = b.Key,
            ValueB = b.ValueB,
        }
        ).ToList();

對於這種情況,在我們的項目中,我們使用名為Merge的擴展方法。

public static class Extensions
{
    public static IEnumerable<TResult> Merge<TLeft, TRight, TKey, TResult>(
        this IEnumerable<TLeft> leftList, 
        IEnumerable<TRight> rightList, 
        Func<TLeft, TKey> leftKeySelector, 
        Func<TRight, TKey> rightKeySelector, 
        Func<TKey, IEnumerable<TLeft>, IEnumerable<TRight>, TResult> combiner)
    {
        var leftLookup = leftList.ToLookup(leftKeySelector);
        var rightLookup = rightList.ToLookup(rightKeySelector);

        var keys = leftLookup.Select(g => g.Key).Concat(rightLookup.Select(g => g.Key)).Distinct();
        return keys.Select(key => combiner(key, leftLookup[key], rightLookup[key]));        
    }
}

您可以像這樣使用Merge

var listAB = listA.Merge(
    listB,
    a => a.Key,
    b => b.Key,
    (key, aItems, bItems) => new TypeAB 
    { 
        Key = key, 
        ValueA = aItems.Select(a => (int?)a.Value).SingleOrDefault(), 
        ValueB = bItems.Select(b => (int?)b.Value).SingleOrDefault()
    });

編輯:修正了。 這很草率但應該有效。

    listA  //Start with lisA
.Where(a=>!listB.Any(b=>a.Key == b.Key))  // Remove any that are duplicated in listB
.Select(a => new TypeAB() { Key=a.Key, ValueA=a.Value})  // Map each A to an AB
.Union(
  listB.Select(b => {
      var correspondingA = listA.FirstOrDefault(a => a.Key == b.Key);  //Is there an a that corresponds?
    return new TypeAB() { Key=b.Key, 
      ValueB=b.Value,  //Value B is easy
      ValueA= correspondingA!=null ? (int?)correspondingA.Value : null  //If there is an A than map its value
    };
  })
)

順便說一句,如果你將它用作某種域操作,TypeA和TypeB可能應該基於某種AorBIsAConceptThatHasMeaningInTheDomain基類。 只要您發現自己組合列表,這只是一般規則。 如果不存在這樣的概念,那么您可能不需要組合列表。

另一方面,如果您將此作為映射的一部分 - 例如將域對象映射到UI - 您可以通過使用匿名類型而不是TypeAB類來稍微簡化代碼。 (或者可能不是,這個取決於個人喜好)

編輯編輯這是使用哈希的一個稍微更有智慧的答案

        var listAB = listA.Cast<object>().Union(listB.Cast<object>()).ToLookup(x => x is TypeA ? (x as TypeA).Key : (x as TypeB).Key)
                .Select(kv => {
                    var a = kv.FirstOrDefault(x => x is TypeA) as TypeA;
                    var b = kv.FirstOrDefault(x => x is TypeB) as TypeB;
                    return new TypeAB() {
                        Key = kv.Key,
                        ValueA = a != null ? (int?)a.Value : null,
                        ValueB = b != null ? (int?)b.Value : null
                    };
                }).ToList();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM