Используйте IQueryable.Count <T> с параметром IEnumerable <T>

Представьте себе класс, допустим, для разбивки на страницы, который можно использовать с IList<T> или IQueryable<T>.

Этот класс будет иметь свойство int TotalItems, которое (не удивительно) получит/задает счетчик запрашиваемого или перечислимого параметра.

Если я использую IEnumerable<T> как параметр,

//simplified
public Pagination(IEnumerable<T> query)
    {
        TotalItems = query.Count();
    }

метод Count() будет (, если я не ошибаюсь, что точка) Enumerable.Count(). Таким образом, даже если запрос является IQueryable<T> (который наследуется от IEnumerable<T>), он будет перечисляться (что явно не желательно с "запросом db" ).

Итак, есть способ использовать метод Queryable.Count(), когда мой IEnumerable<T> на самом деле является IQueryable<T>, или мне нужно изменить свой дизайн, имея, например, в этом случае 2 ctor

//simplified
public Pagination(IEnumerable<T> query)
    {
         TotalItems = query.Count();
    }
public Pagination(IQueryable<T> query)
    {
         TotalItems = query.Count();
    }

ИЗМЕНИТЬ Я понимаю, что IQueryable<T>, наследующий от IEnumerable<T>, не имеет ничего общего с тем, что IEnumerable<T> и IQueryable<T> имеют методы расширения с одинаковым именем, и что это хорошо, чтобы иметь одинаковые имена для метода расширения, которые выглядят они делают то же самое ", но я думаю, что это все еще иногда путается...

Общий вопрос для любопытства

Являются ли они другими примерами в рамках с той же "архитектурой": наследование + общие имена для методов расширения?

Ответы

Ответ 1

Вы должны иметь две перегрузки конструктора, как вы показали в вопросе. Это приводит к тому, что они хотят использовать методы IQueryable или методы IEnumerable, которые будут использоваться.

Если кто-то должен был сделать:

Pagination pager = new Pagination(query.AsEnumerable());

Затем они явно хотят, чтобы объект обрабатывался как IEnumearble, а не IQueryable. Возможно, они знают, что Skip и Take не выполняются поставщиком запросов, поэтому разбиение на страницы не будет выполнено, и его необходимо оценить как Linq-to-objects.

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

Ответ 2

Ну, вы могли бы сделать:

TotalItems = enumerable.AsQueryable().Count();

Это будет напрямую использовать реализацию query-provider Count для собственного запроса или возврата к LINQ to Objects (с некоторыми служебными данными для использования EnumerableQuery) в противном случае.

Другое решение (потенциально более эффективное):

var queryable = enumerable as IQueryable<T>;
TotalItems = queryable != null ? queryable.Count() : enumerable.Count();
Обратите внимание, что если ваш запрос реализует ICollection или ICollection<T> эффективно (как в случае с некоторыми поставщиками запросов), даже ваше существующее решение потенциально может работать хорошо из-за оптимизации в LINQ (он использует Count свойство из этих интерфейсов, где это возможно). Для вашего IList<T> он всегда будет использовать эту оптимизацию, так как она реализует ICollection<T>.