在 Repository<T> 中返回 Queryable<T> 或 List<T>
Posted
技术标签:
【中文标题】在 Repository<T> 中返回 Queryable<T> 或 List<T>【英文标题】:return Queryable<T> or List<T> in a Repository<T> 【发布时间】:2012-08-28 20:27:04 【问题描述】:目前我正在使用 sqlite 构建一个 Windows 应用程序。在数据库中有一个表说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驱动的一部分,几乎等同于EF中的IQueryable<T>
,提供查询,不会立即执行。但是现在的问题是:在UserManager.cs
中,它不知道TableQuery<T>
是什么,我需要在业务层项目中添加新的引用并导入像using SQLite.Query
这样的命名空间。它确实带来了糟糕的代码感觉。为什么我的业务层要知道数据库的细节?为什么业务层要知道什么是 SQLite?那么正确的设计是什么?
【问题讨论】:
你不能“只是”在业务层和数据访问层之间添加一个新的抽象层(接口)吗?是的,这将需要一组新的对象来表示业务层中的相同数据,以及这些对象与 DA 层中的对象之间的映射——在某种意义上,代码重复——但它应该让你在层之间有一个更清晰的划分,如果我理解你的情况。 我同意@Kjartan,你真正想要的是 IQueryableTableQuery<T>
不正确,IQueryable<T>
也不正确。即使我添加了一个新的传输接口,例如IQuery<T>
,它仍然基于 TableQuery<T>
,当我在我的业务层使用IQuery<T>
时,一切都是一样的。它只是看起来与 SQLite 无关,但实际上它戴着面具。
这里的 base on 对我来说并不完全清楚。它们的外观和工作方式可能相同,但如果您明智地通过接口进行分离,那么您的业务层不应该明确依赖 SQLite。它应该能够调用GetTheStuffIWant()
到DA 层,而不必知道它的实现(或者我错过了什么?)。另一方面,@DennisTraub 可能正确地推荐了一些在存储库中的加载策略,或者某种缓存,如果你不需要每次都调用数据库。
我认为最好的解决方法是改进 sqlite-net 以便 TableQuery 实现 IQueryable 以便您可以使用它。但也可能有一个原因,他们忽略了,只实现了 IEnumerable。
【参考方案1】:
延迟加载可能是 PITA,我宁愿尝试将加载策略注入存储库,而不是尝试在我的应用程序或业务层中摆弄查询对象。尤其是当查询对象迫使我将层紧密耦合时。
【讨论】:
“加载策略”是什么意思?【参考方案2】:通常,在干净的架构中,数据查询逻辑封装在存储库中。使用管道和过滤器可以帮助重用查询逻辑。用数据层/存储库中的方法包装这些将更具可读性和可维护性以及可重用性。
例如,用于查询用户的管道和过滤器:
/// 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)
。
编辑:基于 cmets,下面是编写搜索查询的场景,该查询检索居住在洛杉矶且至少拥有 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);
【讨论】:
你将把要求的逻辑放在哪里:“查询居住在纽约并且在系统中至少有两个银行账户的用户”。 这些查询是在 Repository 和 ofcoz 中编写的,您可以相应地重构。是的,也有一个限制,因为并非所有查询场景都可以通过 EF 中的单个 sql 查询来解析。在这种情况下,我们将不得不想出替代方法。【参考方案3】:我建议您使用IEnumerable<T>
而不是IQueryable<T>
,它也允许延迟加载。但是,IEnumerable
并不暗示您可以以任何 方式查询数据。您的 DB LINQ 提供程序可能会减少功能集。
【讨论】:
【参考方案4】:由于TableQuery<T>
实现IEnumerable<T>
,而不是IQueryable<T>
,因此最好的解决方案是简单地将存储库接口更改为返回IEnumerable<T>
而不是TableQuery<T>
。这不仅打破了 SqlLite 库的显式客户端依赖关系,而且使用 abstraction (IEnumerable<T>
) 而不是 implementation (@987654327) 也是一种更好的设计@) 在您的界面中。
您的示例方法应如下所示:
//Repository<User> class
public IEnumerable<User> List(where, orderby, topN parameters and etc)
//query and return
【讨论】:
以上是关于在 Repository<T> 中返回 Queryable<T> 或 List<T>的主要内容,如果未能解决你的问题,请参考以下文章