如何在不将查询结果存储在内存中的情况下实现存储库?

How to implement repository without storing query results in memory?

场景

我需要从数据库中读取超过 500 万个项目并一项一项地处理它们,而不必将所有集合存储在内存中。让我写一个过于简化的 C# 启发的伪代码来澄清(请注意问题是关于 LINQ、group by 和 count 等的用法)-

Lets say the table has the following fields - Id, Name, Age

IList<string> resultList = ...
IDataReader reader = command.executereader...
while(reader.Read()) //Read only one item at a time, no need to load everything
    if (AggregateFunction(resultList, reader.Name, reader.Age))
        resultList.Add(reader.Name);

问题 如果我使用 IDataReader,则不必将所有 500 万个项目都存储在内存中。我可以遍历它们,我的内存要求一次只是一行。

但是,如果我将存储库模式与 IEnumerable 等一起使用,那么在我处理它们之前,我将被迫将所有 500 万个项目存储在内存中。代码看起来像 -

IEnumerable<...> tableData = repository.GetAll() // Here we loaded everything in the memory
foreach(var row in tableData)
    //Do whatever...

我是否应该跳过 Repository 模式而只采用老式的方式?或者有没有一种方法可以在不将所有内容加载到内存的情况下获得存储库模式的好处?

注意:我想到的解决方案是创建一个 repository.GetAggregatedResult(函数聚合函数) 但这并不感觉更干净。另外,这里真正的问题是 - 如何在不将整个结果集存储在内存中的情况下一次迭代存储库中的一项

我不明白为什么你不能实现这样的方法:

public interface IPersonRepository
{
     IEnumerable<string> GetFilteredNames(Func<Person, bool> predicate);
}

此外,像这样的域对象:

public class Person
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public byte Age { get; set; } 
    // byte should be fine unless you would be 
    // working with turtles instead of persons ;)
}

...并使用原始 IDataReader 实现来实现它:

public IEnumerable<string> GetFilteredNames(Func<Person, bool> predicate)
{
    List<string> result = new List<string>();
    IDataReader dataReader = ... // Who knows how you get it!

    while(dataReader.Read()) 
    {
        Person person = new Person 
        {
            Id = (int)dataReader["Id"],
            Name = (string)dataReader["Name"],
            Age = (byte)dataReader["Age"]
        };

        if(predicate(person))
           result.Add(person.Name);
    }

    return result;    
}

如果你想让它完全不可知,你可以在存储库上使用依赖注入来注入一个 IDataReader 工厂!

现在您可以继续探索存储库模式的奇迹世界:

var result = repoImpl.GetFilteredNames(person => AggregateFunction(person.Id, person.Name, person.Age));