Il problema è che stai controllando Anno, Città e QsNo su OutPut
variabile dopo il join... ma se OutPut è null (cosa che accadrebbe se non ci sono righe in AllCosts), questi controlli saranno sempre falsi, quindi la coppia (codice, OutPut) verrà filtrata dalla clausola where. EF rileva questo fatto e genera una query più efficiente semplicemente utilizzando un inner join.
Quello che vuoi veramente fare è filtrare le righe candidate da Costi, piuttosto che filtrare su coppie (codice, costo). Per fare ciò puoi spostare il filtro verso l'alto, in modo che si applichi direttamente alla tabella Costi:
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
};