linq to sql: указание JOIN вместо LEFT OUTER JOIN

Учитывая, что у меня есть три таблицы, Транспорт, Машины, Велосипеды. И у автомобилей, и у мотоциклов есть VehicleID FK, который ссылается на автомобили.

Я хочу посчитать все автомобили, которые являются такими автомобилями.

Vehicles.Select(x=>x.Car).Count();

Тем не менее, это даст мне ВСЕ строки с транспортными средствами и поместит ноль в строки, где тип транспортного средства - Велосипеды.
Я использую linqpad для этого и, глядя на sql statment, я понял, что Причина, по которой он это делает, заключается в том, что при соединении x.Car он выполняет LEFT OUTER JOIN между автомобилем и автомобилем, что означает, что он вернет все автомобили. Если я изменю запрос на использование JOIN, он будет работать должным образом.

Есть ли способ указать linq выполнить соединение с использованием этого синтаксиса? В конечном итоге я хочу сделать что-то вроде:

Vehicles.Select(x=>x.Car.CountryID).Distinct().Dump();

Но из-за этой ошибки:

InvalidOperationException: The null value cannot be assigned to a member with type System.Int32 which is a non-nullable value type.

В итоге я делаю вот что:

Vehicles.Where(x=>x.Car!=null).Select(x=>x.Car.CountryID).Distinct().Dump();

person Joe    schedule 04.04.2011    source источник


Ответы (1)


Что ж, предложение Where кажется разумным, или вы можете использовать фактическое соединение, предполагая, что у вас есть свойство CarID или что-то подобное:

Vehicles.Join(Cars, v => v.CarID, c => c.ID, (v, c) => c.CountryID).Distinct()
person Jon Skeet    schedule 04.04.2011
comment
Где нормально ... пока я не начну делать Vehicles.Select(x=>x.Car.Producer.AnotherTable.Etc.Field). Я просто подумал, было бы здорово, если бы была ссылка на отношения, которая не возвращает все нули внешней таблицы. Цель состоит в том, чтобы минимизировать строку запроса. (учитывая длину и многословность соединения, я бы остановился на where, если нет более элегантного способа выражения JOIN) - person Joe; 04.04.2011
comment
@Joe: Проблема в том, что ваше поле допускает значение NULL. Это не будет проблемой для полей, не допускающих значения NULL. - person Jon Skeet; 04.04.2011
comment
@ Джон, это интересно. Я не понимаю этого полностью. При наведении указателя мыши на возвращаемые типы выбор возвращает IQueryable ‹int›. обычно, если он допускает значение NULL, он вернет IQueryable ‹int?›. Однако вы имеете в виду, что, поскольку транспортное средство является автомобилем или велосипедом, не всегда существует взаимосвязь между транспортным средством и таблицей автомобилей? - person Joe; 04.04.2011
comment
@Joe: Я имею в виду, что свойство Car допускает значение NULL ... потому что не всегда Car связано с транспортным средством. Итак, в вашей таблице, предположительно, CarID - это поле, допускающее значение NULL. - person Jon Skeet; 04.04.2011
comment
@Jon, на самом деле он не появляется в linqpad как тип, допускающий значение NULL. IQueryable ‹Car›. гм ... на самом деле я забыл одну важную деталь при создании этого упрощенного примера. - person Joe; 04.04.2011
comment
@Joe: Предоставление полного примера со всеми деталями задействованных типов и полей, допускающих значение NULL, действительно поможет. - person Jon Skeet; 04.04.2011
comment
@Jon, извините, теперь должно быть более полно. Я обновил первое предложение вопроса. Ни один из запрошенных ключей не допускает значения NULL. - person Joe; 04.04.2011
comment
@Joe: Хорошо, поэтому ключевые отношения находятся в противоположном направлении, чем я ожидал, но, по сути, для любой записи о Транспортном средстве нет необходимости, чтобы с ней был связан Автомобиль. - person Jon Skeet; 04.04.2011
comment
@Jon, сопоставление между автомобилем и транспортным средством и мотоциклом-транспортным средством оба равно 1 к 1, но ни одно из них не является полным сопоставлением. Не уверен, как это называется, но автомобили + велосипеды к транспортным средствам представляют собой полное сопоставление 1-1. Однако ни одно из полей не может иметь значение NULL, поскольку они являются FK, поэтому они не считаются допускающими значение NULL. - person Joe; 04.04.2011
comment
@Joe: Не уверен, что вы имеете в виду, говоря, что ни один из них не является полным отображением. Можете дать дополнительную информацию? Если бы это было действительно 1: 1, я бы предложил для начала использовать Cars вместо Vehicles.Car. Каждый Car связан с Vehicle? - person Jon Skeet; 04.04.2011
comment
@Jon, транспортное средство - это либо автомобиль, либо велосипед. Итак, VehicleID 1 - это автомобиль, а VehicleID 2 - это велосипед. Транспортное средство всегда будет тем или иным. - person Joe; 04.04.2011
comment
@Joe: Да, но всегда ли будет связана запись в таблице Cars с Vehicle? Если да, то почему вы вообще хотите выполнять соединение? Почему бы просто не использовать для начала Cars таблицу? Используйте Cars.Count() вместо Vehicles.Select(v => v.Car).Count(). Вы можете попробовать использовать OfType<Car>() вместо предложения Where, но я не уверен, сработает ли это. - person Jon Skeet; 04.04.2011
comment
@Jon, потому что «Транспортные средства» - это результат другого запроса, в котором я выбираю подмножество транспортных средств на основе других требований. Так, например, ParkingLot.Where(x=>x.Day == DateTime.Today).Select(x=>x.Vehicle.Car.CountryID).Distinct() - person Joe; 04.04.2011
comment
@Joe: Хорошо, в этом случае я думаю, что вы в основном действительно должны использовать предложение Where или явное соединение. - person Jon Skeet; 04.04.2011
comment
@Jon, это возвращает меня к моему вопросу. По сути, я не понимаю, почему выполнение x.Car дает мне ВНУТРЕННЕЕ СОЕДИНЕНИЕ, а не только СОЕДИНЕНИЕ. Было бы намного проще просто присоединиться. и в этом тоже было бы больше смысла ... во всяком случае для меня. - person Joe; 04.04.2011
comment
@Joe: Вы имеете в виду LEFT JOIN, а не JOIN? По логике, использование x.Car представляет собой ЛЕВОЕ СОЕДИНЕНИЕ, а не ВНУТРЕННЕЕ СОЕДИНЕНИЕ, потому что оно может иметь значение null, если транспортное средство не является автомобилем. У вас есть свойство, которое разумно может иметь значение NULL - LINQ не собирается автоматически удалять эту возможность. (Иногда вам могут понадобиться нулевые значения.) - person Jon Skeet; 04.04.2011
comment
@Jon, (да, я хотел сказать LEFT JOIN) ах .. я понимаю, поэтому, если x.Car вернул INNER JOIN, чего я ожидал, тогда могут быть исключения нулевого указателя, возникающие из-за выполнения x.Car.SomeOtherRelationship.Field и поэтому имеет смысл дать результат LEFT JOIN ... гул ... я думаю, так оно и работает. - person Joe; 04.04.2011
comment
@Joe: Нет, это не даст NullReferenceException, но логически должно, потому что есть строки, которые логически имеют свойство null Car. - person Jon Skeet; 04.04.2011