使用EF Core FAST读取数千个对象

Vla*_*yan 4 .net c# asp.net sqlite entity-framework-core

我正在从带有EF核心的SQLite读取40,000个小对象/行,这花了18秒,对于我的UWP应用来说太长了。发生这种情况时,单个内核上的CPU使用率达到100%,但是磁盘读取速度约为1%。

var dataPoints =  _db.DataPoints.AsNoTracking().ToArray();
Run Code Online (Sandbox Code Playgroud)

没有AsNoTracking()时间就更长了。

DataPoint是具有一些原始属性的小型POCO。我正在加载的数据总量为4.5 MB。

    public class DataPointDto
    {
        [Key]
        public ulong Id { get; set; }

        [Required]
        public DateTimeOffset TimeStamp { get; set; }

        [Required]
        public bool trueTime { get; set; }

        [Required]
        public double Value { get; set; }
   }
Run Code Online (Sandbox Code Playgroud)

问题:是否有更好的方法来装载这么多对象,还是我对这种性能水平感到困惑?

有趣的事实: x86花费11秒,x64花费18。“优化代码”节省了一秒钟。使用Async将执行时间推至30秒。

Vla*_*yan 5

大多数答案遵循加载较少数据的常识,但是在某些情况下,例如您在这里,绝对肯定必须加载许多实体。那么我们该怎么做呢?

表现不佳的原因

这样的手术难免要花这么长时间吗?好吧,不是。我们仅从磁盘加载一兆字节的数据,性能不佳的原因是数据分散在40,000个微小实体中。数据库可以解决这个问题,但是实体框架似乎很难设置所有这些实体,进行更改跟踪等。如果我们不打算修改数据,则可以做很多事情。

我尝试了三件事

原语

仅加载一个属性,然后您将获得一个基元列表。

List<double> dataPoints =  _db.DataPoints.Select(dp => dp.Value).ToList();
Run Code Online (Sandbox Code Playgroud)

这绕过了通常由实体框架执行的所有实体创建。该查询花费了0.4秒,而原始查询为18秒。我们说的是改善了45(!)倍。

匿名类型

当然,在大多数情况下,我们不仅需要一组原始数组,我们还可以在LINQ查询内部创建新对象。实体框架不会像通常那样创建实体,并且该操作运行得更快。为了方便起见,我们可以使用匿名对象。

var query = db.DataPoints.Select(dp => new {Guid ID = dp.sensorID, DateTimeOffset Timestamp = dp.TimeStamp, double Value = dp.Value});
Run Code Online (Sandbox Code Playgroud)

此操作需要1.2秒,而通常要检索相同数量的数据需要18秒。

元组

我发现在我的情况下,使用元组而不是匿名类型可以提高性能,以下查询的执行速度大约快30%:

var query = db.DataPoints.Select(dp => Tuple.Create(dp.sensorID, dp.TimeStamp, dp.Value));
Run Code Online (Sandbox Code Playgroud)

其他方法

  1. 您不能在LinQ查询中使用结构,因此这不是一种选择
  2. 在许多情况下,您可以将许多记录合并在一起,以减少与检索许多单个记录相关的开销。通过检索较少的较大记录,可以提高性能。例如,在我的用例中,我每隔5分钟(24/7)进行一次测量。目前,我将它们单独存储,这很愚蠢。没有人会查询少于一天的时间。我计划在进行更改时更新此帖子,并了解性能如何发生变化。
  3. 一些建议使用面向对象的数据库或微型ORM。我也从未用过,所以我无法发表评论。