koenbeuk / EntityFrameworkCore.Projectables

Project over properties and functions in your linq queries
MIT License
260 stars 17 forks source link

Projectable property not include in query result without select #95

Open universorum opened 8 months ago

universorum commented 8 months ago

With Projectable attr, I can select the property direct via Select and/or use as judgement. But directly query the set will not include projectable property.

Env: .net6/EFcore7

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Diagnostics;
using System.Linq.Expressions;
using EntityFrameworkCore.Projectables;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;

var connection = new SqliteConnection("Filename=:memory:");
connection.Open();

var contextOptions = new DbContextOptionsBuilder<AppDbContext()
    .UseSqlite(connection)
    .UseProjectables()
    .Options;

using (var context = new AppDbContext(contextOptions))
{
    context.Database.EnsureCreated();

    context.Users.Add(new UserEntity
    {
        Id     = 1,
        Name   = "Name",
        Orders = new List<OrderEntity> { new() { Id = 1, Amount = 10 }, new() { Id = 2, Amount = 20 } }
    });
    await context.SaveChangesAsync();
}

using (var context = new AppDbContext(contextOptions))
{
    UserEntity user;
    user = await context.Users.FirstAsync(x => x.Count1 == 2);
    Debug.Assert(user != null);
    user = await context.Users.FirstAsync(x => x.Count2 == 2);
    Debug.Assert(user != null);
    user = await context.Users.FirstAsync(x => x.Count3 == 2);
    Debug.Assert(user != null);

    int count;
    count = await context.Users.Select(x => x.Count1).FirstAsync();
    Debug.Assert(count == 2);
    count = await context.Users.Select(x => x.Count2).FirstAsync();
    Debug.Assert(count == 2);
    count = await context.Users.Select(x => x.Count3).FirstAsync();
    Debug.Assert(count == 2);

    user = await context.Users.FirstAsync();
    if (user.Count1 != 2) { Console.WriteLine("Failed: Count1"); }

    if (user.Count2 != 2) { Console.WriteLine("Failed: Count2"); }

    if (user.Count3 != 2) { Console.WriteLine("Failed: Count3"); }
}

public class UserEntity
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    [InverseProperty(nameof(OrderEntity.User))]
    public ICollection<OrderEntity> Orders { get; init; } = new List<OrderEntity>(0);

    [NotMapped]
    [Projectable]
    public int Count1 => Orders.Count;

    [NotMapped]
    [Projectable(UseMemberBody = nameof(InternalCount2))]
    public int Count2 { get; set; }

    private int InternalCount2 => Orders.Count;

    [NotMapped]
    [Projectable(UseMemberBody = nameof(InternalCount3))]
    public int Count3 { get; set; }

    private static Expression<Func<UserEntity, int>> InternalCount3 => u => u.Orders.Count;
}

public class OrderEntity
{
    [Key]
    public int Id { get; set; }

    public int UserId { get; set; }

    public int Amount { get; set; }

    [ForeignKey(nameof(UserId))]
    [InverseProperty(nameof(UserEntity.Orders))]
    public virtual UserEntity User { get; set; }
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
    public DbSet<UserEntity>  Users  { get; set; }
    public DbSet<OrderEntity> Orders { get; set; }
}
leoerlandsson commented 8 months ago

We've seen this as well, but haven't been that affected by it, as we use a mapper that always selects the desired attributes. But yes, it's strange. Also on .NET 6 / EF Core 7, but the same behaviour was there before aswell.

koenbeuk commented 8 months ago

This is a known issue, a partial fix has been implemented with #86. I'm currently working on a more comprehensive fix that should go out as a larger update that will target EF 8 and above.