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

EF.Property is not working in .Include. Is it a bug or intended? #27672

Closed
paxbun opened this issue Mar 21, 2022 · 4 comments
Closed

EF.Property is not working in .Include. Is it a bug or intended? #27672

paxbun opened this issue Mar 21, 2022 · 4 comments

Comments

@paxbun
Copy link

paxbun commented Mar 21, 2022

Ask a question

Remember:

  • Please make your question as clear and specific as possible.
  • Please check that the documentation does not answer your question.
  • Please search in both open and closed issues to check that your question has not already been answered.

Include your code

Usually the best way to ask a clear question and get a quick response is to show your code. Preferably, attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing.

using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Microsoft.Extensions.Configuration;

var configuration = new ConfigurationBuilder()
    .AddUserSecrets(Assembly.GetExecutingAssembly())
    .Build();
var versionString = configuration["MySQL:Version"];
var connectionString = configuration["MySQL:ConnectionString"];

Console.WriteLine("MySQL:Version = {0}", versionString);

var version = new MySqlServerVersion(new Version(versionString));
var options = new DbContextOptionsBuilder<MySqlApplicationDbContext>();

options.UseMySql(connectionString, version);

using MySqlApplicationDbContext dbContext = new(options.Options);

dbContext.Entities.RemoveRange(dbContext.Entities);
dbContext.SaveChanges();

{
    Entity entity = new();
    entity.AddVersion("Foo");
    entity.AddVersion("Bar");
    entity.AddVersion("Baz");
    dbContext.Entities.Add(entity);
}
{
    Entity entity = new();
    entity.AddVersion("Hello");
    entity.AddVersion("Bye");
    dbContext.Entities.Add(entity);
}

dbContext.SaveChanges();

{
    // System.InvalidOperationException: The expression 'Property(e, "_entityVersions")' is invalid inside
    // an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target
    // navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator
    // ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where,
    // OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including
    // related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
    //
    //
    var entities = (
        from e in dbContext.Entities
            .Include(e => EF.Property<EntityVersion>(e, "_entityVersions"))
        select e
    ).ToArray();

    // works well 1
    // var entities = (
    //     from e in dbContext.Entities
    //         .Include("_entityVersions")
    //     select e
    // ).ToArray();

    // works well 2
    // var param = Expression.Parameter(typeof(Entity));
    // var field = typeof(Entity).GetField("_entityVersions", BindingFlags.Instance | BindingFlags.NonPublic)!;
    // var fieldAccess = Expression.Field(param, field);
    // var expression = Expression.Lambda<Func<Entity, IList<EntityVersion>>>(fieldAccess, param);
    // var entities = (
    //     from e in dbContext.Entities
    //         .Include(expression)
    //     select e
    // ).ToArray();
    
    foreach (var entity in entities)
    {
        Console.WriteLine("Entity {0}:", entity.Id);
        foreach (var entityVersion in entity.EntityVersions)
        {
            Console.WriteLine(" - Value: {0}", entityVersion.Value);
        }
    }
}

public class MySqlApplicationDbContext : DbContext
{
    public DbSet<Entity> Entities { get; set; } = null!;
    public DbSet<EntityVersion> EntityVersions { get; set; } = null!;

    public MySqlApplicationDbContext(DbContextOptions options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
    }
}


public class MySqlDbContextFactory : IDesignTimeDbContextFactory<MySqlApplicationDbContext>
{
    public MySqlApplicationDbContext CreateDbContext(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .AddUserSecrets(Assembly.GetExecutingAssembly())
            .Build();
        var versionString = configuration["MySQL:Version"];
        var connectionString = configuration["MySQL:ConnectionString"];

        Console.WriteLine("MySQL:Version = {0}", versionString);

        var version = new MySqlServerVersion(new Version(versionString));
        var options = new DbContextOptionsBuilder<MySqlApplicationDbContext>();

        options.UseMySql(connectionString, version);

        return new(options.Options);
    }
}

public class EntityVersion
{
    public Guid Id { get; }
    public Guid EntityId { get; }
    public int Version { get; set; }
    public string Value { get; set; } = null!;

    public EntityVersion()
    {
    }

    public EntityVersion(Entity entity, string value)
    {
        Id = Guid.NewGuid();
        EntityId = entity.Id;
        Version = entity.AdvanceVersion();
        Value = value;
    }
}

public class EntityVersionConfiguration : IEntityTypeConfiguration<EntityVersion>
{
    public void Configure(EntityTypeBuilder<EntityVersion> builder)
    {
        builder.HasKey(m => m.Id);
        builder.Property(m => m.Id)
            .IsRequired();
        builder.Property(m => m.Version)
            .IsRequired();
        builder.HasIndex(m => m.Version);
        builder.Property(m => m.Value)
            .IsRequired();
        builder.HasOne<Entity>()
            .WithMany("_entityVersions")
            .HasForeignKey(m => m.EntityId)
            .OnDelete(DeleteBehavior.Cascade);
    }
}

public class Entity
{
    public Guid Id { get; }
    public int PublicVersion { get; private set; }
    public IEnumerable<EntityVersion> EntityVersions => _entityVersions;
    private readonly IList<EntityVersion> _entityVersions;

    public Entity()
    {
        Id = Guid.NewGuid();
        PublicVersion = 0;
        _entityVersions = new List<EntityVersion>();
    }

    public int AdvanceVersion()
    {
        return ++PublicVersion;
    }

    public void AddVersion(string value)
    {
        _entityVersions.Add(new EntityVersion(this, value));
    }
}

public class EntityConfiguration : IEntityTypeConfiguration<Entity>
{
    public void Configure(EntityTypeBuilder<Entity> builder)
    {
        builder.HasKey(m => m.Id);
        builder.Property(m => m.PublicVersion)
            .IsRequired();
        builder.Ignore(m => m.EntityVersions);
    }
}

Include stack traces

Include the full exception message and stack trace for any exception you encounter.

Use triple-tick fences for stack traces. For example:

Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
   at SixFour.Sub() in C:\Stuff\AllTogetherNow\SixFour\SixFour.cs:line 49
   at SixFour.Main() in C:\Stuff\AllTogetherNow\SixFour\SixFour.cs:line 54

Include verbose output

Please include verbose output when asking questions about the dotnet ef or Package Manager Console tools.

System.InvalidOperationException: The expression 'Property(e, "_entityVersions")' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.<ProcessInclude>g__ExtractIncludeFilter|32_0(Expression currentExpression, Expression includeExpression)
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.ProcessInclude(NavigationExpansionExpression source, Expression expression, Boolean thenInclude, Boolean setLoaded)
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   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__DisplayClass9_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
   at Program.<Main>$(String[] args) in .../Program.cs:line 51

Include provider and version information

EF Core version: 6.0.1
Database provider: Pomelo.EntityFrameworkCore.MySql 6.0.1
Target framework: .NET 6.0
Operating system: macOS
IDE: Rider 2021.3.3

@paxbun
Copy link
Author

paxbun commented Mar 21, 2022

Note that the version which uses LINQ APIs to get private field works, i.e.,

var param = Expression.Parameter(typeof(Entity));
var field = typeof(Entity).GetField("_entityVersions", BindingFlags.Instance | BindingFlags.NonPublic)!;
var fieldAccess = Expression.Field(param, field);
var expression = Expression.Lambda<Func<Entity, IList<EntityVersion>>>(fieldAccess, param);
var entities = (
    from e in dbContext.Entities
        .Include(expression)
    select e
).ToArray();

@ajcvickers
Copy link
Member

@paxbun Because a) it's not implemented, and b) it would probably be more appropriate to introduce a Navigation method for this, since it is about referencing a navigation rather than a normal property. See #27493.

@paxbun
Copy link
Author

paxbun commented Mar 21, 2022

@ajcvickers Then, if I want to use .ThenInclude, is using the LINQ APIs the only way to achieve this at the moment?

@ajcvickers
Copy link
Member

@paxbun Typically if you are building these includes dynamically, then it is easier to just use strings for the navigations to load. For example, .Include("_entityVersions").

@ajcvickers ajcvickers reopened this Oct 16, 2022
@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 16, 2022
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

2 participants