A production page started returning HTTP 500 with an InvalidCastException: “Unable to cast object of type System.Int32 to type System.String.” At first glance, the error seemed to be about an attempted cast. The more useful interpretation was that the application and database disagreed about the shape of the data.
The symptom was not the root cause
The failing endpoint loaded a list of records with Entity Framework Core. The query eagerly loaded several related entities, then failed at ToListAsync(). The exception mentioned only SqlDataReader.GetString(); it did not name the offending property or related table.
The database column was an SQL Server int, while the corresponding C# entity property was declared string. EF Core trusted the model and asked the data reader for a string. SQL Server returned an integer. GetString() does not automatically convert values, so materialization failed.
Database column: int
C# property: string?
Reader operation: GetString(...)
Actual value: System.Int32
How to make sense of the stack trace
- Start at the exception type and message.
InvalidCastExceptionsays a runtime type assumption was wrong; theInt32andStringnames identify the direction of the mismatch. - Find the first application frame. Framework frames explain the mechanism. The first application frame usually identifies the operation that triggered it—in this case, the list endpoint’s
ToListAsync(). - Notice what the stack does not say. The endpoint did not mention a doctor, phone number, or related table. That is normal: EF’s generated materializer hides the individual column mapping inside a generated lambda.
- Walk backward through the query. Inspect
Include, projections, joins, and entity models. AnIncludecan load every column of a related row even when the controller only displays its name. - Compare the model with the live schema. Check the actual SQL type and nullability, then look for non-null rows that exercise the mismatch.
Why it appeared on one page
The query could count rows successfully because CountAsync() does not materialize entity properties. The failure happened only when the paged results were read. It was also data-dependent: a null value would be skipped, while a non-null integer in the mismatched column triggered GetString(). That explains why the error appeared for a particular user, page, or sort/filter combination.
The fix
There are two possible strategies: explicitly convert the value in SQL, or make the application model match the database. When the production schema is authoritative, aligning the EF property with the SQL type is usually the safer fix. After changing the property to nullable int, EF uses the integer reader and materialization succeeds.
The broader lesson is simple: treat a cast exception as a type-contract investigation. Read the stack from the bottom up, identify the materialization boundary, inspect hidden eager loads, and verify the live schema instead of guessing from the controller’s visible fields.