在存储库中返回Iqueryable <t>或List <t> <t> </t> </t> </t>

时间:2012-09-04 07:56:04

标签: c# .net architecture data-access-layer business-layer

目前我正在使用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?那么正确的设计是什么?

4 个答案:

答案 0 :(得分: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) { }

编辑:根据评论,以下是编写搜索查询的方案,该搜索查询检索在洛杉矶拥有至少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);
    }
}

答案 1 :(得分:2)

我建议您使用IEnumerable<T>而不是IQueryable<T>,这也允许延迟加载。但是,IEnumerable并不意味着您可以用任何方式查询数据。您的数据库LINQ提供程序可能具有缩减的功能集。

答案 2 :(得分:1)

由于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
}

答案 3 :(得分:0)

延迟加载可以是PITA,我宁愿尝试将加载策略注入存储库,而不是试图在我的应用程序或业务层中摆弄查询对象。特别是当查询对象强迫我紧密耦合图层时。