EF启动速度极慢 – 15分钟

前段时间我创建了一个系统,用户可以使用自定义文件为某些对象定义类别。 然后,每个对象都具有基于其类别的FieldValue。 以下课程:

public class DbCategory { public int Id { get; set; } [Required] public string Name { get; set; } [Required] public TextDbField MainField { get; set; } public List Fields { get; set; } } public class DbObject { public int Id { get; set; } public byte[] Bytes { get; set; } [Required] public DbCategory Category { get; set; } public TextDbFieldValue MainFieldValue { get; set; } public List FieldsValues { get; set; } } public abstract class DbField { public int Id { get; set; } [Required] public string Name { get; set; } [Required] public bool Required { get; set; } } public class IntegerDbField : DbField { public int? Minimum { get; set; } public int? Maximum { get; set; } } public class FloatDbField : DbField { public double? Minimum { get; set; } public double? Maximum { get; set; } } //... few other types public abstract class DbFieldValue { [Key] public int Id { get; set; } [Required] public DbField Field { get; set; } [JsonIgnore] public abstract string Value { get; set; } } public class IntDbFieldValue : DbFieldValue { public int? IntValue { get; set; } public override string Value { get { return IntValue?.ToString(); } set { if (value == null) IntValue = null; else IntValue = int.Parse(value); } } }// and other FieldValue types 

在我的开发机器(i5,16bg ram和ssd驱动器)上,数据库(在SqlExpress中)有4个类别,每个类别有5-6个字段,10k记录,第一个查询大约需要15s。 第一个查询是

 var result = db.Objects .Include(s => s.Category) .Include(s => s.Category.MainField) .Include(s => s.MainFieldValue.Field) .Include(s => s.FieldsValues.Select(f => f.Field)) .Where(predicate ?? AlwaysTrue) .ToArray(); 

我这样做是为了将所有内容加载到内存中 然后,我在缓存列表上工作,只是将更改写入数据库。 我这样做,因为用户可以在每个FieldValue上使用filter执行搜索。 每次查询数据库然后被certificate要慢得多 – 但这部分工作得很好。

问题发生在以后。 一些客户定义了6个类别,每个类别有20多个字段,并存储70k +记录,启动时间有时超过15分钟。 之后,5k和50k之间的速度没有差别。

我发现的每一项改进EF Code First启动时间的技术主要考虑视图创建缓存,ngening EF等等,但在这种情况下,启动时间会在添加更多记录后增加,而不是更多实体类型。

我意识到这是由架构的复杂性引起的,但有没有办法加速这个? 幸运的是,这是Windows服务,所以一旦启动,它会持续数周,但仍然如此。

我应该在第一次加载时删除EF并在纯SQL中执行吗? 我应该分批这样做吗? 我应该将EF更改为nHibernate吗? 或者是其他东西? 在执行此行期间的虚拟化服务器上​​,此程序最大化CPU(不是SQL服务器,而是我的应用程序)。

我尝试过只加载对象,然后再加载它们的属性。 这在小型数据库上要快一点(但不是很明显),但在较大的数据库上则要慢一些。 任何帮助表示赞赏,即使答案是“吮吸并等待”。

我设法用这些技巧减少了EF 3倍的总开始时间:

  1. 将框架更新到6.2并启用模型缓存 :

    public class CachingContextConfiguration:DbConfiguration {public CachingContextConfiguration(){SetModelStore(new DefaultDbModelStore(Directory.GetCurrentDirectory())); }

    }

  2. 尽可能早地从新线程中调用ctx.Database.Initialize() 。 这仍然需要3-4秒,但由于它与其他东西一起发生,它有很大帮助。

  3. 以合理的顺序将实体加载到EF缓存中。

以前,我刚写了Inlude之后的Include,它转换为多个连接。 我在一些博客文章中找到了一个“经验法则”,最多两个链接包括EF表现相当不错,但每一个都会大幅减慢一切。 我还发现了一篇博文 ,其中显示了EF缓存:一旦给定的实体加载了Include或Load,它将自动放入适当的属性(博客作者对象的联合是错误的)。 所以我这样做了:

  using (var db = new MyContext()) { db.Fields.Load(); db.Categories.Include(c => c.MainField).Include(x => x.Fields).Load(); db.FieldValues.Load(); return db.Objects.Include(x => x.MainFieldValue.Field).ToArray(); } 

这比从问题包含的数据快6倍。 我认为,一旦先前加载了实体,EF引擎就不会为相关对象调用数据库,它只是从缓存中获取它们。

  1. 我还在我的上下文构造函数中添加了这个:

      Configuration.LazyLoadingEnabled = false; Configuration.ProxyCreationEnabled = false; 

这种影响几乎不可察觉,但可能在庞大的数据集中发挥更大的作用。

我也看过Rowan Miller对EF Core的介绍,我将在下一个版本中切换到它 – 在某些情况下,它比EF6快5-6倍。

希望这有助于某人