Description
openedon Aug 5, 2023
We can support projecting complex types just fine, but projecting them via an optional navigation creates a problem:
public virtual Task Project_complex_type_via_optional_navigation(bool async)
=> AssertQuery(
async,
ss => ss.Set<CustomerGroup>().Select(cg => cg.OptionalCustomer!.ShippingAddress));
For cases where OptionalCustomer is null, EF currently materializes a non-null ShippingAddress with all-null values; it should be materializing null. The issue is that all we have are the ShippingAddress columns coming back from the database, and no way to know whether the address itself is null or not:
SELECT [c0].[ShippingAddress_AddressLine1], [c0].[ShippingAddress_AddressLine2], [c0].[ShippingAddress_ZipCode], [c0].[ShippingAddress_Country_Code], [c0].[ShippingAddress_Country_FullName]
FROM [CustomerGroup] AS [c]
LEFT JOIN [Customer] AS [c0] ON [c].[OptionalCustomerId] = [c0].[Id]
With e.g. owned entities this doesn't occur since the key columns of the owner entity type are projected as well (they're also the key columns of the owned entity type), and their nullability determines whether the owned is there or not.
The solution here would be to do a similar thing and project a single key property from the containing entity type, and then check that in the materializer to determine whether ShippingAddress should be null or not.
However, as @maumar pointed out, this is probably incompatible with Distinct: if we add the containing key column before the distinct, that effectively prevents DISTINCT from doing its job (since it's unique). We don't think there's a way to solve this in SQL (except maybe in PG where you can do DISTINCT BY 😉), so we should detect this and block it.