如何将数据从 LINQ to Entities 查询流式传输?
How can data be streamed from a LINQ to Entities query?
我想知道如何使用 EF6 从 SQL 服务器流式传输数据。
假设有这些 classes
- 个人资料库
- EFPerson(EF 模型)
- DomainPerson(领域模型)
- PersonUsingClass
假设 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
实例附加到更改跟踪器。
可以通过两种方式防止缓存实体。
- 只需使用
db.Person.AsNoTracking()
。
- 立即项目。投影创建 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
方法一样干。
立即投影的好处是只从数据库中拉取需要的字段,之后不会触发延迟加载。延迟加载可能是序列化问题或异常的根源,因为在触发延迟加载时会释放上下文。
我想知道如何使用 EF6 从 SQL 服务器流式传输数据。
假设有这些 classes
- 个人资料库
- EFPerson(EF 模型)
- DomainPerson(领域模型)
- PersonUsingClass
假设 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
实例附加到更改跟踪器。
可以通过两种方式防止缓存实体。
- 只需使用
db.Person.AsNoTracking()
。 - 立即项目。投影创建 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
方法一样干。
立即投影的好处是只从数据库中拉取需要的字段,之后不会触发延迟加载。延迟加载可能是序列化问题或异常的根源,因为在触发延迟加载时会释放上下文。