如何将数据从 LINQ to Entities 查询流式传输?

How can data be streamed from a LINQ to Entities query?

我想知道如何使用 EF6 从 SQL 服务器流式传输数据。

假设有这些 classes

假设 PersonUsingClass 依赖于获取一堆 DomainPersons。 假设业务规则规定不允许 EFPersons 离开 PersonRepository。

通常我会有一个如下所示的存储库方法:

    public IEnumerable<DomainPerson> GetPeople()
    {
        using (var db = new efContext())
        {
            IQueryable efPeople = db.Person.Where(someCriteria);

            foreach (var person in efPeople)
            {
                yield return person.ToDomainPerson();
            }
        }
    }

使用我这里的代码,在执行 foreach 时所有内容都会加载到内存中。我可以通过将 IQueryable 返回到 PersonUsingClass 来实现流式传输,但这会将 EF 模型暴露给 class,这是一个不需要的场景。

难道真的不可能隐藏 EF 模型,为什么要同时流式传输数据?或者有什么我不知道的?

您创建的方法迭代由 EF 创建的 IQueryable<> 对象。

IQueryable<> 变量已延迟执行,因此在内部,EF 只会在迭代 IQueryable<> 时调用数据库(即首次调用 .MoveNext() 时) .

此外,如果您曾经使用 SqlDataReader 手动调用过数据库,您会发现可以 .Read() 一个一个地查询结果,您不会' 需要将所有记录加载到内存中。 EF 可能以这种方式流式传输记录(这是我的假设,可能取决于您的特定 EF 设置)。

您的方法正在返回一个 IEnumerable<> 对象,该对象也需要延迟执行。通过调用 GetPeople() 创建此实例不会导致数据库调用。

当您的方法的结果被 迭代 时,您将触发对内部 IQueryable<> 对象的迭代并一个一个地转换结果。

因此:

在该方法中没有记录被加载到内存中(除非 EF 在内部进行一些缓存)。如果您迭代该方法的结果,那么您将逐条迭代每条记录。

如果您对该方法的结果调用 .ToList().ToArray(),则记录将加载到内存中。

Entity Framework 查询过去是缓冲的,可以通过 AsStreaming 扩展方法进行流式处理。但流式处理长期以来一直是默认方法,扩展方法仍然存在但现在已过时(在 EF6 中)。就是一个。

但不要忘记 EF 的更改跟踪器。默认情况下,EF 缓存它在其更改跟踪器中具体化的所有实体,这是一个身份缓存。因此,即使查询是流式的,为了防止内存消耗,您也必须阻止 EF 跟踪实体。而这正是您的代码中缺少的。

foreach 循环的每次迭代都会将一个 Person 实例附加到更改跟踪器。

可以通过两种方式防止缓存实体。

  1. 只需使用db.Person.AsNoTracking()
  2. 立即项目。投影创建 EF 不跟踪的类型的对象。

第二种方法如下所示:

var people = db.Person.Where(someCriteria).Select(p => p.ToDomainPerson());

但是ToDomainPerson()当然不能翻译成SQL。相反,你应该这样做:

db.Person.Where(someCriteria).Select(p => new DomainPerson
{
    Name = p.Name,
    ...
}
);

或者,更好的是,使用 AutoMapper's ProjectTo 方法,它可以使您的代码与此 ToDomainPerson 方法一样干。

立即投影的好处是只从数据库中拉取需要的字段,之后不会触发延迟加载。延迟加载可能是序列化问题或异常的根源,因为在触发延迟加载时会释放上下文。