El problema es que está comprobando el año, la ciudad y el número de Qs en OutPut
variable después de la combinación... pero si la Salida es nula (lo que sucedería si no hay filas en Todos los Costos), estas comprobaciones siempre serán falsas, por lo que el par (código, Salida) será filtrado por la cláusula where. EF detecta este hecho y genera una consulta que es más eficiente simplemente usando una unión interna.
Lo que realmente desea hacer es filtrar filas candidatas de Costos, en lugar de filtrar por pares (código, costo). Para hacer esto, puede mover su filtro hacia arriba, para que se aplique directamente a la tabla Costos:
var Result = from code in ent.ProductCodes
join cost
in ent.Costs.Where(c => c.Year == Year && c.City == City && c.QsNo == Qsno)
on new { code.Year, code.Code } equals new { cost.Year, cost.Code }
into AllCosts
from OutPut in AllCosts.DefaultIfEmpty()
where code.PageNo == PageNo
select new
{
ProductCode = code.Code
Col6 = OutPut.Price
};