问题
I have 2 lists and I need to combine the joining values from A and B, but also include the values from A and B that don't match the join.
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 },
};
I want these lists combined to equal this:
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 },
};
What is a Linq statement that will do this? I've been playing around and can't quite get there. I can get almost there by doing a left outer join on A to B and Union that to a left outer join on B to A, but I get duplicate Intersection values.
Update
Here is what I did based on George's answer:
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();
回答1:
Edit: Fixed up. This is sloppy but it should work.
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
};
})
)
As an aside, if you're using this as some sort of domain operation, TypeA and TypeB should probably be based on some sort AorBIsAConceptThatHasMeaningInTheDomain base class. That's just a general rule whenever you find yourself combining lists. If no such concept exists, then you probably don't need to be combining the lists.
On the other hand, if you're doing this as part of a mapping - such as mapping domain objects to UI - you might be able to simplify your code somewhat by using anonymous types instead of a TypeAB class. (Or maybe not, this one is up to personal preference)
Edit Edit Here's a slightly more intellectually interesting answer using hashes
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();
回答2:
For exactly this scenario, in our project we use an extension method called 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]));
}
}
You can use Merge like this
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()
});
来源:https://stackoverflow.com/questions/1930288/linq-query-to-combine-2-lists