I have a problem when trying to use TPC hierarchy mapping in EF Core 8, where I get InvalidCastException
s.
What I wanted to do is have a BaseAuditedEntity
class for most models in my app (only except for Identity models), so that I could use it for an unrelated feature: TierList
entity that has many TierListItem
s, and each TierListItem
relates to a BaseAuditedEntity
entity in the app, such as a Circle
(music group) or ArrangementSong
.
The (simplified) models:
public class Circle : BaseAuditedEntity
{
public string Name { get; set; }
public List<ArrangementSong> ArrangementSongs { get; set; }
}
public class ArrangementSong : BaseAuditedEntity
{
public string Title { get; set; }
public int CircleId { get; set; }
public Circle Circle { get; set; }
}
public class TierListItem : BaseEntity
{
public int TierListTierId { get; set; }
public TierListTier TierListTier { get; set; } = default!;
public int SourceId { get; set; }
public BaseAuditedEntity Source { get; set; }
}
public abstract class BaseAuditedEntity : BaseEntity
{
public DateTime CreatedOn { get; set; }
public string CreatedByUserName { get; set; } = string.Empty;
public DateTime? UpdatedOn { get; set; }
public string? UpdatedByUserName { get; set; }
}
public abstract class BaseEntity
{
public int Id { get; set; }
}
The base classes configuration:
modelBuilder.Entity<BaseEntity>()
.UseTpcMappingStrategy();
modelBuilder.Entity<Circle>()
.HasMany(c => c.ArrangementSongs)
.WithOne(a => a.Circle)
.IsRequired();
A simple query could be like this:
var arrangementSongs_Res = await _context.ArrangementSongs
.Include(a => a.Circle)
.Include(a => a.OfficialSongs)
.Select(a => new ArrangementSongResponse(a)
{
CircleName = a.Circle.Name,
OfficialSongTitles = a.OfficialSongs.Select(os => os.Title).ToList(),
})
.ToListAsync();
But when running, I get this exception (it doesn’t happen if I remove the .Include(a => a.Circle)
line):
[16:13:14 INFOR] Executed DbCommand (25ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
SELECT a.id, a.created_by_user_name, a.created_on, a.updated_by_user_name, a.updated_on, a.circle_id, a.status, a.title, a.title_japanese, a.title_romaji, a.url, c.id, c.created_by_user_name, c.created_on, c.updated_by_user_name, c.updated_on, c.name, c.status, t.id, t.arrangement_song_id, t.official_song_id, t.id0, t.created_by_user_name, t.created_on, t.updated_by_user_name, t.updated_on, t.context, t.game_id, t.title, t0.title, t0.id, t0.id0
FROM arrangement_songs AS a
INNER JOIN circles AS c ON a.circle_id = c.id
LEFT JOIN (
SELECT o.id, o.arrangement_song_id, o.official_song_id, o0.id AS id0, o0.created_by_user_name, o0.created_on, o0.updated_by_user_name, o0.updated_on, o0.context, o0.game_id, o0.title
FROM official_song_arrangement_song AS o
INNER JOIN official_songs AS o0 ON o.official_song_id = o0.id
) AS t ON a.id = t.arrangement_song_id
LEFT JOIN (
SELECT o2.title, o1.id, o2.id AS id0, o1.arrangement_song_id
FROM official_song_arrangement_song AS o1
INNER JOIN official_songs AS o2 ON o1.official_song_id = o2.id
) AS t0 ON a.id = t0.arrangement_song_id
ORDER BY a.id, c.id, t.id, t.id0, t0.id
[16:13:14 ERROR] An exception occurred while iterating over the results of a query for context type 'Touhou_Songs.Data.AppDbContext'.
System.InvalidCastException: Unable to cast object of type 'Touhou_Songs.App.Unofficial.Songs.ArrangementSong' to type 'Touhou_Songs.App.Unofficial.Circles.Circle'.
at lambda_method70(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
System.InvalidCastException: Unable to cast object of type 'Touhou_Songs.App.Unofficial.Songs.ArrangementSong' to type 'Touhou_Songs.App.Unofficial.Circles.Circle'.
at lambda_method70(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
[16:13:14 INFOR] Executed action Touhou_Songs.App.Unofficial.ArrangementSongs.ArrangementSongsController.GetArrangementSongs (Touhou_Songs) in 2101.1069ms
[16:13:14 INFOR] Executed endpoint 'Touhou_Songs.App.Unofficial.ArrangementSongs.ArrangementSongsController.GetArrangementSongs (Touhou_Songs)'
[16:13:15 ERROR] HTTP GET /api/ArrangementSongs responded 500 in 2264.5217 ms
System.InvalidCastException: Unable to cast object of type 'Touhou_Songs.App.Unofficial.Songs.ArrangementSong' to type 'Touhou_Songs.App.Unofficial.Circles.Circle'.
at lambda_method70(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Touhou_Songs.App.Unofficial.ArrangementSongs.Features.GetArrangementSongsHandler.Handle(GetArrangementSongsQuery request, CancellationToken cancellationToken) in D:DevSSD ProjectsMainTouhou_SongsAPIServerAppUnofficialArrangementSongsFeaturesGetArrangementSongs.cs:line 41
at Touhou_Songs.App.Unofficial.ArrangementSongs.ArrangementSongsController.GetArrangementSongs(GetArrangementSongsQuery query) in D:DevSSD ProjectsMainTouhou_SongsAPIServerAppUnofficialArrangementSongsArrangementSongsController.cs:line 19
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
[16:13:15 ERROR] An unhandled exception has occurred while executing the request.
System.InvalidCastException: Unable to cast object of type 'Touhou_Songs.App.Unofficial.Songs.ArrangementSong' to type 'Touhou_Songs.App.Unofficial.Circles.Circle'.
at lambda_method70(Closure, QueryContext, DbDataReader, ResultContext, SingleQueryResultCoordinator)
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Touhou_Songs.App.Unofficial.ArrangementSongs.Features.GetArrangementSongsHandler.Handle(GetArrangementSongsQuery request, CancellationToken cancellationToken) in D:DevSSD ProjectsMainTouhou_SongsAPIServerAppUnofficialArrangementSongsFeaturesGetArrangementSongs.cs:line 41
at Touhou_Songs.App.Unofficial.ArrangementSongs.ArrangementSongsController.GetArrangementSongs(GetArrangementSongsQuery query) in D:DevSSD ProjectsMainTouhou_SongsAPIServerAppUnofficialArrangementSongsArrangementSongsController.cs:line 19
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(ActionContext actionContext, IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|20_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|7_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
[16:13:15 INFOR] Request finished HTTP/2 GET https://localhost:5000/api/ArrangementSongs - 500 null text/plain; charset=utf-8 2424.9688ms
I’m guessing that this is a problem with BaseEntity
rows in the DB. There is no BaseEntity
table because it’s TPC, but after a migration I made to update the models from standalone Id
s to using a base class, then I see that they use a common sequence. However, the existing Id
s are unchanged, so there are many rows that share the same Id
s. But I’m not sure if this is the cause, or how to fix this.
Any help with this issue of mine is greatly appreciated!