diff --git a/EntityFrameworkCore.UseRowNumberForPaging.Test/EntityFrameworkCore.UseRowNumberForPaging.Test.csproj b/EntityFrameworkCore.UseRowNumberForPaging.Test/EntityFrameworkCore.UseRowNumberForPaging.Test.csproj index 1e26f68..4ace74d 100644 --- a/EntityFrameworkCore.UseRowNumberForPaging.Test/EntityFrameworkCore.UseRowNumberForPaging.Test.csproj +++ b/EntityFrameworkCore.UseRowNumberForPaging.Test/EntityFrameworkCore.UseRowNumberForPaging.Test.csproj @@ -1,20 +1,20 @@ - net6.0;net7.0;net8.0 + net8.0;net9.0 false - - - - + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/EntityFrameworkCore.UseRowNumberForPaging.Test/NotUseRowNumberDbContext.cs b/EntityFrameworkCore.UseRowNumberForPaging.Test/NotUseRowNumberDbContext.cs index fe91077..4f85b22 100644 --- a/EntityFrameworkCore.UseRowNumberForPaging.Test/NotUseRowNumberDbContext.cs +++ b/EntityFrameworkCore.UseRowNumberForPaging.Test/NotUseRowNumberDbContext.cs @@ -1,21 +1,15 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; -namespace EntityFrameworkCore.UseRowNumberForPaging +namespace EntityFrameworkCore.UseRowNumberForPaging; + +public class NotUseRowNumberDbContext : DbContext { - public class NotUseRowNumberDbContext : DbContext - { - public DbSet Blogs { get; set; } + public DbSet Blogs { get; set; } + public DbSet Authors { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseSqlServer( - @"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True"); - } + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + optionsBuilder.UseSqlServer( + @"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True"); } - } diff --git a/EntityFrameworkCore.UseRowNumberForPaging.Test/SimpleTestCases.cs b/EntityFrameworkCore.UseRowNumberForPaging.Test/SimpleTestCases.cs new file mode 100644 index 0000000..cbdd883 --- /dev/null +++ b/EntityFrameworkCore.UseRowNumberForPaging.Test/SimpleTestCases.cs @@ -0,0 +1,64 @@ +using System.Linq; +using Microsoft.EntityFrameworkCore; +using Shouldly; +using Xunit; + +namespace EntityFrameworkCore.UseRowNumberForPaging.Test; + +public class SimpleTestCases +{ + [Fact] + public void With_TrivialOk() + { + using (var dbContext = new UseRowNumberDbContext()) + { + var rawSql = dbContext.Blogs.Where(i => i.BlogId > 1).Skip(0).Take(10).ToQueryString(); + rawSql.ShouldContain("ROW_NUMBER"); + } + } + + [Fact] + public void Without_TrivialOk() + { + using (var dbContext = new NotUseRowNumberDbContext()) + { + var rawSql = dbContext.Blogs.Where(i => i.BlogId > 1).Skip(0).Take(10).ToQueryString(); + rawSql.ShouldContain("OFFSET"); + rawSql.ShouldNotContain("ROW_NUMBER"); + } + } + + [Fact] + public void With_NoSkipClause_OrderDesc_NoRowNumber() + { + using var dbContext = new UseRowNumberDbContext(); + var rawSql = dbContext.Blogs.Where(i => i.BlogId > 1).OrderByDescending(o => o.Rating).Take(20).ToQueryString(); + rawSql.ShouldNotContain("ROW_NUMBER"); + rawSql.ShouldContain("TOP"); + rawSql.ShouldContain("ORDER BY"); + } + + [Fact] + public void With_OrderDesc_UsesRowNumber() + { + using var dbContext = new UseRowNumberDbContext(); + var rawSql = dbContext.Blogs.Where(i => i.BlogId > 1).OrderByDescending(o => o.Rating).Skip(20).Take(20).ToQueryString(); + rawSql.ShouldContain("ROW_NUMBER"); + rawSql.ShouldContain("ORDER BY"); + rawSql.ShouldContain("TOP"); + } + + [Fact] + public void With_Order_SplitQuery_UsesRowNumber() + { + using var dbContext = new UseRowNumberDbContext(); + var rawSql = dbContext.Blogs.Include(b => b.Author).Where(i => i.BlogId > 1) + .OrderBy(a => a.Author.ContributingSince) + .OrderByDescending(o => o.Rating) + .Skip(30).Take(15) + .AsSplitQuery().ToQueryString(); + rawSql.ShouldContain("ROW_NUMBER"); + rawSql.ShouldContain("ORDER BY"); + rawSql.ShouldContain("TOP"); + } +} diff --git a/EntityFrameworkCore.UseRowNumberForPaging.Test/UnitTest1.cs b/EntityFrameworkCore.UseRowNumberForPaging.Test/UnitTest1.cs deleted file mode 100644 index b7be8f0..0000000 --- a/EntityFrameworkCore.UseRowNumberForPaging.Test/UnitTest1.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using System.Linq; -using Microsoft.EntityFrameworkCore; -using Shouldly; -using Xunit; - -namespace EntityFrameworkCore.UseRowNumberForPaging.Test -{ - public class UnitTest1 - { - [Fact] - public void Test1() - { - using (var dbContext = new UseRowNumberDbContext()) - { - var rawSql = dbContext.Blogs.Where(i => i.BlogId > 1).Skip(0).Take(10).ToQueryString(); - rawSql.ShouldContain("ROW_NUMBER"); - } - } - - [Fact] - public void Test2() - { - using (var dbContext = new NotUseRowNumberDbContext()) - { - var rawSql = dbContext.Blogs.Where(i => i.BlogId > 1).Skip(0).Take(10).ToQueryString(); - rawSql.ShouldContain("OFFSET"); - rawSql.ShouldNotContain("ROW_NUMBER"); - } - } - } -} diff --git a/EntityFrameworkCore.UseRowNumberForPaging.Test/UseRowNumberDbContext.cs b/EntityFrameworkCore.UseRowNumberForPaging.Test/UseRowNumberDbContext.cs index fe96a69..f73874c 100644 --- a/EntityFrameworkCore.UseRowNumberForPaging.Test/UseRowNumberDbContext.cs +++ b/EntityFrameworkCore.UseRowNumberForPaging.Test/UseRowNumberDbContext.cs @@ -1,27 +1,32 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; -namespace EntityFrameworkCore.UseRowNumberForPaging -{ - public class UseRowNumberDbContext : DbContext - { - public DbSet Blogs { get; set; } +namespace EntityFrameworkCore.UseRowNumberForPaging; - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) - { - optionsBuilder.UseSqlServer( - @"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True", i => i.UseRowNumberForPaging()); - } - } +public class UseRowNumberDbContext : DbContext +{ + public DbSet Blogs { get; set; } + public DbSet Authors { get; set; } - public class Blog + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { - public int BlogId { get; set; } - public string Url { get; set; } - public int Rating { get; set; } + optionsBuilder.UseSqlServer( + @"Server=(localdb)\mssqllocaldb;Database=Blogging;Integrated Security=True", i => i.UseRowNumberForPaging()); } } + +public class Blog +{ + public int BlogId { get; set; } + public string Url { get; set; } + public int Rating { get; set; } + public virtual Author Author { get; set; } +} +public class Author +{ + public int AuthorId { get; set; } + public string Name { get; set; } + public DateOnly ContributingSince { get; set; } + public virtual List Blogs { get; set; } +} diff --git a/EntityFrameworkCore.UseRowNumberForPaging/EntityFrameworkCore.UseRowNumberForPaging.csproj b/EntityFrameworkCore.UseRowNumberForPaging/EntityFrameworkCore.UseRowNumberForPaging.csproj index 79edfab..696879a 100644 --- a/EntityFrameworkCore.UseRowNumberForPaging/EntityFrameworkCore.UseRowNumberForPaging.csproj +++ b/EntityFrameworkCore.UseRowNumberForPaging/EntityFrameworkCore.UseRowNumberForPaging.csproj @@ -1,13 +1,13 @@  - net6.0;net7.0;net8.0 - 0.5 + net8.0;net9.0 + 0.7 Rwing true https://github.com/Rwing/EntityFrameworkCore.UseRowNumberForPaging https://github.com/Rwing/EntityFrameworkCore.UseRowNumberForPaging - Bring back support for UseRowNumberForPaging in EntityFrameworkCore 8.0/7.0/6.0/5.0. Use a ROW_NUMBER() in queries instead of OFFSET/FETCH. This method is backwards-compatible to SQL Server 2005. + Bring back support for UseRowNumberForPaging in EntityFrameworkCore 9.0/8.0. Use a ROW_NUMBER() in queries instead of OFFSET/FETCH. This method is backwards-compatible to SQL Server 2005. LICENSE true true @@ -15,21 +15,22 @@ README.md + + + $(NoWarn);NU1901;NU1902; + + - + - - - + + + - - - + + + - - - - True diff --git a/EntityFrameworkCore.UseRowNumberForPaging/Offset2RowNumberConvertVisitor.net8.cs b/EntityFrameworkCore.UseRowNumberForPaging/Offset2RowNumberConvertVisitor.net8.cs new file mode 100644 index 0000000..e733652 --- /dev/null +++ b/EntityFrameworkCore.UseRowNumberForPaging/Offset2RowNumberConvertVisitor.net8.cs @@ -0,0 +1,101 @@ +#if !NET9_0_OR_GREATER +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace EntityFrameworkCore.UseRowNumberForPaging; + +internal class Offset2RowNumberConvertVisitor : ExpressionVisitor +{ + private static readonly MethodInfo GenerateOuterColumnAccessor; + private static readonly Type TableReferenceExpressionType; + + static Offset2RowNumberConvertVisitor() + { + var method = typeof(SelectExpression).GetMethod("GenerateOuterColumn", BindingFlags.NonPublic | BindingFlags.Instance); + if (!typeof(ColumnExpression).IsAssignableFrom(method?.ReturnType)) + { + throw new InvalidOperationException("SelectExpression.GenerateOuterColum() was not found"); + } + + TableReferenceExpressionType = method.GetParameters().First().ParameterType; + GenerateOuterColumnAccessor = method; + } + + private readonly Expression root; + private readonly ISqlExpressionFactory sqlExpressionFactory; + + public Offset2RowNumberConvertVisitor(Expression root, ISqlExpressionFactory sqlExpressionFactory) + { + this.root = root; + this.sqlExpressionFactory = sqlExpressionFactory; + } + + protected override Expression VisitExtension(Expression node) + { + if (node is ShapedQueryExpression shapedQueryExpression) + { + return shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), Visit(shapedQueryExpression.ShaperExpression)); + } + if (node is SelectExpression se) + { + return VisitSelect(se); + } + return base.VisitExtension(node); + } + + private Expression VisitSelect(SelectExpression selectExpression) + { + 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(); + // Change SelectExpression + selectExpression = selectExpression.Update(projections: selectExpression.Projection.ToList(), + tables: selectExpression.Tables.ToList(), + predicate: selectExpression.Predicate, + groupBy: selectExpression.GroupBy.ToList(), + having: selectExpression.Having, + orderings: newOrderings, + limit: null, + offset: null); + var rowOrderings = oldOrderings.Count != 0 ? oldOrderings + : new[] { new OrderingExpression(new SqlFragmentExpression("(SELECT 1)"), true) }; + + selectExpression.PushdownIntoSubquery(); + + var subQuery = (SelectExpression)selectExpression.Tables[0]; + var projection = new RowNumberExpression(Array.Empty(), rowOrderings, oldOffset.TypeMapping); + var left = GenerateOuterColumnAccessor.Invoke(subQuery + , new object[] + { + Activator.CreateInstance(TableReferenceExpressionType, new object[] { subQuery,subQuery.Alias! })!, + projection, + "row", + true + }) as ColumnExpression; + selectExpression.ApplyPredicate(sqlExpressionFactory.GreaterThan(left!, oldOffset)); + + if (oldLimit != null) + { + if (oldOrderings.Count == 0) + { + selectExpression.ApplyPredicate(sqlExpressionFactory.LessThanOrEqual(left, sqlExpressionFactory.Add(oldOffset, oldLimit))); + } + else + { + selectExpression.ApplyLimit(oldLimit); + } + } + return selectExpression; + } +} +#endif \ No newline at end of file diff --git a/EntityFrameworkCore.UseRowNumberForPaging/Offset2RowNumberConvertVisitor.net9.cs b/EntityFrameworkCore.UseRowNumberForPaging/Offset2RowNumberConvertVisitor.net9.cs new file mode 100644 index 0000000..b5bda52 --- /dev/null +++ b/EntityFrameworkCore.UseRowNumberForPaging/Offset2RowNumberConvertVisitor.net9.cs @@ -0,0 +1,118 @@ +#if NET9_0_OR_GREATER +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; + +namespace EntityFrameworkCore.UseRowNumberForPaging; + +internal class Offset2RowNumberConvertVisitor( + Expression root, + ISqlExpressionFactory sqlExpressionFactory, + SqlAliasManager sqlAliasManager +) : ExpressionVisitor +{ + private readonly Expression root = root; + private readonly ISqlExpressionFactory sqlExpressionFactory = sqlExpressionFactory; + private readonly SqlAliasManager sqlAliasManager = sqlAliasManager; + + protected override Expression VisitExtension(Expression node) => node switch + { + ShapedQueryExpression shapedQueryExpression => shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), Visit(shapedQueryExpression.ShaperExpression)), + SelectExpression se => VisitSelect(se), + _ => base.VisitExtension(node), + }; + + private SelectExpression VisitSelect(SelectExpression selectExpression) + { + // if we have no offset, we do not need to use ROW_NUMBER for offset calculations + if (selectExpression.Offset == null) + { + return selectExpression; + } + var isRootQuery = selectExpression == root; + + // store offset, limit and orderings + var oldOffset = selectExpression.Offset; + var oldLimit = selectExpression.Limit; + var oldOrderings = selectExpression.Orderings; + + // remove offset and limit by creating new select expression from old one + // we can't use SelectExpression.Update because that breaks PushDownIntoSubquery + var enhancedSelect = new SelectExpression( + alias: null, + tables: new(selectExpression.Tables), + predicate: selectExpression.Predicate, + groupBy: new(selectExpression.GroupBy), + having: selectExpression.Having, + projections: new(selectExpression.Projection), + distinct: selectExpression.IsDistinct, + orderings: isRootQuery ? [] : new(selectExpression.Orderings), + offset: null, + limit: null, + tags: selectExpression.Tags, + annotations: null, + sqlAliasManager: sqlAliasManager, + isMutable: true + ); + // set up row_number expression + var rowNumber = new RowNumberExpression([], isRootQuery ? [ new(new SqlFragmentExpression("(SELECT 1)"), true) ] : oldOrderings, oldOffset.TypeMapping); + enhancedSelect.AddToProjection(rowNumber); + enhancedSelect.PushdownIntoSubquery(); + + // restore ordering to outer select after earlier removal + if (isRootQuery) + { + foreach (var orderingClause in oldOrderings) + { + selectExpression.AppendOrdering(orderingClause); + } + } + + // generate subselect rownumber access expression + var innerTable = enhancedSelect.Tables[0]; + var rowNumberColname = enhancedSelect.Projection[enhancedSelect.Projection.Count - 1].Alias; + var rowNumberAlias = enhancedSelect.CreateColumnExpression(innerTable, rowNumberColname, typeof(int), null, false); + + // apply offset and limit + var rowNumberGtOffset = sqlExpressionFactory.GreaterThan(rowNumberAlias, oldOffset); + enhancedSelect.ApplyPredicate(rowNumberGtOffset); + if (oldLimit != null) + { + if (oldOrderings.Count == 0) + { + var rowNumberLimiting = sqlExpressionFactory.LessThanOrEqual(rowNumberAlias, sqlExpressionFactory.Add(oldOffset, oldLimit)); + enhancedSelect.ApplyPredicate(rowNumberLimiting); + } + else + { + enhancedSelect.ApplyLimit(oldLimit); + } + } + + enhancedSelect.ApplyProjection(); // to make immutable + var restoredProjections = enhancedSelect.Projection + .Where(p => p.Alias != rowNumberColname) + .ToList(); + var result = enhancedSelect.Update( + enhancedSelect.Tables, + enhancedSelect.Predicate, + enhancedSelect.GroupBy, + enhancedSelect.Having, + restoredProjections, + enhancedSelect.Orderings, + enhancedSelect.Offset, + enhancedSelect.Limit + ); + + // restore projection member binding lookup capabilities via reflection magic + var clientProjections = typeof(SelectExpression).GetField("_clientProjections", BindingFlags.NonPublic | BindingFlags.Instance); + clientProjections.SetValue(result, clientProjections.GetValue(selectExpression)); + var projectionMapping = typeof(SelectExpression).GetField("_projectionMapping", BindingFlags.NonPublic | BindingFlags.Instance); + projectionMapping.SetValue(result, projectionMapping.GetValue(selectExpression)); + return result; + } +} +#endif \ No newline at end of file diff --git a/EntityFrameworkCore.UseRowNumberForPaging/SqlServer2008QueryTranslationPostprocessorFactory.cs b/EntityFrameworkCore.UseRowNumberForPaging/SqlServer2008QueryTranslationPostprocessorFactory.cs index 93e8080..6967bb4 100644 --- a/EntityFrameworkCore.UseRowNumberForPaging/SqlServer2008QueryTranslationPostprocessorFactory.cs +++ b/EntityFrameworkCore.UseRowNumberForPaging/SqlServer2008QueryTranslationPostprocessorFactory.cs @@ -1,10 +1,5 @@ -using System.Collections.Generic; -using System.Linq; -using System; -using System.Linq.Expressions; -using System.Reflection; +using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Query; -using Microsoft.EntityFrameworkCore.Query.SqlExpressions; namespace EntityFrameworkCore.UseRowNumberForPaging { @@ -22,111 +17,35 @@ public virtual QueryTranslationPostprocessor Create(QueryCompilationContext quer => new SqlServer2008QueryTranslationPostprocessor( _dependencies, _relationalDependencies, - queryCompilationContext); - public class SqlServer2008QueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor - { - public SqlServer2008QueryTranslationPostprocessor(QueryTranslationPostprocessorDependencies dependencies, RelationalQueryTranslationPostprocessorDependencies relationalDependencies, QueryCompilationContext queryCompilationContext) - : base(dependencies, relationalDependencies, queryCompilationContext) - { - } - public override Expression Process(Expression query) - { - query = base.Process(query); - query = new Offset2RowNumberConvertVisitor(query, RelationalDependencies.SqlExpressionFactory).Visit(query); - return query; - } - internal class Offset2RowNumberConvertVisitor : ExpressionVisitor - { -#if !NET5_0 - private static readonly MethodInfo GenerateOuterColumnAccessor; - private static readonly Type TableReferenceExpressionType; +#if NET9_0_OR_GREATER + (RelationalQueryCompilationContext)queryCompilationContext #else - private static readonly Func GenerateOuterColumnAccessor; + queryCompilationContext #endif - private readonly Expression root; - private readonly ISqlExpressionFactory sqlExpressionFactory; - static Offset2RowNumberConvertVisitor() - { - var method = typeof(SelectExpression).GetMethod("GenerateOuterColumn", BindingFlags.NonPublic | BindingFlags.Instance); - - if (!typeof(ColumnExpression).IsAssignableFrom(method?.ReturnType)) - throw new InvalidOperationException("SelectExpression.GenerateOuterColumn() is not found."); - -#if !NET5_0 - TableReferenceExpressionType = method.GetParameters().First().ParameterType; - GenerateOuterColumnAccessor = method; + ); + public class SqlServer2008QueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor + { + public SqlServer2008QueryTranslationPostprocessor( + QueryTranslationPostprocessorDependencies dependencies, + RelationalQueryTranslationPostprocessorDependencies relationalDependencies, +#if NET9_0_OR_GREATER + RelationalQueryCompilationContext queryCompilationContext #else - GenerateOuterColumnAccessor = (Func)method.CreateDelegate(typeof(Func)); + QueryCompilationContext queryCompilationContext #endif - } - public Offset2RowNumberConvertVisitor(Expression root, ISqlExpressionFactory sqlExpressionFactory) - { - this.root = root; - this.sqlExpressionFactory = sqlExpressionFactory; - } - protected override Expression VisitExtension(Expression node) - { - if (node is ShapedQueryExpression shapedQueryExpression) - { - return shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression); - } - if (node is SelectExpression se) - node = VisitSelect(se); - return base.VisitExtension(node); - } - private Expression VisitSelect(SelectExpression selectExpression) - { - 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(); - // Change SelectExpression - selectExpression = selectExpression.Update(selectExpression.Projection.ToList(), - selectExpression.Tables.ToList(), - selectExpression.Predicate, - selectExpression.GroupBy.ToList(), - selectExpression.Having, - orderings: newOrderings, - limit: null, - offset: null); - var rowOrderings = oldOrderings.Count != 0 ? oldOrderings - : new[] { new OrderingExpression(new SqlFragmentExpression("(SELECT 1)"), true) }; - - selectExpression.PushdownIntoSubquery(); + ) + : base(dependencies, relationalDependencies, queryCompilationContext) + {} - var subQuery = (SelectExpression)selectExpression.Tables[0]; - var projection = new RowNumberExpression(Array.Empty(), rowOrderings, oldOffset.TypeMapping); -#if !NET5_0 - var left = GenerateOuterColumnAccessor.Invoke(subQuery - , new object[] - { - Activator.CreateInstance(TableReferenceExpressionType, new object[] { subQuery,subQuery.Alias! })!, - projection, - "row", - true - }) as ColumnExpression; + public override Expression Process(Expression query) + { + query = base.Process(query); +#if NET9_0_OR_GREATER + query = new Offset2RowNumberConvertVisitor(query, RelationalDependencies.SqlExpressionFactory, RelationalQueryCompilationContext.SqlAliasManager).Visit(query); #else - var left = GenerateOuterColumnAccessor(subQuery, projection, "row"); + query = new Offset2RowNumberConvertVisitor(query, RelationalDependencies.SqlExpressionFactory).Visit(query); #endif - selectExpression.ApplyPredicate(sqlExpressionFactory.GreaterThan(left!, oldOffset)); - - if (oldLimit != null) - { - if (oldOrderings.Count == 0) - { - selectExpression.ApplyPredicate(sqlExpressionFactory.LessThanOrEqual(left, sqlExpressionFactory.Add(oldOffset, oldLimit))); - } - else - { - selectExpression.ApplyLimit(oldLimit); - } - } - return selectExpression; - } + return query; } } } diff --git a/README.md b/README.md index 7f61046..c2adcfe 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ [main-nuget]: https://www.nuget.org/packages/EntityFrameworkCore.UseRowNumberForPaging/ [main-nuget-badge]: https://img.shields.io/nuget/v/EntityFrameworkCore.UseRowNumberForPaging.svg?style=flat-square&label=nuget -Bring back support for [UseRowNumberForPaging](https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.infrastructure.sqlserverdbcontextoptionsbuilder.userownumberforpaging?view=efcore-3.0) in EntityFrameworkCore 8.0/7.0/6.0/5.0 +Bring back support for [UseRowNumberForPaging](https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.infrastructure.sqlserverdbcontextoptionsbuilder.userownumberforpaging?view=efcore-3.0) in EntityFrameworkCore 9.0/8.0 -If you are using EntityFrameworkCore 5.0 please use version 0.2 +If you are using EntityFrameworkCore 5.0 please use version 0.2. +If you are using EntityFrameworkCore 7.0 please use version 0.5. +If you are using EntityFrameworkCore 6.0 please use version 0.6. # Usage @@ -20,3 +22,4 @@ optionsBuilder.UseSqlServer("connection string", i => i.UseRowNumberForPaging()) * [@Megasware128](https://github.com/Megasware128) * [Simon Foster (@funkysi1701)](https://github.com/funkysi1701) +* [Clemens Lieb (@Vogel612)](https://github.com/Vogel612)