Чтобы вернуть IQueryable <T> или не вернуть IQueryable <T>

У меня есть класс репозитория, который переносит мой LINQ в SQL Data Context. Класс репозитория - это класс бизнес-линии, который содержит всю логику уровня данных (и кэширование и т.д.).

Здесь мой v1 моего интерфейса репо.

public interface ILocationRepository
{
    IList<Location> FindAll();
    IList<Location> FindForState(State state);
    IList<Location> FindForPostCode(string postCode);
}

Но для обработки пейджинга для FindAll я обсуждаю, следует ли выставлять IQueryable <ILocation> вместо IList, чтобы упростить интерфейс для таких ситуаций, как пейджинг.

Каковы плюсы и минусы для раскрытия IQueryable из репо данных?

Любая помощь очень ценится.

Ответ 1

Профи; компонуемости:

  • абоненты могут добавлять фильтры
  • вызывающие могут добавить пейджинг
  • вызывающие могут добавлять сортировку
  • и т.д.

Против; без проверяемость:

  • Ваш репозиторий более не поддается тестированию; вы не можете полагаться на: он работает, b: что он делает;
    • вызывающий может добавить непереводимую функцию (т.е. не отображать TSQL, перерывы во время выполнения)
    • вызывающий может добавить фильтр/сортировку, которая заставляет его выполнять как собаку
  • Поскольку вызывающие абоненты ожидают, что IQueryable<T> будет составным, он исключит неконсолидируемые реализации - или он заставит вас написать свой собственный поставщик запросов для них.
  • это означает, что вы не можете оптимизировать/профиль DAL

Для стабильности я перешел на не отображение IQueryable<T> или Expression<...> в моих репозиториях. Это означает, что я знаю, как работает репозиторий, а мои верхние уровни могут использовать mocks, не беспокоясь о том, поддерживает ли реальный репозиторий это? (принудительные интеграционные тесты).

Я все еще использую IQueryable<T> и т.д. внутри репозитория - но не над границей. Я добавил несколько больше мыслей по этой теме здесь. Также легко задать параметры поискового вызова в интерфейсе репозитория. Вы можете даже использовать методы расширения (на интерфейсе), чтобы добавить необязательные параметры поискового вызова, чтобы конкретные классы имели только один способ реализации, но может быть 2 или 3 перегрузки, доступные для вызывающего.

Ответ 2

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

Инкапсуляция бизнес-логики в первую очередь заключается в поддержании целостности вашей базы данных.

Вы можете продолжить публикацию IList и изменить свои параметры следующим образом: вот как мы это делаем...

public interface ILocationRepository
{
    IList<Location> FindAll(int start, int size);
    IList<Location> FindForState(State state, int start, int size);
    IList<Location> FindForPostCode(string postCode, int start, int size);
}

if size == -1, а затем вернуть все...

Альтернативный способ...

Если вы все еще хотите вернуть IQueryable, вы можете вернуть IQueryable из списка внутри своих функций.. например...

public class MyRepository
{
    IQueryable<Location> FindAll()
    {
        List<Location> myLocations = ....;
        return myLocations.AsQueryable<Location>;
        // here Query can only be applied on this
        // subset, not directly to the database
    }
}

Первый метод имеет преимущество перед памятью, потому что вы будете возвращать меньше данных вместо всех.

Ответ 3

Я рекомендую использовать IEnumerable вместо IList, при этом у вас будет больше гибкости.

Таким образом, вы сможете получить от Db только ту часть данных, которую вы действительно собираетесь использовать без дополнительной работы в вашем репозитории.

Пример:

// Repository
public interface IRepository
{
    IEnumerable<Location> GetLocations();
}

// Controller
public ActionResult Locations(int? page)
{
    return View(repository.GetLocations().AsPagination(page ?? 1, 10);
}

Это супер чистое и простое.