Вернуть 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, и я бы скорее попытался внедрить стратегию загрузки в хранилище, чем пытаться возиться с объектом запроса в моем приложении или бизнес-уровне. Особенно, когда объект запроса заставляет меня тесно соединять слои.