Вернуть Queryable<T> или List<T> в хранилище<T>

В настоящее время я создаю приложение для Windows, используя sqlite. В базе данных есть таблица скажем Userи в моем коде есть Repository<User> и UserManager, Я думаю, что это очень распространенный дизайн. В хранилище есть List метод:

//Repository<User> class
public List<User> List(where, orderby, topN parameters and etc)
{
    //query and return
}

Это приносит проблему, если я хочу сделать что-то сложное в UserManager.cs:

//UserManager.cs
public List<User> ListUsersWithBankAccounts()
{
    var userRep = new UserRepository();
    var bankRep = new BankAccountRepository();
    var result = //do something complex, say "I want the users live in NY 
                 //and have at least two bank accounts in the system
}

Вы можете увидеть, возвращаясь List<User> приносит проблемы с производительностью, потому что запрос выполняется раньше, чем ожидалось. Теперь мне нужно изменить его на что-то вроде IQueryable<T>:

//Repository<User> class
public TableQuery<User> List(where, orderby, topN parameters and etc)
{
    //query and return
}

TableQuery<T> является частью драйвера sqlite, который почти равен IQueryable<T> в EF, который предоставляет запрос и не выполнит его немедленно. Но сейчас проблема в том, UserManager.csне знает, что такое TableQuery<T>Мне нужно добавить новые ссылки и импортировать пространства имен, такие как using SQLite.Query в проекте бизнес-уровня. Это действительно приносит плохое чувство кода. Почему мой бизнес-уровень должен знать детали базы данных? почему бизнес-уровень должен знать, что такое SQLite? Каков правильный дизайн тогда?

4 ответа

Решение

Я бы порекомендовал вам использовать IEnumerable<T> скорее, чем IQueryable<T>, что позволяет ленивую загрузку тоже. IEnumerable Однако это не означает, что вы можете запрашивать данные любым способом. Ваш поставщик данных LINQ, вероятно, будет иметь ограниченный набор функций.

Как правило, в чистой архитектуре логика запросов данных инкапсулируется в репозитории. Использование каналов и фильтров может помочь в повторном использовании логики запросов. Объединение их с методами в уровне данных / репозиториях будет более читабельным и поддерживаемым, а также может использоваться повторно.

Например, Трубы и фильтры для запросов пользователей:

/// Pipes/Filters for user queries.
public static class UserExtensions
{
    public static IQueryable<User> Active(this IQueryable<User> query)
    {
        return query.Where(user => user.Active == true);
    }
}

public class UserRepository : IRepository<User>, IUserRepository
{
    /// Retrieve all users
    public List<User> List()
    {
        // Logic to query all users in the database.
    }
    public List<User> ListActive()
    {
        // Logic to query all active users in the database.
        return context.Users.Active().ToList();
    }
}

Сложные запросы требуют понимания его цели и обязанностей, чтобы абстрагировать логику запроса от его репозиториев. Например, "Получить все учетные записи принадлежит этому пользователю" можно записать в AccountRepository класс как List<Account> ListForUser(int userId) { },

Изменить: на основе комментариев, вот сценарии для написания поискового запроса, который извлекает пользователей живет в Лос-Анджелесе, которые имеют по крайней мере 2 учетных записи.

public class UserRepository : IRepository<User>, IUserRepository
{
    // other queries.

    public List<User> List(ISearchQuery query)
    {
        // Logic to query all active users in the database.
        return context.Users.Active().LivesIn(query.Country).WithAccounts(query.AccountsAtLeast).ToList();
    }
}

public static class UserExtensions
{
    // other pipes and filters.

    public static IQueryable<User> LivesIn(this IQueryable<User> query, string country)
    {
        return query.Where(user => user.Country.Name == country);
    }
    public static IQueryable<User> WithAccounts(this IQueryable<User> query, int count)
    {
        return query.Where(user => user.Accounts.Count() >= count);
    }
}

Поскольку TableQuery<T> инвентарь IEnumerable<T>, и не IQueryable<T>Лучшее решение было бы просто изменить интерфейс хранилища, чтобы вернуться IEnumerable<T> вместо TableQuery<T>, Это не только нарушает явную клиентскую зависимость с вашей библиотекой SqlLite, но также лучше использовать абстракцию (IEnumerable<T>) вместо реализации (TableQuery<T>) в ваших интерфейсах.

Ваш пример метода должен выглядеть следующим образом:

//Repository<User> class
public IEnumerable<User> List(where, orderby, topN parameters and etc)
{
    //query and return
}

Ленивая загрузка может быть PITA, и я бы скорее попытался внедрить стратегию загрузки в хранилище, чем пытаться возиться с объектом запроса в моем приложении или бизнес-уровне. Особенно, когда объект запроса заставляет меня тесно соединять слои.

Другие вопросы по тегам