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

C# 如何将数据从LINQ流式传输到实体查询?,c#,entity-framework-6,C#,Entity Framework 6,我想知道如何使用EF6从SQL server进行数据流传输 假设有这些类 个人知识库 EFPerson(EF模型) DomainPerson(域模型) 人格阶层 假设PersonUsingClass依赖于获取一组DomainPersons。 假设业务规则规定不允许EFPerson离开PersonRepository 通常,我会有一个如下所示的存储库方法: public IEnumerable<DomainPerson> GetPeople() {

我想知道如何使用EF6从SQL server进行数据流传输

假设有这些类

  • 个人知识库
  • EFPerson(EF模型)
  • DomainPerson(域模型)
  • 人格阶层
假设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();
            }
        }
    }
public IEnumerable GetPeople()
{
使用(var db=new efContext())
{
IQueryable efPeople=db.Person.Where(someCriteria);
foreach(efPeople中的var person)
{
让出返回人。ToDomainPerson();
}
}
}
使用我这里的代码,在执行foreach时,所有内容都将加载到内存中。我可以通过将IQueryable返回给PersonUsingClass来实现流化,但这会将EF模型暴露给该类,这是一个不需要的场景


是否真的不可能同时隐藏EF模型和流式数据?或者有什么我不知道的吗?

您创建的方法会迭代EF创建的
IQueryable
对象

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

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

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

当方法的结果在上迭代时,您将触发对内部
IQueryable
对象的迭代,并逐个转换结果

因此:


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


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

实体框架查询用于缓冲,并可通过
关联扩展方法进行流式处理。但是流式传输一直是默认的,扩展方法仍然存在,但现在已经过时了(在EF6中)。这是一个

但别忘了英孚的变化跟踪系统。默认情况下,EF在其变更跟踪程序(即身份缓存)中缓存其具体化的所有实体。因此,即使查询是流式的,为了防止内存消耗,也必须防止EF跟踪实体。这正是您的代码中缺少的内容

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

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

  • 只需使用
    db.Person.AsNoTracking()
  • 立即计划。投影创建EF不跟踪的类型的对象
  • 第二种方法如下所示:

    var people = db.Person.Where(someCriteria).Select(p => p.ToDomainPerson());
    
    当然,
    ToDomainPerson()
    不能翻译成SQL。相反,您应该做如下操作:

    db.Person.Where(someCriteria)。选择(p=>newdomainPerson
    {
    名称=p.名称,
    ...
    }
    );
    
    或者,更好的方法是使用这个方法,它可以让您的代码像这个
    ToDomainPerson
    方法一样保持干燥


    立即投影的优点是,您只需从数据库中提取所需的字段,之后不会触发延迟加载。延迟加载可能是序列化问题或异常的来源,因为触发延迟加载时会释放上下文。

    在该方法中没有记录加载到内存中(除非EF在内部进行缓存)。如果您对该方法的结果进行
    foreach
    ,那么您将逐个迭代每个记录。如果对该方法的结果调用.
    ToList()、ToArray()
    ,则记录将加载到内存中。太棒了!一位团队成员让我确信我错了,因为我们可以用这种方式传输数据。您可以在EF online的文档中找到这样的信息:“LINQ查询总是在迭代查询变量时执行,而不是在创建查询变量时执行”。因此,最后我们计算出,一旦foreach被命中,所有内容都将被加载到内存中,然后产生的返回将不会产生任何有意义的影响,除了推迟映射“到域内人”。请创建您的评论作为答案,然后我可以接受。除非EF在内部进行一些缓存——好吧,它是这样做的,并且有两种方法可以防止这种情况发生(参见另一个答案)。@GertArnold您完全正确。我对这个问题的解释导致了我对迭代方面的关注。我把EF缓存这个主题留得很模糊,因为有很多变量在起作用,这个主题甚至可能需要一个额外的问题。例如,可以在上下文级别禁用跟踪,您不必使用
    .AsNoTracking()
    。但是,只能在ef core中在上下文级别禁用跟踪。