Skip to content

Wrong mapping of nested complex property with generic types and same property name, resulting in InvalidCastException #33449

Description

@lucahost

File a bug

I stumbled upon a bug in the RelationalSqlTranslatingExpressionVisitor.CreatePropertyAccessExpression
I have a .Where() query comparing 2 ComplexProperties of a given type:

Repro-Repo

I set up a repository to reproduce the error: https://github.com/lucahost/ef-complex-bug-repro
Just adjust the connection string in Program.cs and run the sample.

Setup

var orderPosition = // load orderPosition

dbContext.DbSet<Shipment>()
  .Where(shipmnet => shipment.Recipient == orderPosition.Recipient)
  .ToList();
...

public record Recipient
{
    public required Address Address { get; init; }
    public required Customer Customer { get; init; }
    public required Person BuyerPerson { get; init; }
}

public sealed record Address
{
    public required Id<Address> Id { get; init; }
}
public sealed record Customer
{
    public required Id<Customer> Id { get; init; }
}
public sealed record Person
{
    public required Id<Person> Id { get; init; }
}

builder.ComplexProperty(
    shipment => shipment.Recipient,
    complexPropertyBuilder =>
    {
        complexPropertyBuilder
            .ComplexProperty(
                recipient => recipient.Address,
                addressBuilder => addressBuilder.Property(address => address.Id)
                    .HasConversion<IdConverter<Address>>()
                    .IsRequired())
            .ComplexProperty(
                recipient => recipient.Customer,
                customerBuilder => customerBuilder.Property(customer => customer.Id)
                    .HasConversion<IdConverter<Customer>>()
                    .IsRequired())
            .ComplexProperty(
                recipient => recipient.BuyerPerson,
                buyerPersonBuilder => buyerPersonBuilder.Property(person => person.Id)
                    .HasConversion<IdConverter<Person>>()
                    .IsRequired());
    });

// Same Mapping for OrderPosition

Now in the RelationalSqlTranslatingExpressionVisitor in CreatePropertyAccessExpression the SQL statements parameterName for OrderPosition.Recipient.Address.Id of Type Id<Address> is generated as following: __entity_equality_orderPosition_Recipient_0_Id

Exception in RelationalTypeMapping.CreateParameter

Then in the conversion of the parameter to the provider, the wrong cast is being made.
The SQL-parameter @__entity_equality_orderPosition_Recipient_0_Id of type Id<Customer> would be converted to Id<Address>

image

Assumption

As you see, the Id property on the complex types are all called the same. I think this is the culprit which results in the parameter not being added correctly.

Workarounds

One workaround was to get rid of the nesting -> OrderPosition.Recipient.AddressId where Recipient is configured as ComplexProperty() and AddresId as .Property().

Another one I found out while stepping through the code was by using a distinct parameter-name in RelationalSqlTranslatingExpressionVisitor.CreatePropertyAccessExpression. Something along the lines

// get generic class as propertyName
var propName = $"{property.ClrType.GenericTypeArguments[0].Name}_{property.Name}";

var newParameterName =
    $"{RuntimeParameterPrefix}"
    + $"{sqlParameterExpression.Name[QueryCompilationContext.QueryParameterPrefix.Length..]}_{propName }";

Later is not a solution for everything, but was working in my case.
I think the parameter-name should be generated something like this: __entity_equality_orderPosition_Recipient_0_Address_Id including the nested properties name.

EF Core version: 8.0.3
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET 8.0
Operating system:
IDE: Rider 2024.1 RC

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions