Problem polega na tym, że sprawdzasz rok, miasto i QsNo na OutPut
zmienna po złączeniu... ale jeśli OutPut ma wartość null (co miałoby miejsce, gdyby nie było wierszy w AllCosts), to te sprawdzenia zawsze będą fałszywe, więc para (kod, OutPut) zostanie odfiltrowana przez klauzulę where. EF wykrywa ten fakt i generuje zapytanie, które jest wydajniejsze po prostu przy użyciu sprzężenia wewnętrznego.
To, co naprawdę chcesz zrobić, to odfiltrować wiersze kandydatów z kosztów, zamiast filtrować według par (kod, koszt). Aby to zrobić, możesz przenieść filtr w górę, aby odnosił się bezpośrednio do tabeli Koszty:
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
};