Closed Dresel closed 3 years ago
Running your simple errors at model building time with "The entity type 'EntityA' requires a primary key to be defined.". I'm using EF Core 5.0, which version are using?
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="5.0.2" />
</ItemGroup>
</Project>
What if you uncomment the HasKey statements?
Now I get "The property 'EntityA.Id' is of type 'EntityAId' which is not supported by the current database provider.".
Please, help me out by submitting a runnable code sample (or point to a github repo) which actually works on your end...
Sorry, I didn't mean to steal your time, but the code above works for me as described. I sounds like the TypedIdValueConverterSelector
is missing? Anyway I have copied everything and pasted it into a repository. I have also asked a colleague to double check.
The following
git clone https://github.com/SpatialFocus/efcorepg-issue-1716 temp
dotnet run --project .\temp\EFCorePGIssue1716\EFCorePGIssue1716.csproj
leads to the Couldn't find array or element type mapping in ArrayAnyAllExpression
.
Thanks, I can see the exception now - will take a look.
This is quite winding/complex, but here are my findings :smile:
First, there's a problem with the value converter; it adds supports for value-converting Entity1AId and Entity1BId, but not their corresponding array and List types (Entity1AId[]
, List<Entity1AId>
etc.). If you look at NpgsqlValueConverterSelector, you can see the logic adding proper support for these (it's not trivial).
The logic is missing because TypedIdValueConverterSelector extends NpgsqlValueConverterSelector, so the former adds its conversions on top of the latter's... The order should be reversed, with NpgsqlValueConverterSelector adding array/list conversions for the non-array conversions added to TypedIdValueConverterSelector. Unfortunately, because this is an inheritance hierarchy, that doesn't work; we could refactor NpgsqlValueConverterSelector to wrap another selector passed through the constructor (composition instead of inheritance), which would allow this kind of thing. But if you copy-paste the code from NpgsqlValueConverterSelector you should be able to get it to work.
There's still the question of why things work with the explicit HasConversion configuration (without TypedIdValueConverterSelector). Well, they actually don't :) In this mode, the SQL generated is:
SELECT e."Id", e."EntityBId"
FROM "EntitiesA" AS e
LEFT JOIN "EntitiesB" AS e0 ON e."EntityBId" = e0."Id"
WHERE e0."Id" IN (8, 9)
This is the default SQL generated by EF Core, and is not what Npgsql usually generates; try to just do Contains with a list of ints or similar, and you should see something like WHERE e0."Id" = ANY (@p)
. This is a superior translation, since the entire array/list is passed as a parameter to PG, whereas the default EF one expands the array into constants in the SQL, creating various types of perf issues. In effect, Npgsql fails to translate this to its own (efficient) construct, and EF falls back to its default (less efficient) translation.
Finally, Npgsql fails to translate this because of a shortcoming: it's because the array's CLR type isn't known to Npgsql, since the value converter in HasConversion only lives on the property. Value convertors returns by the ValueConverterSelector, on the other hand, add global support for the CLR type everywhere, so Npgsql does attempt to translate, but fails because the selector doesn't have the proper array mappings as above. The problematic line is https://github.com/npgsql/efcore.pg/blob/main/src/EFCore.PG/Storage/ValueConversion/NpgsqlValueConverterSelector.cs.
I'll keep this open for looking into a fix for this.
Thanks for the insight, this makes sense. Wish I had more time to deep dive into frameworks like this 😄
I will try the modified / copied NpgsqlValueConverterSelector workaround later, but I'm sure it will work out 👍
Note (mainly to myself).
For array properties with value converters, in some scenarios we require to have the element converter. For example, when doing .Where(b => b.SomeArrayPropertyWithValueConverter.Contains(someParam)
, the parameter has no type mapping, so we need to infer it from the array - including an element value converter to do the conversion. Up to now, such array properties had a mapping with the proper converter, but pointed to an ElementMapping without a converter at all; so this could not be translated.
In order to make all this work, element mappings of array mappings with a converter themselves must have the proper converter. This means that we can't allow users to set up array conversions directly - as lambdas operating on arrays - since it's impossible derive an element converter from that. So we force users to provide an element converter when setting up the array property (breaking change), and build the array conversion over that ourselves; this new API is called HasPostgresArrayConversion.
Just read and tried to understand your explanation. Did I understand it correctly that with this fix I would still have to copy / paste your selector code (which - just to point out - is not an problem for me) to make sure array converters are created for my custom type converters?
By the way the modified version of the NpgsqlValueConverterSelector
works flawlessly - I just hardcoded the inner selector because I wasn't able to find a good way to register decorations within the DbContext configuration (ReplaceService is limited and I don't know how to deal with InternalServiceProvider / ApplicationServiceProvider) 😬
Just read and tried to understand your explanation. Did I understand it correctly that with this fix I would still have to copy / paste your selector code (which - just to point out - is not an problem for me) to make sure array converters are created for my custom type converters?
Yeah, #1719 only fixes the last problem detailed above, and doesn't refactor NpgsqlValueConverterSelector (which is a less severe thing and can be worked around as you saw). If you feel this is important, please open another issue to track it - hopefully at some point I can take a look.
As discussed here, some query combinations including
Contains
do not work ifValueConverters
are used (throwingCouldn't find array or element type mapping in ArrayAnyAllExpression
). If configured viaHasConversion
, everything works as expected.