Based on the comment that has been left on pwilcox's answer it seems like the OP is look for a solution where the unmatched rows are also included. That means instead of using inner join we are looking for a left outer join.
In the world of Linq this could be achieved via a combination of GroupJoin
, SelectMany
and Select
operators.
In order to be able to join on two different columns we have to introduce an intermediate class to be able to tell the types of the GroupJoin
. So, I have created the following class:
internal class IntermediateKey
{
public int Id { get; set; }
public string Value { get; set; }
}
We also have to define a comparer for this class to be able to find matching data:
internal class IntermediateKeyComparer : IEqualityComparer<IntermediateKey>
{
public bool Equals(IntermediateKey x, IntermediateKey y)
{
return x.Id == y.Id && x.Value == y.Value;
}
public int GetHashCode(IntermediateKey obj)
{
return obj.Id.GetHashCode() + obj.Value.GetHashCode();
}
}
Please bear in mind that this implementation is quite simplified. The correct way to implement it is shown in this thread.
Now can define our query as this:
var comparer = new IntermediateKeyComparer();
var result = userValues
.GroupJoin(
lookupMetas,
uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
lm => new IntermediateKey { Id = lm.FieldId, Value = lm.Id.ToString() },
(uv, lm) => new { Value = uv, Lookups = lm},
comparer)
.SelectMany(
pair => pair.Lookups.DefaultIfEmpty(),
(paired, meta) => new { Value = paired.Value, Lookup = meta})
.Select(res =>
{
res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
return res.Value;
});
- We defined that
userValues
should be left outer joined on lookupMetas
- if
uv
's FieldId
is matches to lm
's FieldId
- and if
uv
's FieldValue
is matches to lm
's Id
's string representation
- With the
SelectMany
we choose either the matching LookupMeta
entity or null
- With the
Select
we update the UserValue
's FieldValue
property only if there is a related LookupMeta
otherwise we use its original value.
Now let's see how this works with some sample data:
static void Main(string[] args)
{
var userValues = new List<UserValue>
{
new UserValue { FieldId = 1, FieldValue = "2"},
new UserValue { FieldId = 2, FieldValue = "3"},
new UserValue { FieldId = 4, FieldValue = "5"}
};
var lookupMetas = new List<LookupMeta>
{
new LookupMeta { FieldId = 1, Id = 2, FieldValueId = 20 },
new LookupMeta { FieldId = 2, Id = 3, FieldValueId = 30 },
new LookupMeta { FieldId = 3, Id = 4, FieldValueId = 40 },
};
var comparer = new IntermediateKeyComparer();
var result = userValues
.GroupJoin(
lookupMetas,
uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
lm => new IntermediateKey { Id = lm.FieldId, Value = lm.Id.ToString() },
(uv, lm) => new { Value = uv, Lookups = lm},
comparer)
.SelectMany(
pair => pair.Lookups.DefaultIfEmpty(),
(x, meta) => new { Value = x.Value, Lookup = meta})
.Select(res =>
{
res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
return res.Value;
});
foreach (var maybeUpdatedUserValue in result)
{
Console.WriteLine($"{maybeUpdatedUserValue.FieldId}: {maybeUpdatedUserValue.FieldValue}");
}
}
The output will be:
1: 20
2: 30
4: 5
So, as you can see there is no matching LookupMeta
for the last UserValue
that's why its FieldValue
remained intact.