前段时间我创建了一个系统,用户可以使用自定义文件为某些对象定义类别。然后,每个对象都具有基于其类别的FieldValue。以下课程:
public class DbCategory
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public TextDbField MainField { get; set; }
public List<DbField> 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<DbFieldValue> 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记录,第一个查询大约需要15秒。第一个查询是
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上使用过滤器执行搜索。每次查询数据库都证明要慢得多 - 但这部分工作得很好。
以后会出现问题。一些客户定义了6个类别,每个类别有20多个字段,并存储70k +记录,启动时间有时超过15分钟。之后,5k和50k之间的速度没有差别。
每项改进EF Code First技术的技术我发现主要考虑视图创建缓存,ngening EF等等,但在这种情况下,启动时间会在添加更多记录后增加,而不是更多实体类型。
我意识到这是由架构的复杂性引起的,但有没有办法加速这个?幸运的是,这是Windows服务,因此一旦启动,它将持续数周,但仍然。
我应该在第一次加载时删除EF并在纯SQL中执行吗?我应该分批这样做吗?我应该将EF更改为nHibernate吗?或者是其他东西?在执行此行期间的虚拟化服务器上,此程序最大化CPU(不是SQL服务器,而是我的应用程序)。
我尝试过只加载对象,然后再加载它们的属性。这在小型数据库上要快一点(但不是很明显),但在较大的数据库上则要慢一些。任何帮助表示赞赏,即使答案是“吮吸并等待”。
答案 0 :(得分:2)
我设法用这些技巧减少了EF 3倍的总开始时间:
将框架更新到6.2并启用model caching:
public class CachingContextConfiguration:DbConfiguration { public CachingContextConfiguration() { SetModelStore(new DefaultDbModelStore(Directory.GetCurrentDirectory())); }
}
尽早从新主题中明确调用ctx.Database.Initialize()
。这仍然需要3-4秒,但由于它与其他东西一起发生,它会有很大帮助。
以合理的顺序将实体加载到EF缓存中。
以前,我刚写了Inlude之后的Include,它转换为多个连接。我在一些博客文章中找到了一个“经验法则”,最多两个链接包括EF表现相当不错,但每一个都会大幅减慢一切。我还发现了一个显示EF缓存的blog post:一旦给定的实体加载了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引擎就不会为相关对象调用数据库,它只是从缓存中获取它们。
我还在我的上下文构造函数中添加了这个:
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
这种影响几乎不可察觉,但可能在庞大的数据集中扮演更重要的角色。
我还看过Rowan Miller对{1}} EF Core的介绍,我将在下一个版本中转发它 - 在某些情况下,它比EF6快5-6倍。
希望这有助于某人