EntityFrameworkCore.UseRowNumberForPaging icon indicating copy to clipboard operation
EntityFrameworkCore.UseRowNumberForPaging copied to clipboard

Adds support for split queries

Open QianMoXi opened this issue 2 years ago • 6 comments

the subquery is not converted when using a split query.

image

example code:

public class Table1
{
    public int          Id      { get; set; }
    public List<Table2> Table2s { get; set; }
    public List<Table3> Table3s { get; set; }
}
public class Table2
{
    public int    Id       { get; set; }
    public int    Table1ID { get; set; }
    public Table1 Table1   { get; set; }
}

public class Table3
{
    public int    Id       { get; set; }
    public int    Table1ID { get; set; }
    public Table1 Table1   { get; set; }
}
public class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions options) : base(options)
    {
    }

    public DbSet<Table1> Table1s { get; set; }
    public DbSet<Table2> Table2s { get; set; }
    public DbSet<Table3> Table3s { get; set; }
}

internal class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddLogging(b => b.AddConsole());
        services.AddDbContext<MyDbContext>(b =>
        {
            b.UseSqlServer("Data Source=localhost;Initial Catalog=TestDb1;Integrated Security=True;Encrypt=False;", s =>
            {
                s.UseRowNumberForPaging();
            });
        });

        using var provider = services.BuildServiceProvider();
        using (var scope = provider.CreateScope())
        {
            var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();

            for (var i = 0; i < 100; i++)
            {
                context.Table1s.Add(new()
                {
                    Table2s = new() {new(), new(), new(), new()},
                    Table3s = new() {new(), new(), new(), new()}
                });
            }

            context.SaveChanges();

            context.Table1s
                   .Include(x => x.Table2s)
                   .Include(x => x.Table3s)
                   .Skip(5)
                   .Take(5)
                   .AsSplitQuery() // using a split query
                   .ToList();
        }

        Console.ReadLine();
    }
}

QianMoXi avatar Dec 14 '22 02:12 QianMoXi

Yes, it has been confirmed as a bug and I will take care of it as soon as not busy

Rwing avatar Jan 12 '23 03:01 Rwing

Hi everyone,

Facing the same issue. Can you point us, so we can help coding this, please?

Cheers

deaquino avatar May 28 '24 08:05 deaquino

@deaquino I haven't used SQL Server in a long time and can't test it.

EF Core puts the split query in the RelationalSplitCollectionShaperExpression, so you can handle it in the VisitExtension method as well, which might solve the problem

Related Links: https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.query.relationalsplitcollectionshaperexpression?view=efcore-8.0

QianMoXi avatar Jun 14 '24 07:06 QianMoXi

@deaquino I have solved the problem by adding code at beginning of "VisitSelect" method:

if (selectExpression.Tables.Any())
{
    selectExpression = selectExpression.Update(projections: selectExpression.Projection.ToList(),
        predicate: selectExpression.Predicate,
        tables: selectExpression.Tables.Select(x => (TableExpressionBase)VisitExtension(x)).ToList().AsReadOnly(),
        groupBy: selectExpression.GroupBy.ToList(),
        having: selectExpression.Having,
        orderings: selectExpression.Orderings,
        limit: selectExpression.Limit,
        offset: selectExpression.Offset);
}

Problem is "VisitExtension" method is not called for property "Tables" when processing expression type "SelectExpression". This code will ensure it is called. I did this for .NET 8 visitor class. This fix may not be the most optimal one but it works.

Full code:

private Expression VisitSelect(SelectExpression selectExpression)
{
        if (selectExpression.Tables.Any())
        {
            selectExpression = selectExpression.Update(projections: selectExpression.Projection.ToList(),
                predicate: selectExpression.Predicate,
                tables: selectExpression.Tables.Select(x => (TableExpressionBase)VisitExtension(x)).ToList().AsReadOnly(),
                groupBy: selectExpression.GroupBy.ToList(),
                having: selectExpression.Having,
                orderings: selectExpression.Orderings,
                limit: selectExpression.Limit,
                offset: selectExpression.Offset);
        }
  
        var oldOffset = selectExpression.Offset;
        if (oldOffset == null)
            return selectExpression;
        var oldLimit = selectExpression.Limit;
        var oldOrderings = selectExpression.Orderings;
        var newOrderings = oldOrderings.Count > 0 && (oldLimit != null || selectExpression == root)
            ? oldOrderings.ToList()
            : new List<OrderingExpression>();
      
        ...

LukaMatkic avatar Jan 23 '25 10:01 LukaMatkic

Yes, it has been confirmed as a bug and I will take care of it as soon as not busy

Can you put some time on this ticket? I think you were NOT busy in the past 3 years. At least you have time to bully others like me.

tonyqus avatar Apr 06 '25 20:04 tonyqus