Я нашел то, что мне нравится, это объединить OuterCollection.SelectMany()
с InnerCollection.DefaultIfEmpty()
. Вы можете запустить следующее в LINQPad , используя режим «C # Statements».
var teams = new[]
{
new { Id = 1, Name = "Tigers" },
new { Id = 2, Name = "Sharks" },
new { Id = 3, Name = "Rangers" },
};
var players = new[]
{
new { Name = "Abe", TeamId = 2},
new { Name = "Beth", TeamId = 4},
new { Name = "Chaz", TeamId = 1},
new { Name = "Dee", TeamId = 2},
};
// SelectMany generally aggregates a collection based upon a selector: from the outer item to
// a collection of the inner item. Adding .DefaultIfEmpty ensures that every outer item
// will map to something, even null. This circumstance makes the query a left outer join.
// Here we use a form of SelectMany with a second selector parameter that performs an
// an additional transformation from the (outer,inner) pair to an arbitrary value (an
// an anonymous type in this case.)
var teamAndPlayer = teams.SelectMany(
team =>
players
.Where(player => player.TeamId == team.Id)
.DefaultIfEmpty(),
(team, player) => new
{
Team = team.Name,
Player = player != null ? player.Name : null
});
teamAndPlayer.Dump();
// teamAndPlayer is:
// {
// {"Tigers", "Chaz"},
// {"Sharks", "Abe"},
// {"Sharks", "Dee"},
// {"Rangers", null}
// }
Экспериментируя с этим, я обнаружил, что иногда вы можете опустить нулевую проверку player
в экземплярах анонимного типа. Я думаю, что это имеет место при использовании LINQ-to-SQL в базе данных (вместо этих массивов здесь, которые, я думаю, делает это LINQ-to-objects или что-то в этом роде). Я думаю, что пропуск пустой проверки работает в LINQ -to-SQL, потому что запрос переводится в SQL LEFT OUTER JOIN
, который сразу переходит к соединению null с внешним элементом. (Обратите внимание, что значение свойства анонимного объекта должно быть равно нулю; поэтому, если вы хотите безопасно включить int
, скажем, вам понадобится что-то вроде: new { TeamId = (int?)player.TeamId }
.