Linq查询结合2个列表

时间:2009-12-18 19:41:42

标签: c# linq

我有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();

2 个答案:

答案 0 :(得分:3)

对于这种情况,在我们的项目中,我们使用名为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]));        
    }
}

您可以像这样使用合并

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()
    });

答案 1 :(得分:1)

编辑:已修复。这很草率但它应该有效。

    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();