我想知道如何使用EF6从SQL Server流数据。
假设有这些课程
假设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模型,为什么同时传输数据吗?还是有我不知道的东西?
答案 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
实例附加到变更跟踪器。
可以通过两种方式防止缓存实体。
db.Person.AsNoTracking()
。第二种方法如下:
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
方法一样干燥。
立即进行投影的优点是,您仅从数据库中提取了必填字段,并且此后不会触发延迟加载。延迟加载可能是导致序列化问题或异常的原因,因为触发延迟加载时会处理上下文。