Эквивалентность синтаксиса запроса и метода (лямбда) предложения Join with Where
Мой упрощенный LINQ Join
plus Where
из двух таблиц выглядит следующим образом:
var join = context.Foo
.Join(context.Bar,
foo => new { foo.Year, foo.Month },
bar => new { bar.Year, bar.Month },
(foo, bar) => new { foo.Name, bar.Owner, foo.Year })
.Where(anon => anon.Year == 2015).ToList();
В качестве альтернативы я мог бы использовать следующий синтаксис, который, я надеюсь, будет эквивалентным:
var joinQuery = from foo in context.Foo
join bar in context.Bar
on new { foo.Year, foo.Month } equals new { bar.Year, bar.Month }
where foo.Year == 2015
select new { foo.Name, bar.Owner };
var join = joinQuery.ToList();
Одна разница, которая возникает у меня, и что я задаюсь вопросом, это порядок команд. В соединении лямбда-синтаксиса я добавляю свойство foo.Year
к моему типу анонимного возврата, чтобы я мог фильтровать его, а в другом запросе я все еще могу использовать foo
(и bar
, если захочу) в Where
. Мне не нужно добавлять поле foo.Year
к моему типу возврата здесь, если я не хочу или нуждаюсь.
К сожалению, у меня нет ReSharper или чего-то подобного, что могло бы преобразовать нижний оператор в лямбда-код, чтобы я мог сравнивать.
То, что я мог на самом деле (и сделать верхний оператор более похожим по структуре на нижний), добавляет следующую строку между Where(..)
и ToList()
в первом:
.Select(anon => new { /* the properties I want */ })
Но разве это не просто добавляет "еще одно" создание анонимного типа по сравнению со вторым утверждением, или я ошибаюсь здесь?
Вкратце: какой эквивалентный синтаксис Join
для второго оператора? Или первый, плюс добавленный Select
действительно эквивалентный, т.е. Внутри joinQuery
создает тот же код?
Ответы
Ответ 1
В общем случае вы не всегда можете преобразовать между синтаксисом понимания запроса и синтаксисом лямбда точно так же, как это делает компилятор. Это связано с использованием прозрачных идентификаторов . Но вы можете обойти это и создать семантически эквивалентные лямбда-выражения. И это то, что делает ReSharper.
В любом случае, в вашем случае вы можете просто добавить:
.Select(anon => new { /* the properties I want */ })
Это создаст анонимный тип для каждой строки, но не будет "еще одним", поэтому не беспокойтесь об этом: выражение преобразуется в SQL, поэтому new { foo.Year, foo.Month }
операторы в join
на самом деле не создают экземпляры этих объектов, они просто преобразуются в SQL. Только последний выбор будет использоваться для списка SQL SELECT
и для гидратации объекта после получения строк.
Ответ 2
Но разве это не просто добавляет "еще одно" создание анонимного типа по сравнению со вторым утверждением, или я ошибаюсь здесь?
Как показано в ответе dotctor: это то, что делает компилятор, когда вы используете синтаксис понимания в этом случае. Включив год в свой анонимный тип, вы немного уменьшите накладные расходы.
Однако:
- Анонимные типы очень легкие: использование дженериков, и что генерические экземпляры, созданные через ссылочные типы, совместно используют реализацию, означает, что кода мало (посмотрите на сборку в декомпиляторе).
- Пока создано множество экземпляров, в большинстве случаев они будут очищены в генерации 0, поскольку они будут выпущены почти сразу.
- У компилятора С# и оптимизаторов JIT есть много возможностей для работы здесь. Вполне возможны быстрые клавиши (но вам нужно будет прочитать сборку x86/x64 из работающего процесса, чтобы увидеть).
Помните первые два правила оптимизации: если вы не можете показать данные профилировщика из реалистичных тестовых данных, у вас есть проблема с производительностью который легко упрощается.
Ответ 3
Другой альтернативой было бы переместить метод where
до метода join
, а затем оставить год вне анонимного класса:
var join = context.Foo
.Where(foo => foo.Year == 2015)
.Join(context.Bar,
foo => new { foo.Year, foo.Month },
bar => new { bar.Year, bar.Month },
(foo, bar) => new { foo.Name, bar.Owner })
.ToList();
Но в целом другие ответы правы в том, что нет большой разницы, и компилятор может обрабатывать детали для вас.
Ответ 4
для Короче: часть вашего вопроса: ответ да.
здесь результат resharpers
var joinQuery = context.Foo.Join(context.Bar, foo => new
{
foo.Year,
foo.Month
}, bar => new
{
bar.Year,
bar.Month
}, (foo, bar) => new
{
foo,
bar
}).Where(@t => @t.foo.Year == 2015).Select(@t => new
{
@t.foo.Name,
@t.bar.Owner
});