如何从LINQ到实体查询流式传输数据?

时间:2019-07-25 14:12:58

标签: c# entity-framework-6

我想知道如何使用EF6从SQL Server流数据。

假设有这些课程

  • PersonRepository
  • EFPerson(EF模式)
  • DomainPerson(域模型)
  • PersonUsingClass

假设PersonUsingClass依赖于获得一堆DomainPersons。 假设业务规则表明不允许EFPerson离开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模型暴露给该类,这是不希望的情况。

真的真的不可能隐藏EF模型,为什么同时传输数据吗?还是有我不知道的东西?

2 个答案:

答案 0 :(得分:1)

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

IQueryable<>变量已推迟执行,因此在内部,EF仅在迭代IQueryable<>时(即,首次调用.MoveNext()时)才对数据库进行调用。 / p>

此外,如果您曾经使用SqlDataReader手动滚动数据库调用,则会看到可以.Read()一对一地查询结果,但您不会需要将所有记录加载到内存中。 EF可能以这种方式对记录进行流式处理(这是我的假设,这可能取决于您的特定EF设置)。

您的方法将返回IEnumerable<>对象,该对象也将被推迟执行。通过调用GetPeople()创建此实例将不会导致数据库调用。

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

因此:

该方法不会将任何记录加载到内存中(除非EF在内部进行了一些缓存)。如果您遍历该方法的结果,那么您将遍历每个记录。

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

答案 1 :(得分:1)

实体框架查询曾经是缓冲的,可以通过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方法一样干燥。

立即进行投影的优点是,您仅从数据库中提取了必填字段,并且此后不会触发延迟加载。延迟加载可能是导致序列化问题或异常的原因,因为触发延迟加载时会处理上下文。