Le problème est que vous vérifiez l'année, la ville et le QsNo sur le OutPut
variable après la jointure... mais si OutPut est null (ce qui arriverait s'il n'y a pas de lignes dans AllCosts), alors ces vérifications seront toujours fausses, donc la paire (code, OutPut) sera filtrée par la clause where. EF détecte ce fait et génère une requête qui est plus efficace en utilisant simplement une jointure interne.
Ce que vous voulez vraiment faire, c'est filtrer les lignes candidates de Costs, plutôt que de filtrer sur des paires (code, coût). Pour ce faire, vous pouvez déplacer votre filtre vers le haut, afin qu'il s'applique directement au tableau des coûts :
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
};