Entity Framework and OFFSET/FETCH on Sql Server

2020-03-30 03:11发布

问题:

I just encountered a weird bug in my app where a paged data grid contained duplicate rows.

Investigation showed that it's because the new paging mechanism Entity Framework uses with Sql Server 2012 as of version 6.1.2 only works on strictly ordered column sets, as is documented here.

Since most columns are not strictly ordered (meaning you have duplicate entries with respect to the sort order), simply binding one of the typical grid middlewares to Entity Framework is now broken subtly and horribly.

I can't believe I'm the first to complain, but I did google!

I found at least a dba.stackexchange question of a fellow wondering why Sql Server behaves this way.

Like the cynical old man I am, I already anticipated with a grin on my face what the fellow got as an answer: It's by design, what do you think?

Since I have little control over how middlewares create their linq, the only hope left is that Entity Framework has provided a way to fall back to the previous implementation of paging by using the ROW_NUMBER() OVER(ORDER BY ... feature, which always worked splendidly.

Is there? Any other ideas?

EDIT:

Here's an example of how a not strictly ordered column set leads to duplicates. (The linked question also has examples on SQL Fiddle but they don't seem to work right now.)

create table Foo(
  name varchar(10) not null primary key,
  value varchar(10) not null
  );

insert into Foo values ('1', ''), ('2', ''), ('3', ''), ('4', '')

select * from Foo order by value offset 0 rows fetch next 2 rows only;
select * from Foo order by value offset 2 rows fetch next 2 rows only;

This gives for me:

name    value
3   
2   

name    value
2   
1   

As you can see, they overlap. The reason is that the value column itself is always empty and thus not strictly ordered.

The previous way Entity Framework implemented paging used a the ROW_NUMBER feature like this:

select * from (select ROW_NUMBER() over(order by value) as rownumber, * from Foo) as squery where squery.rownumber between 1 and 2;
select * from (select ROW_NUMBER() over(order by value) as rownumber, * from Foo) as squery where squery.rownumber between 3 and 4;

These queries indeed give disjoint results.