【问题标题】:Number of results in DbSet<TEntity>DbSet<TEntity> 中的结果数
【发布时间】:2019-04-29 23:46:04
【问题描述】:

我正在浏览一些关于 DBEntities 和 DbContext 的示例代码。 DbSet 从数据库中提取的行数是否有限制?在下面的代码示例中,假设有一个 DbSet&lt;History&gt; historyDbSet&lt;Logs&gt; logs,当创建 dbcontext 时,dbcontext.logsdbcontext.history 是否将所有日志都存在于数据库中?如果是这样,如果表有数百万行怎么办。在 linq 或任何更新和保存上下文期间它不会影响性能吗?

public virtual DbSet<Course> Courses { get; set; }
public virtual DbSet<Standard> Standards { get; set; }
public virtual DbSet<Student> Students { get; set; }
public virtual DbSet<StudentAddress> StudentAddresses { get; set; }
using (var context = await _contextFactory.CreateContext())
{
     context.History.Add(history);
     context.SaveChanges();
}

【问题讨论】:

标签: c# entity-framework linq dbcontext dbset


【解决方案1】:

实体框架不需要拉任何行来执行插入操作,这正是 Add() 方法和 SaveChanged() 所做的。它应该像您在 SQL 中所做的那样向相关表中添加一行。

【讨论】:

  • Add() 或 Select() 我在创建上下文时观察到 context.History 具有我认为不必要的数据库中的所有行(预计数百万行)。我可以在创建上下文时限制它提取的行数吗?
  • 这是一个“海森堡效应”:如果您查看它,您会在执行查询时枚举它(扩展 IEnumerable)。不要看它(或使用分析器),它会表现得很好。使用 linq 的 Where 语句进行过滤。
  • 在创建上下文时执行的唯一查询(更准确地说:当为上下文初始化程序运行该上下文类型的模型构建器时)是检查迁移历史表以查看是否数据库架构是正确的。除非您枚举从 DbContext 的 DbSet 属性公开或创建的 IQueryable,否则不会从数据库加载任何数据。
【解决方案2】:

在你的例子中,它不会“爆炸”

以下行基本上只是将一个项目添加到一个空的更改跟踪器:

 context.History.Add(history);

如果你会执行

context.History.ToList()

然后查询作为“select * from History”执行,如果它包含数百万行,您肯定会遇到性能问题。

关键是 EF “足够聪明”,不会将内存中的所有内容作为一个整体加载。您可以附加探查器(或启用 EF 日志记录)以查看正在执行的实际查询。摆弄一下它以获得一些经验。

如果您扩展集合,例如使用调试器,那么基本上您不会应用任何过滤器,而是会检索整个集合。如果滥用导航属性,您甚至可以将整个数据库加载到内存中。

字幕差异在IQueryable与其他IEnumerable类界面的差异之内。

虽然对象仍然只有IQueryable,但实际查询仍将被执行,并且可以使用过滤器进行扩展。就像我说的;一旦开始枚举,就会执行实际查询,因此,未过滤的 dbset 将返回表中的所有行。


还要注意提到的 linq 方法

.跳过

还有

.拍

还有几个,比如group、join、where等

【讨论】:

  • ^ 及以上评论有帮助。我将尝试使用分析器来查看正在执行的查询。谢谢
  • 欢迎您,如果您有任何问题,请随时回来。享受吧!
【解决方案3】:

您必须意识到DbSet&lt;Student&gt; 并不代表您的Students 集合,它代表您数据库中的Students 表。这意味着您可以查询Students 的属性序列。

如果需要,您可以查询完整的序列,但这会导致性能问题,如果不是内存问题。

因此,如果您要求Student 数据,您必须牢记您将使用获取的数据的用途:不要选择您已经知道其值的属性,不要选择您知道的项目不打算使用。

一个例子:一个具有SchoolsStudents的数据库,具有一对多的关系,每个School有零个或多个Students,每个Student恰好参与一个School

class School
{
     public int Id {get; set;}
     public string Name {get; set;}
     ...

     // every School has zero or more Students (one-to-many)
     public virtual ICollection<Student> Students {get; set;}
}

class Student
{
     public int Id {get; set;}
     public string Name {get; set;}
     ...

     // Every Student attends exactly one School, using foreign key:
     public int SchoolId {get; set;}
     public virtual School School {get; set;}
}

在实体框架中,表的列由非虚拟属性表示。虚拟属性表示表之间的关系(一对多,多对多,...)

请勿执行以下操作!

public IEnumerable<School> GetSchoolByLocation(string city)
{
    return mySchoolWithItsStudents = dbContext.Schools
        .Where(school => school.City == city)
        .Include(school => school.Students)
        .ToList();
}

为什么不呢?这看起来像是完美的代码,不是吗?

也许您获取的数据比调用者使用的数据多:

var mySchoolId = GetSchoolByLocation("Oxford")
    .Where(school => schoolStreet == "Main Street")
    .Select(school => school.Id)
    .FirstOrDefault();

太浪费了,先把牛津所有的学校都拿来,然后只保留这一个!

此外:您获得了学校及其所有学生,以及您使用的所有学校 ID?

尽量返回IQueryable&lt;...&gt;,让调用者决定如何处理返回的数据。

也许他想做ToList,或Count,或FirstOrDefault。也许他只想要IdName。只要你不知道,就不要替他做决定,只会让你的代码更难复用。

始终使用Select 选择属性,并且只选择您实际计划使用的数据。如果您打算更新包含的数据,请仅使用Include

var schools = dbContext.Schools.Where(school => ...)
    // Keep only the Schools that you actually plan to use:
    .Select(school => new
    {
        // only select the properties that you plan to use
        Id = school.Id,
        Name = school.Name,
        ...

        // Only the Students you plan to use:
        Students = school.Students.Where(student => ...)
            .Select(student => new
            {
                // Again, only the properties you plan to use
                Id = student.Id,
                Name = student.Name,

                // no need for the foreign key: you already know the value
                // SchoolId = student.SchoolId,
            }),
     });

最后,如果您想访问所有Students 以显示它们,但又不想一次获取所有百万学生,请考虑按页面获取它们。记住最后抓取页面的最后一项的主键,使用`.Where(item => item.Id > lastFetchedPrimaryKey).Take(pageSize)获取下一页,直到没有更多页面为止。

这样,您可能会要求 50 个学生,而您只会显示其中的 25 个,但至少您不会在内存中拥有所有百万学生。获取下一页相当快,因为​​主键上已经有一个索引,并且获取的项目已经按主键排序。

【讨论】:

  • 我会在“不要做以下事情!”与“在(可重用)库代码”
  • 你也不应该在本地代码中使用Include。如果你使用Include而不是Select,那么查询School 10 及其7000Students将传输外键SchoolId 7000 次,而你已经知道值是10,School.Id的值
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-31
  • 2011-11-17
  • 1970-01-01
  • 1970-01-01
  • 2014-10-01
  • 1970-01-01
相关资源
最近更新 更多