This is a cross-posting of a corresponding github issue on the EF Core repo.
Until polymorphic relations can be supported, I am trying to take an existing schema and create some useful navigation properties out of it. Unfortunately, I've been thwarted on a number of fronts, although it feels like the closest I've been able to get is via something along these lines:
public class Entitlement
{
public Guid Id { get; set; }
public Guid EntitleableId { get; set; }
}
public class Tenant
{
public Guid Id { get; set; }
public ICollection<Entitlement> AssignedEntitlements { get; set; }
}
// note: Consider User and Firm the same as tenant in this example.
// ...
modelBuilder
.Entity<Tenant>()
.HasMany((tenant) => tenant.AssignedEntitlements)
.WithOne()
.IsRequired(true)
.HasForeignKey((entitlement) => entitlement.EntitleableId)
.HasPrincipalKey((tenant) => tenant.Id);
modelBuilder
.Entity<Firm>()
.HasMany((firm) => firm.AssignedEntitlements)
.WithOne()
.IsRequired(true)
.HasForeignKey((entitlement) => entitlement.EntitleableId)
.HasPrincipalKey((firm) => firm.Id);
modelBuilder
.Entity<User>()
.HasMany((user) => user.AssignedEntitlements)
.WithOne()
.IsRequired(true)
.HasForeignKey((entitlement) => entitlement.EntitleableId)
.HasPrincipalKey((user) => user.Id);
// ...
I can't seem to retrieve anything when using something like:
dbContext.Tenants.Include((t) => t.AssignedEntitlements);
Entity framework generates a valid query (.ToQueryString()
):
SELECT t.id, t.created_at, t."default", t.deleted_at, t.label, t.name, t.updated_at, e.id, e.created_at, e.entitleable_id, e.entitleable_type, e.feature, e.source_id, e.source_type, e.updated_at
FROM tenants AS t
LEFT JOIN entitlements AS e ON t.id = e.entitleable_id
ORDER BY t.id, e.id
Running the generated SQL returns the correct data, but any attempt to run .First(), FirstOrDefault(), etc...
on the query object seems to cause issues:
System.InvalidOperationException: Sequence contains no matching element
at System.Linq.ThrowHelper.ThrowNoMatchException()
at System.Linq.Enumerable.First[TSource](IEnumerable`1 source, Func`2 predicate)
at MyProject.Conventions.Feature..ctor(String name)
at lambda_method374(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.<PopulateIncludeCollection>g__ProcessCurrentElementRow|60_0[TIncludingEntity,TIncludedEntity](<>c__DisplayClass60_0`2& )
at Microsoft.EntityFrameworkCore.Query.RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.PopulateIncludeCollection[TIncludingEntity,TIncludedEntity](Int32 collectionId, QueryContext queryContext, DbDataReader dbDataReader, SingleQueryResultCoordinator resultCoordinator, Func`3 parentIdentifier, Func`3 outerIdentifier, Func`3 selfIdentifier, IReadOnlyList`1 parentIdentifierValueComparers, IReadOnlyList`1 outerIdentifierValueComparers, IReadOnlyList`1 selfIdentifierValueComparers, Func`5 innerShaper, INavigationBase inverseNavigation, Action`2 fixup, Boolean trackingQuery)
at lambda_method376(Closure , QueryContext , DbDataReader , ResultContext , SingleQueryResultCoordinator )
at Microsoft.EntityFrameworkCore.Query.Internal.SingleQueryingEnumerable`1.Enumerator.MoveNext()
at System.Linq.Enumerable.SingleOrDefault[TSource](IEnumerable`1 source)
at lambda_method377(Closure , QueryContext )
at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
at System.Linq.Queryable.FirstOrDefault[TSource](IQueryable`1 source)
at MyProject.Global.Api.Controller.TestController.Test(MyProjectGlobalContext context) in /home/me/Development/MyProject/global/MyProject.Global.Api/src/Controller/TestController.cs:line 19
at lambda_method289(Closure , Object )
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
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()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_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.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.Policy.AuthorizationMiddlewareResultHandler.HandleAsync(RequestDelegate next, HttpContext context, AuthorizationPolicy policy, PolicyAuthorizationResult authorizeResult)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Looks like your code cause this exception. MyProject.Conventions.Feature
constructor contains code which calls First
extension for some empty recordset.