简体   繁体   English

实体框架性能问题

[英]Entity Framework Performance Issue

I am running into an interesting performance issue with Entity Framework. 我遇到了Entity Framework的一个有趣的性能问题。 I am using Code First. 我正在使用Code First。

Here is the structure of my entities: 这是我的实体的结构:

A Book can have many Reviews. 一本书可以有很多评论。 A Review is associated with a single Book. 评论与单本书相关联。 A Review can have one or many Comments. 评论可以有一个或多个评论。 A Comment is associated with one Review. 评论与一篇评论相关联。

public class Book
{
    public int BookId { get; set; }
    // ...
    public ICollection<Review> Reviews { get; set; }
}

public class Review 
{
    public int ReviewId { get; set; }
    public int BookId { get; set; }
    public Book Book { get; set; }
    public ICollection<Comment> Comments { get; set; }
}

public class Comment
{
     public int CommentId { get; set; }
     public int ReviewId { get; set; }
     public Review Review { get; set; }
}

I populated my database with a lot of data and added the proper indexes. 我用大量数据填充了我的数据库并添加了适当的索引。 I am trying to retrieve a single book that has 10,000 reviews on it using this query: 我正在尝试使用此查询检索一本包含10,000条评论的图书:

var bookAndReviews = db.Books.Where(b => b.BookId == id)
                       .Include(b => b.Reviews)
                       .FirstOrDefault();

This particular book has 10,000 reviews. 这本特别的书有10,000条评论。 The performance of this query is around 4 seconds. 此查询的性能约为4秒。 Running the exact same query (via SQL Profiler) actually returns in no time at all. 运行完全相同的查询(通过SQL事件探查器)实际上很快就会返回。 I used the same query and a SqlDataAdapter and custom objects to retrieve the data and it happens in under 500 milliseconds. 我使用相同的查询和SqlDataAdapter以及自定义对象来检索数据,它发生在500毫秒以内。

Using ANTS Performance Profiler it looks like a bulk of the time is being spent doing a few different things: 使用ANTS Performance Profiler看起来大部分时间花在做一些不同的事情上:

The Equals method is being called 50 million times. Equals方法被称为5000万次。

Does anyone know why it would need to call this 50 million times and how I could increase the performance for this? 有谁知道为什么需要调用这5000万次以及如何才能提高性能呢?

Why is Equals called 50M times? 为什么Equals被称为50M次?

It sounds quite suspicious. 这听起来很可疑。 You have 10.000 reviews and 50.000.000 calls to Equals . 您有10,000条评论和50.000.000次调用Equals Suppose that this is caused by identity map internally implemented by EF. 假设这是由EF内部实现的身份映射引起的。 Identity map ensures that each entity with unique key is tracked by the context only once so if context already has instance with the same key as loaded record from the database it will not materialize new instance and instead uses the existing one. 身份映射确保上下文仅跟踪具有唯一键的每个实体一次,因此如果上下文已经具有与来自数据库的加载记录具有相同键的实例,则它将不实现新实例而是使用现有实例。 Now how this can coincide with those numbers? 现在这又如何与这些数字相吻合? My terrifying guess: 我可怕的猜测:

=============================================
1st      record read   |  0     comparisons
2nd      record read   |  1     comparison
3rd      record read   |  2     comparisons
...
10.000th record read   |  9.999 comparisons

That means that each new record is compared with every existing record in identity map. 这意味着将每个新记录与身份映射中的每个现有记录进行比较。 By applying math to compute sum of all comparison we can use something called "Arithmetic sequence": 通过应用数学计算所有比较的总和,我们可以使用称为“算术序列”的东西:

a(n) = a(n-1) + 1
Sum(n) = (n / 2) * (a(1) + a(n))
Sum(10.000) = 5.000 * (0 + 9.999) => 5.000 * 10.000 = 50.000.000

I hope I didn't make mistake in my assumptions or calculation. 我希望我的假设或计算没有错。 Wait! 等待! I hope I did mistake because this doesn't seem good. 我希望我错了,因为这似乎并不好。

Try turning off change tracking = hopefully turning off identity map checking. 尝试关闭更改跟踪=希望关闭身份地图检查。

It can be tricky. 这可能很棘手。 Start with: 从...开始:

var bookAndReviews = db.Books.Where(b => b.BookId == id)
                             .Include(b => b.Reviews)
                             .AsNoTracking()
                             .FirstOrDefault();

But there is a big chance that your navigation property will not be populated (because it is handled by change tracking). 但是很有可能您的导航属性不会被填充(因为它由变更跟踪处理)。 In such case use this approach: 在这种情况下使用这种方法:

var book = db.Books.Where(b => b.BookId == id).AsNoTracking().FirstOrDefault();
book.Reviews = db.Reviews.Where(r => r.BookId == id).AsNoTracking().ToList();

Anyway can you see what object type is passed to Equals? 无论如何,你能看到哪些对象类型传递给Equals? I think it should compare only primary keys and even 50M integer comparisons should not be such a problem. 我认为它应该只比较主键,甚至50M整数比较不应该是这样的问题。

As a side note EF is slow - it is well known fact. 作为旁注,EF很慢 - 这是众所周知的事实。 It also uses reflection internally when materializing entities so simply 10.000 records can take "some time". 在实现实体时,它还在内部使用反射,因此简单的10.000记录可能需要“一些时间”。 Unless you already did that you can also turn off dynamic proxy creation ( db.Configuration.ProxyCreationEnabled ). 除非您已经这样做,否则还可以关闭动态代理创建( db.Configuration.ProxyCreationEnabled )。

I know this sounds lame, but have you tried the other way around, eg: 我知道这听起来有点蹩脚,但你是否尝试过相反的方式,例如:

var reviewsAndBooks = db.Reviews.Where(r => r.Book.BookId == id)
                       .Include(r => r.Book);

I have noticed sometimes better performance from EF when you approach your queries this way (but I haven't had the time to figure out why). 当你以这种方式处理你的查询时,我注意到EF有时会有更好的表现(但我没有时间弄清楚原因)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM