efcore: EF Core 3.1.7 Eager Load two level ThenInclude last one with inherit type not working

second .ThenInclude with a derived type class, .ThenInclude(…).ThenInclude(x => ((derivedClass)x).prop)

i have below code not working and need help to make it work. i have two data models classes in two separate DLLs, and wanted to create a one-to-one optional relationship, of course with direct reference that would lead to “circular reference” situation. my solution was to have a third DLL referencing both and have another data model inherited from one of the models and add relationship to that. now i need to include the extra property on that module, my code goes as follow

SecurityGroup securityGroup = await _securityGroupRepository.GetAll().Where(sg => sg.SecurityGroupId == new Guid(groupId))
                                                   .Include(sg => sg.SecurityPolicies)
                                                   .Include(sg => sg.Users)
                                                        .ThenInclude(sgu => sgu.User)
                                                        .ThenInclude(su => ((CrossSecurityUser)su).Person) //this is not working, if i remove it work
                                                        .FirstOrDefaultAsync();

on 1st DLL, Person Module

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;

namespace WorkNxt.Contacts.Models
{
    public class Person
    {
        [Required]
        public Guid PersonId { get; set; }
        [Required]
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        [Required]
        public string LastName { get; set; }
        //public virtual List<PersonsOrgnizations> Organizations { get; set; }
        [NotMapped]
        public string FullName => $"{FirstName} {MiddleName} {LastName}";
        public byte[] RowVersion { get; set; }
    }

    public class PersonEfConfig : IEntityTypeConfiguration<Person>
    {
        public void Configure(EntityTypeBuilder<Person> builder)
        {
            builder.ToTable("Person");
            builder.Property(e => e.PersonId).HasColumnName("PersonId").ValueGeneratedOnAdd();
            builder.HasKey(e => e.PersonId);
            //builder.HasMany(p => p.Organizations).WithOne(po => po.Person).HasForeignKey(po => po.PersonId).IsRequired(false);
            builder.Property(e => e.RowVersion).IsConcurrencyToken().ValueGeneratedOnAddOrUpdate();
        }
    }
}

on 2nd DLL, SecurityGroup, SecurityGroupUsers and Security User which i want one-to-one with Person and same from Person (Circular reference) Modules

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace WorkNxt.Security.Models
{
    public class SecurityGroup
    {
        [Required]
        public Guid SecurityGroupId { get; set; }
        public string SecurityGroupCode { get; set; }
        public string Descrption { get; set; }
        public byte[] RowVersion { get; set; }

        public virtual List<SecurityGroupsTenantsPolicies> SecurityPolicies { get; set; }

        public virtual List<SecurityGroupsUsers> Users { get; set; }
    }

    public class SecurityGroupEfConfig : IEntityTypeConfiguration<SecurityGroup>
    {
        public void Configure(EntityTypeBuilder<SecurityGroup> builder)
        {
            builder.ToTable("SecurityGroup");
            builder.Property(e => e.SecurityGroupId).HasColumnName("SecurityGroupId");

            builder.HasKey(e => new { e.SecurityGroupId });

            builder.Property(e => e.RowVersion).IsConcurrencyToken().ValueGeneratedOnAddOrUpdate();

            builder.HasMany(sg => sg.SecurityPolicies).WithOne(pg => pg.SecurityGroup).HasForeignKey(pg => pg.SecurityGroupId);
            builder.HasMany(sg => sg.Users).WithOne(ug => ug.SecurityGroup).HasForeignKey(ug => ug.GroupId);
        }
    }
}
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.ComponentModel.DataAnnotations;

namespace WorkNxt.Security.Models
{
    public class SecurityGroupsUsers
    {
        [Required]
        public Guid UserId { get; set; }
        public SecurityUser User { get; set; }
        [Required]
        public Guid GroupId { get; set; }
        public SecurityGroup SecurityGroup { get; set; }

        public class SecurityGroupsUsersEfConfig : IEntityTypeConfiguration<SecurityGroupsUsers>
        {
            public void Configure(EntityTypeBuilder<SecurityGroupsUsers> builder)
            {
                builder.ToTable("SecurityGroupsUsers");
                builder.Property(e => e.UserId).HasColumnName("UserId");
                builder.Property(e => e.GroupId).HasColumnName("GroupId");

                builder.HasKey(e => new { e.UserId, e.GroupId });

                builder.HasOne(ug => ug.User).WithMany(u => u.SecurityGroups).HasForeignKey(ug => ug.UserId);
                builder.HasOne(ug => ug.SecurityGroup).WithMany(sg => sg.Users).HasForeignKey(ug => ug.GroupId);
            }
        }
    }
}
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Text;


namespace WorkNxt.Security.Models
{
    public class SecurityUser
    {
        public Guid SecurityUserId { get; set; }
        [Required]
        public string UserName { get; set; }
        [NotMapped]
        [Required]
        [DataType(DataType.Password)]
        public string Password { get; set; }
        public string Hash { get; set; }
        public string Salt { get; set; }
        public byte[] RowVersion { get; set; }
        public virtual List<SecurityGroupsUsers> SecurityGroups { get; set; }
    }

    public class SecurityUserEfConfig : IEntityTypeConfiguration<SecurityUser>
    {
        public void Configure(EntityTypeBuilder<SecurityUser> builder)
        {
            builder.ToTable("SecurityUser");
            builder.Property(e => e.SecurityUserId).HasColumnName("SecurityUserId");
            builder.HasKey(e => new { e.SecurityUserId });

            builder.HasIndex(e => e.UserName).IsUnique();

            builder.Property(e => e.RowVersion).IsConcurrencyToken().ValueGeneratedOnAddOrUpdate();

            builder.HasMany(u => u.SecurityGroups).WithOne(sg => sg.User).HasForeignKey(sg => sg.UserId);
        }
    }
}

to avoid that i have a 3rd DLL with

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.ComponentModel.DataAnnotations.Schema;
using WorkNxt.Contacts.Models;
using WorkNxt.Security.Models;

namespace WorkNxt.Security.CrossModels
{
    public class CrossSecurityUser : SecurityUser
    {
        public Guid? PersonId { get; set; }
        [ForeignKey("PersonId")]
        public virtual Person Person { get; set; }
    }
}

now when this execute

SecurityGroup securityGroup = await _securityGroupRepository.GetAll().Where(sg => sg.SecurityGroupId == new Guid(groupId))
                                                   .Include(sg => sg.SecurityPolicies)
                                                   .Include(sg => sg.Users)
                                                        .ThenInclude(sgu => sgu.User)
                                                        .ThenInclude(su => ((CrossSecurityUser)su).Person) //this is not working, if i remove it work
                                                        .FirstOrDefaultAsync();

i get “Invalid include.”

Stack Trace

at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.PopulateIncludeTree(IncludeTreeNode includeTreeNode, Expression expression) at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ProcessInclude(NavigationExpansionExpression source, Expression expression, Boolean thenInclude) at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.Expand(Expression query) at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query) at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query) at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_01.<ExecuteAsync>b__0() at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func1 compiler) at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func1 compiler) at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable1 source, Expression expression, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable1 source, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable1 source, CancellationToken cancellationToken) at WorkNxt.Security.Api.Controllers.GroupController.<Get>d__7.MoveNext() in {removed for privacy}\WorkNxt.Security.Api\Controllers\GroupController.cs:line 101

Further technical details

EF Core version: 3.1.7 Database provider: (Microsoft.EntityFrameworkCore.SqlServer) Target framework: (.NET Core 3.1) Operating system: Windows10 IDE: (Visual Studio 2019 16.7.2)

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Comments: 17 (8 by maintainers)

Most upvoted comments

https://github.com/dotnet/efcore/issues/10140 is needed to map this scenario correctly