在 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&lt;User&gt; 和一个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&lt;User&gt; 带来了性能问题,因为查询执行得比预期的要早。现在我需要把它改成IQueryable&lt;T&gt;

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

    //query and return

TableQuery&lt;T&gt;是sqlite驱动的一部分,几乎等同于EF中的IQueryable&lt;T&gt;,提供查询,不会立即执行。但是现在的问题是:在UserManager.cs中,它不知道TableQuery&lt;T&gt;是什么,我需要在业务层项目中添加新的引用并导入像using SQLite.Query这样的命名空间。它确实带来了糟糕的代码感觉。为什么我的业务层要知道数据库的细节?为什么业务层要知道什么是 SQLite?那么正确的设计是什么?

【问题讨论】:

你不能“只是”在业务层和数据访问层之间添加一个新的抽象层(接口)吗?是的,这将需要一组新的对象来表示业务层中的相同数据,以及这些对象与 DA 层中的对象之间的映射——在某种意义上,代码重复——但它应该让你在层之间有一个更清晰的划分,如果我理解你的情况。 我同意@Kjartan,你真正想要的是 IQueryable,可惜 SQLite 没有,值得创建另一个层来转移到正确的类型。 @Kjartan @ivenxu:我没说TableQuery&lt;T&gt; 不正确,IQueryable&lt;T&gt; 也不正确。即使我添加了一个新的传输接口,例如IQuery&lt;T&gt;,它仍然基于 TableQuery&lt;T&gt;,当我在我的业务层使用IQuery&lt;T&gt; 时,一切都是一样的。它只是看起来与 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&lt;Account&gt; 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&lt;T&gt; 而不是IQueryable&lt;T&gt;,它也允许延迟加载。但是,IEnumerable 并不暗示您可以以任何 方式查询数据。您的 DB LINQ 提供程序可能会减少功能集。

【讨论】:

【参考方案4】:

由于TableQuery&lt;T&gt; 实现IEnumerable&lt;T&gt;,而不是IQueryable&lt;T&gt;,因此最好的解决方案是简单地将存储库接口更改为返回IEnumerable&lt;T&gt; 而不是TableQuery&lt;T&gt;。这不仅打破了 SqlLite 库的显式客户端依赖关系,而且使用 abstraction (IEnumerable&lt;T&gt;) 而不是 implementation (@987654327) 也是一种更好的设计@) 在您的界面中。

您的示例方法应如下所示:

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

    //query and return

【讨论】:

以上是关于在 Repository<T> 中返回 Queryable<T> 或 List<T>的主要内容,如果未能解决你的问题,请参考以下文章

仓储模式Repository的选择与设计

Repository接口

maven应用

maven应用

在 C#8 IAsyncEnumerable<T> 中并行化收益返回

在java中t.getClass为啥返回的是Class<gt;而不是Class<T>