仓库是否应该实现IQueryable<T>?

27
我正在考虑两个IRepository接口中的一个,一个是IQueryable的后代,另一个包含IQueryable。
就像这样:
public interface IRepository<T> : IQueryable<T>
{
    T Save(T entity);
    void Delete(T entity);
}

或者这个:
public interface IRepository<T>
{
    T Save(T entity);
    void Delete(T entity);
    IQueryable<T> Query();
}

LINQ的使用方法如下:
from dos
in ServiceLocator.Current.GetInstance<IRepository<DomainObject>>()
where dos.Id == id
select dos

或者...
from dos
in ServiceLocator.Current.GetInstance<IRepository<DomainObject>>().Query
where dos.Id == id
select dos

我有点喜欢第一个,但是模拟起来有问题。其他人是如何实现可LINQ,可模拟的存储库的?


只是确认一下?如果你在 Query() 中返回 所有 实体,然后在使用 LINQ 进行搜索的消费代码中,它会进行延迟加载,是吗?它不会实际加载成千上万条记录,然后在消费代码中搜索。 - gideon
3个回答

14

取决于您想要 Has-A 还是 Is-A 关系。

第一个是 Is-A 关系。IRepository 接口是 IQueryable 接口。第二个是 has-a。IRepository 有一个 IQueryable 接口。在编写过程中,我实际上更喜欢第二个,因为当使用您的第二个 IRepository 时,我可以给 Query() 方法任何返回 IQueryable 的东西。对我来说,这比第一个实现更灵活。


这就是我最终做的事情...使用 Moq 来模拟示例 1 太痛苦了。 - Andy S
相关问题,我正在使用Moq来模拟一个类似于示例2的存储库,但我的查询包含在“select new { ... }”中的映射逻辑从未得到测试,因为执行被推迟到IRepository.Save(),而这个方法被模拟并且从不执行映射。有什么想法吗? - flipdoubt

9
个人而言,我使用“仓储模式”将存储库中的所有项目作为“IQueryable”返回。通过这样做,我的存储库层现在非常轻巧、小巧。服务层(消耗存储库层)现在可以开放给所有类型的查询操作。
基本上,现在所有的逻辑都位于服务层(不知道它将使用什么类型的存储库,也不想知道 <-- 关注点分离)。而我的存储库层只是处理从 Repo 获取数据和将数据保存到 Repo(SQL Server,文件,太空卫星等 <-- 更多关注点分离)。
例如,更多或更少的伪代码,因为我在记忆我们在代码中所做的事情,并简化了这个答案...
public interface IRepository<T>
{
    IQueryable<T> Find();
    void Save(T entity);
    void Delete(T entity);
}

并且需要拥有一个用户存储库...

public class UserRepository : IRepository<User>
{
    public IQueryable<User> Find()
    {
        // Context is some Entity Framework context or 
        // Linq-to-Sql or NHib or an Xml file, etc...
        // I didn't bother adding this, to this example code.
        return context.Users().AsQueryable();
    }

    // ... etc
}

现在是最棒的部分 :)

public void UserServices : IUserServices
{
    private readonly IRepository<User> _userRepository;

    public UserServices(IRepository<User> userRepository)
    {
        _userRepository = userRepository;
    }

    public User FindById(int userId)
    {
        return _userRepository.Find()
            .WithUserId(userId)
            .SingleOrDefault();  // <-- This will be null, if the 
                                 //     user doesn't exist
                                 //     in the repository.
    }

    // Note: some people might not want the FindBySingle method because this
    //       uber method can do that, also. But i wanted to show u the power
    //       of having the Repository return an IQuerable.
    public User FindSingle(Expression<Func<User, bool>> predicate)
    {
        return _userRepository
            .Find()
            .SingleOrDefault(predicate);
    }
}

加分项:在FindById方法中,WithUserId(userId)是什么意思?这是一个管道和过滤器。使用它们 :) 爱它们 :) 拥抱它们 :) 它们使你的代码更易读 :) 现在,如果你想知道它是做什么的... 这是扩展方法。

public static User WithId(this IQueryable<User> source, int userId)
{
    return source.Where(u => u.UserId == userId).SingleOrDefault();
}

尽管这个问题已经快两年了,但是HTH仍然有用 :)


1
我喜欢有关管道和过滤器的提示。 - makerofthings7

0

你可以随时快速编写针对List的代码,这不是使用模拟框架进行嘲弄,但它确实非常有效。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接