Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Invalid Cast Operation #35380

Closed
wbaldanw opened this issue Dec 23, 2024 · 3 comments
Closed

Invalid Cast Operation #35380

wbaldanw opened this issue Dec 23, 2024 · 3 comments

Comments

@wbaldanw
Copy link

wbaldanw commented Dec 23, 2024

Hi, I found an issue when I try to run this code:
var place = await context.Places.Include(x => x.Organization).FirstOrDefaultAsync(x => x.Id == id);
InvalidCastException: Unable to cast object of type 'PlaceManagement.Place' to type 'OrganizationManagement.Organization'.

if I use AsNoTracking works fine:
await context.Places.Include(x => x.Organization).AsNoTracking().FirstOrDefaultAsync(x => x.Id == id);

It seems to be a bug because I have a specific scenario designed to simplify record retrieval. Here's the context: I have two classes, Organization and Place, both inheriting from EntityBase. The EntityBase class includes an ID property, which is a GUID.

The database already contains Organization records, and Place is a new feature. Place and Organization have a one-to-one relationship, and Place can also be associated with Location (another entity). In this setup, the Place ID shares the same GUID as the associated Organization or Location.

Based on this structure, when I attempt to retrieve an Organization, an error is thrown.

Project: InvalidCast-EF.zip

Git Project: https://github.com/wbaldanw/ef-invalid-operation-exception

@maumar maumar self-assigned this Jan 1, 2025
@maumar
Copy link
Contributor

maumar commented Jan 6, 2025

Problem is that PK values for the Place and Organization entities have the same value.

(from the repro migration code)

            //simulate the Organization values on DB
            migrationBuilder.InsertData(
                table: "Organizations",
                columns: new[] { "Id", "Name" },
                values: new object[] { "34087C3F-9E8C-4B75-BEB0-023387CF695C", "Tech Corp" });

            migrationBuilder.InsertData(
                table: "Organizations",
                columns: new[] { "Id", "Name" },
                values: new object[] { "34087C3F-9E8C-4B75-BEB0-023387CF695D", "Blue Corp" });

            migrationBuilder.InsertData(
                table: "Organizations",
                columns: new[] { "Id", "Name" },
                values: new object[] { "34087C3F-9E8C-4B75-BEB0-023387CF695E", "Green Corp" });

            //simulate the Migrations for places.
            migrationBuilder.Sql("INSERT INTO Places (Id, OrganizationId, LocationId) SELECT ID, ID, NULL FROM Organizations");

In tracking scenario we try to retrieve entity entry from the identity map, which maps IKey to EntityEntry. IKey for Organization and Person is the same (same value, same declaring entity type:

    public abstract class EntityBase
    {
        public Guid Id { get; protected set; }
    }

so the first time we try to find entry for entity Place, the map is empty so we add Place entity for that key.
Second time, we try to find entry for the entity Organization - map contains a matching key (to the previously added Place entity) and that's where the exception 'Unable to cast object of type 'Place' to type 'Organization'.' is coming from.

Workaround is to use different key values for the entities that share EntityBase base type.

@maumar maumar assigned AndriySvyryd and unassigned maumar Jan 6, 2025
@maumar
Copy link
Contributor

maumar commented Jan 6, 2025

note: if keys are changed to, say, int - when EF SaveChanges APIs are used to generate the data (rather than adding them via raw sql updates), we get the following:

The instance of entity type 'Organization' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

it "works" for guid - EF generates different values for Place entity

@maumar
Copy link
Contributor

maumar commented Jan 6, 2025

@wbaldanw this is by design/not supported scenario - entities share base type which declares the key property. Using base type not only reduces code duplication but also indicates to EF how to reason about the relationship between the entities (specifically that they share common key). You can either use different key values, or rewrite the EF model so that key is not shared

@maumar maumar closed this as not planned Won't fix, can't repro, duplicate, stale Jan 6, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants