К сожалению, в LINQ нет ни внешнего соединения, ни добавления произвольных условий соединения. Внутреннее соединение можно обойти, используя DefaultIfEmpty, но часть условия соединения Bn.type = n должна быть перемещена в условие where.
Следующий код производит именно тот код SQL, который вы указали, за исключением указанных выше типов:
from A in products
join B1 in categories on A.key equals B1.key into tmp_color
join B2 in categories on A.key equals B2.key into tmp_size
join B3 in categories on A.key equals B3.key into tmp_shape
from B1 in tmp_color.DefaultIfEmpty()
from B2 in tmp_size.DefaultIfEmpty()
from B3 in tmp_shape.DefaultIfEmpty()
where B1.type == 1 && B2.type == 2 && B3.type == 3
select new { product = A, color = B1.category, size = B2.category, shape = B3.category };
Результаты в
exec sp_executesql N'SELECT [t0].[key], [t1].[category] AS [color], [t2].[category] AS [size], [t3].[category] AS [shape]
FROM [Product] AS [t0]
LEFT OUTER JOIN [Category] AS [t1] ON [t0].[key] = [t1].[key]
LEFT OUTER JOIN [Category] AS [t2] ON [t0].[key] = [t2].[key]
LEFT OUTER JOIN [Category] AS [t3] ON [t0].[key] = [t3].[key]
WHERE ([t1].[type] = @p0) AND ([t2].[type] = @p1) AND ([t3].[type] = @p2)',N'@p0 int,@p1 int,@p2 int',@p0=1,@p1=2,@p2=3
(Обновление: это LINQ to SQL, только если предположить, что EF будет похожим.)
Ответ Альбина более читабелен, но, вероятно, дает менее оптимальный SQL. Для точного соответствия с вашим SQL вам нужно заменить FirstOrDefault на DefaultIfEmpty (может не иметь значения, в зависимости от ваших данных). (Извините, пока не могу комментировать; -))