Как получить общее количество доступных строк из основного запроса ef с разбивкой на страницы

Заранее спасибо, что нашли время прочитать этот вопрос.

У меня есть представление в моей базе данных, назовем его Members_VW

В моем API .net 5 я пытаюсь получить ответ с разбивкой на страницы для списка участников из представления с параметрами поиска. Мне также нужно вернуть общее количество ответов, чтобы внешний интерфейс знал, на скольких страницах будут возвращены результаты.

В настоящее время Members_VW выполняется с запросом вроде:

select
    col1, col2, col3
from
    table1 1
    inner join table2 2 on 1.key = 2.key
    inner join tble3 3 on 3.key = 2.key
where
    defaultcondition1 = '1'
        and
    defaultcondition2 = '2'

Я сослался на этот ответ и попытался использовать CTE, который закончилось тем, что я изменил свое мнение на использование такого запроса:

with cte1 as (
select
    col1, col2, col3
from
    table1 1
    inner join table2 2 on 1.key = 2.key
    inner join tble3 3 on 3.key = 2.key
where
    defaultcondition1 = '1'
        and
    defaultcondition2 = '2')
cte2 as (
select count(*) over() from cte1 )
select
    *
from
    cte1, cte2

Но это не сработало, потому что всегда возвращалось общее количество строк в cte1 без применения каких-либо фильтров.

Итак, я продолжил попытки построить запросы для возврата общего количества строк после применения условий и обнаружил, что этот запрос работает:

select
    col1, col2, col3, count(*) over()
from
    table1 1
    inner join table2 2 on 1.key = 2.key
    inner join tble3 3 on 3.key = 2.key
where
    defaultcondition1 = '1'
        and
    defaultcondition2 = '2'

В настоящее время я пытаюсь реализовать тот же запрос с помощью EF Core, но не могу это реализовать.

Я попытался реализовать решение, предоставленное здесь, но, как предполагает один из комментариев, эта реализация больше не разрешена.

Я пытаюсь избежать реализации, в которой я использую необработанный запрос. Есть ли способ получить результат от count(*) over() без использования необработанного запроса?

Ниже приведена моя текущая реализация:

IQueryable<MembersVW> membersQuery = _context.MembersVW;
membersQuery = membersQuery.Where(u => u.MemberId == memberid);
membersQuery = membersQuery.OrderBy(m => m.MemberId).Skip(page * size).Take(size);

Когда я делаю: membersQuery = membersQuery.Count() я возвращаюсь со следующей ошибкой:

Error   CS0029  Cannot implicitly convert type 'int' to 'System.Linq.IQueryable<PersonalPolicyAPI.Models.VwPersonalPolicyMember>'

Еще раз, спасибо за чтение моего вопроса, ценим любую помощь, которую вы можете предложить. ????????


person Sathya    schedule 20.07.2021    source источник
comment
Что произойдет, если вы запустите запрос подсчета прямо перед этой строкой membersQuery = membersQuery.OrderBy(m => m.MemberId).Skip(page * size).Take(size);   -  person Roar S.    schedule 20.07.2021
comment
@RoarS. без подсчета он работает и возвращает все результаты. Но это не то, что мне нужно. Мне нужно иметь возможность разбивать запрос на страницы, чтобы оптимизировать время ответа на запрос.   -  person Sathya    schedule 20.07.2021
comment
Вместо membersQuery = membersQuery.Count() используйте int count= membersQuery.Count().   -  person Hassan Monjezi    schedule 28.07.2021


Ответы (4)


membersQuery.Count() возвращает целое число, а не запрашиваемое

ты можешь сделать

     int count = membersQuery.Count();
     List<MemberVW> = membersQuery.OrderBy(m => m.MemberId).Skip(page * size).Take(size).ToList();

и вы можете вернуться с

       public class MemberVwWithCount {
           public int Count{get;set;}
           public List<MemberVW> Members {get; set;}
         }
person a_k    schedule 20.07.2021
comment
Спасибо @a_k за ваш ответ. Я попробовал вашу реализацию. Это сработало. Однако он выполняет 2 разных запроса. Я пытаюсь сделать это с помощью всего одного запроса. Разве вы не думаете, что это невозможно сделать с помощью всего одного запроса? - person Sathya; 21.07.2021
comment
Это можно сделать в одном запросе, но это будет стоить с точки зрения производительности. Поэтому я думаю, что текущий подход, предложенный @a_k, целесообразен. - person Manthan Makani; 24.07.2021

Вы пытаетесь присвоить значение счетчика, которое является целым числом, переменной вашего запроса, которая является IQueryable. Вот и все.

Если вы хотите сделать это в одном запросе, как вы предлагаете в одном из своих комментариев, вы можете сначала выполнить запрос, чтобы получить все записи, затем подсчитать результат, а затем отфильтровать результат с помощью skip/take. Скорее всего, это не самый эффективный способ сделать это, но он должен работать.

Я бы также предложил использовать AsNoTracking(), если вы не изменяете данные в этой функции/api.

РЕДАКТИРОВАТЬ: я бы предложил это решение на данный момент. Подсчет выполняется быстро, так как на самом деле не извлекается никаких данных, а просто подсчитываются строки. Это все еще два запроса, но я попытаюсь объединить их и отредактировать свой ответ позже.

 var count = await yourContext.YourTable.CountAsync();
 var data = await yourContext.YourTable
    .OrderBy(x => x.YourProp)
    .Skip(10).Take(10)
    //.AsNoTracking()
    .ToListAsync();

EDIT2: Итак, я пока не мог заставить его просто сделать DB-Call, однако я мог бы объединить его синтаксически. Тем не менее, подход в моем первом редактировании легче читать, и в основном он работает так же. Тем не менее, если копнуть поглубже, должен быть причудливый способ сделать это.

var query = yourContext.YourTable.AsQueryable();
var result =  await query.OrderBy(x => x.Prop)
                .Select(x => new {Data = x, Count = query.Count()} )
                .Skip(50).Take(50)
                .AsNoTracking()
                .ToListAsync();
var count = result.FirstOrDefault()?.Count ?? 0; //If empty/null return 0
var data = result.Select(x => x.Data).ToList();

person Sebastian Börgers    schedule 23.07.2021
comment
Спасибо за ответ, Себастьян! Надеюсь, вы столкнетесь с этим фанковым способом - person Sathya; 26.07.2021

В строке membersQuery = membersQuery.Count() вы присваиваете запрашиваемому списку целочисленное значение, что неверно. Вы можете получить количество элементов списка после вашего запроса, как это, т.е.

membersQuery = membersQuery.OrderBy(m => m.MemberId).Skip(page * size).Take(size);
int totalCount = membersQuery.Count();

Чтобы получить столбец count в том же списке, сначала нужно добавить свойство Count в свой класс MembersVW, а затем использовать проекцию LINQ для добавления значения столбца.

Решение 1.

memberQuery = membersQuery.Select(p => new MembersVW 
              {
                  col1 = p.col1
                  col2 = p.col2
                  col3 = p.col3
                  count = totalCount
              });

Решение 2.

С циклом foreach LINQ, т.е.

membersQuery.ForEach(item => 
              {
                  item.count = totalCount;
              });
person asmak9    schedule 26.07.2021
comment
спасибо за Ваш ответ! Ты прав. Но он по-прежнему генерирует 2 отдельных запроса. Я ищу возможное решение, которое возвращает результат со столбцом с общим количеством. - person Sathya; 27.07.2021
comment
Вы можете добиться этого, используя проекцию запроса, как указано выше, но вам также необходимо добавить свойство Count в свой класс MembersVW. Я отредактировал свой ответ. - person asmak9; 28.07.2021

Я прочитал ваш вопрос о том, можно ли это сделать с помощью одного запроса. Хотя я не знаю, как это сделать с помощью 1 запроса, я могу предложить еще одно решение, которое поможет решить вашу проблему с производительностью и двумя запросами. Я делаю это часто. ???? Попробуйте:

//execute both queries at the same time instead of sequentially
var countqry = membersQuery.CountAsync();
var pageqry = membersQuery.OrderBy(m => m.MemberId).Skip(page * size).Take(size).ToListAsync();

//wait for them both to complete
Task.WaitAll(countqry, pageqry);

//use the results
var count = countqry.Result;
var page = pageqry.Result;
person Jon    schedule 28.07.2021