2017-08-24 105 views
3

我在桌面应用程序中使用实体框架访问mysql数据库。数据库安装在同一个系统上,而我现在是唯一使用它的系统。为什么DBSet.Find越来越慢?

我有一种方法,检查某个集合docs中的对象是否已经在数据库中,如果它们不是它们将被添加到数据库中,并且最后会执行DBContext上的保存方法。该方法在我的程序中循环执行多次。

我注意到,即使要查找的对象数量相当恒定(每次大约500次),每次执行该方法时查找速度都会变得越来越慢。可能是什么原因?

代码看起来或多或少是这样的:

TimeSpan timeToFind = new TimeSpan(); 

foreach (var docFromResult in docs) 
{ 
    DateTime operationStart = DateTime.Now; 
    var existingDocument = 
     db.VaStDocuments.Find(docFromResult.Id, docFromResult.OwnerId, docFromResult.Year); 
    timeToFind += DateTime.Now - operationStart; 
    if (existingDocument != null && docFromResult.Hash.Equals(existingDocument.Hash)) 
    { 
     continue; 
    } 
    if (existingDocument == null) 
    { 
     db.VaStDocuments.Add(docFromResult); 
    } 
    else if (!docFromResult.Hash.Equals(existingDocument.Hash)) 
    { 
     existingDocument.Hash = docFromResult.Hash; 
     existingDocument.IsNew = true; 
     existingDocument.Text = null; 
    } 
    docsForNotification.Add(docFromResult); 
} 
if (docsForNotification.Any()) 
{ 
    db.SaveChangesDisplayValidationErrors(); 
} 

因此,它是该文件将无法在数据库中找到这样基本上是唯一的数据库活动在这里查找方法非常罕见的情况。
timetoFind通常随着方法的每次执行而增加。在20-30个循环之后,从0.5-2s开始到120s。

+3

您追踪的实体列表将会增加,因此您可以尝试避免:'db.VaStDocuments.AsNoTracking()。查找(...)' – DavidG

+0

@DavidG - 任何副作用? – Greg

+0

除了性能提升之外,此代码中没有任何内容。如果你在其他地方使用这些实体,它可能会受到一些影响,但我非常怀疑这一点。 – DavidG

回答

1

您的DbContext会跟踪您检索的项目以及您对这些检索项目所做的更改。

这是为了能够添加/更新/删除这些项目,而无需与每个操作的数据库进行通信。只有在调用SaveChanges时,才会将更改与数据库进行通信。

这使您可以在将这些更改提交到数据库之前使用新添加或更改的对象。

考虑一对多关系运输客户订单:客户有零个或多个订单,每个订单只属于一个客户。

现在您可以先介绍一位新客户。之后,你可以介绍订单,而客户没有ID尚未:

using (var dbContext = new MyDbContext(...)) 
{ // Introduce a Customer: 
    Customer customerToAdd = GetCustomerData(); 
    var addedCustomer = dbContext.Customers.Add(customerToAdd); 

    // Introduce an order for this Customer 
    Order addedOrder = dbContext.Orders.Add(new Order() 
    { 
     // addedCustomer has no Id yet, we can't use addedCustomer.Id 
     Order.Customer = addedCustomer; 
     ... 
    }); 
    dbContext.SaveChanges(); 
} 

因为不停的DbContext跟踪添加客户的您能够使用它保存的更改。

它还使您无需增加客户至上添加顺序为:

var notAddedCustomer = new Customer() {...} 
var order1= dbContext.Orders.Add(new Order() 
{ 
    // this order belongs to a new customer that has not been added yet: 
    Customer = notAddedCustomer 
} 

的DbContext检测到客户尚未添加,并添加它本身。如果您要为同一个notAddedCustomer创建另一个订单,那么dbContext必须能够检测到该客户是在引入第一个订单期间添加的。另外,如果您删除了一个客户,并尝试给他一个新的订单,那么dbContext需要检测到该客户已被删除,并且无法添加新的订单。同样,如果您在添加一些订单后删除客户,则dbContext应该检测到这一点。

这就是为什么dbContext需要跟踪更改。唉,如果你有很多改变的对象,这会减慢使用率。大多数dbContext的用户只能在相当短的时间内进行相当少量的更改。

如果您在进行SaveChanges之前需要做很多更改,并且确定在执行实际SaveChanges之前不需要检测它们中的任何一个,则可以使用Queryable.AsNoTracking关闭跟踪。这大大提高了查询和添加项目的速度。缺点是在使用相关对象时必须使用Ids而不是对象。

+0

谢谢你的回答。我注意到了两件事 - 即使我经常打电话给'SaveChanges',如果我不使用AsNoTracking,性能仍然很慢。当我使用AsNoTracking与保存docFromResult,因为它引用另一个所有者实体。如果我也检索Owner AsNoTracking,则在保存时会出现错误。所有者已经在数据库中,不需要添加。但也许我应该为此做一个单独的问题。 – Greg

+1

SaveChanges不会删除跟踪的项目。优点:如果再次请求相同的项目,则不必从数据库中查询。如果关闭使用(并因此Dispose dbContext),然后重新创建一个新的DbContext对象 –

+0

,那么通过在每个循环中重新初始化/配置dbContext,看起来会比使用40-50倍更好。在进行一些同步时挣扎了一下,但仍然比追踪所有事情少得多。 – Greg