最近,我通过禁用自动更改检测(Context.Configuration.AutoDetectChangesEnabled = false
)来调整运行缓慢的应用程序的一部分,然后再进行批量删除,然后重新启用它并保存更改。
我读了几个不同的资料,从本质上讲,每当我在DbSet上调用诸如.Add()
或.Remove()
之类的方法时,就会调用DetectChanges()
,而当我们处理大量实体时,这可能会变得昂贵。好的。
现在,我想特别注意这些文章:
Entity Framework Automatic Detect Changes (MSDN)
Secrets of Detect Changes: Part 3
也许它在我眼前,但是假设例如,我将.SaveChanges()
包装在一个始终始终首先称为DetectChanges()
的方法中,那么我会遇到哪些我通常不会遇到的错误?我所看到的所有警告都隐约表明,糟糕的事情可能发生而无需深入了解。
最佳答案
假设我们具有以下BankAccount
s和Deposit
s模型-一个简单的一对多关系:BankAccount
有一个Deposit
s集合,并且Deposit
属于单个BankAccount
:
public class BankAccount
{
public int Id { get; set; }
public int AccountNumber { get; set; }
public string Owner { get; set; }
public ICollection<Deposit> Deposits { get; set; }
}
public class Deposit
{
public int Id { get; set; }
public decimal Value { get; set; }
public int BankAccountId { get; set; }
public BankAccount BankAccount { get; set; }
}
和一个简单的数据库上下文:
public class MyContext : DbContext
{
public DbSet<BankAccount> BankAccounts { get; set; }
public DbSet<Deposit> Deposits { get; set; }
}
约翰·史密斯先生想要在我们的银行拥有两个帐户,并向他的第一个帐户支付一笔1.000.000美元的 margin 。我们银行的程序员可以像这样完成这项任务:
using (var ctx = new MyContext())
{
var bankAccount123 = new BankAccount
{
AccountNumber = 123,
Owner = "John Smith",
Deposits = new List<Deposit> { new Deposit { Value = 1000000m } }
};
var bankAccount456 = new BankAccount
{
AccountNumber = 456,
Owner = "John Smith"
};
ctx.BankAccounts.Add(bankAccount123);
ctx.BankAccounts.Add(bankAccount456);
ctx.SaveChanges();
}
它像预期的那样工作:
一天后,史密斯先生给银行打了个电话:“我改变了主意。我不希望这两个帐户,只有一个,一个帐号为456,我更喜欢这个号码。我的123帐户上有100万美元。请将它们移到帐户456,然后删除我的帐户123!”
我们的程序员听说删除是危险的事情,因此决定将数据库复制到测试环境中,并首先测试他现在编写的新例程,以便遵循Smith先生的要求:
using (var ctx = new MyContext())
{
var bankAccount123 = ctx.BankAccounts.Include(b => b.Deposits)
.Single(b => b.AccountNumber == 123);
var bankAccount456 = ctx.BankAccounts
.Single(b => b.AccountNumber == 456);
var deposit = bankAccount123.Deposits.Single();
// here our programmer moves the deposit to account 456 by changing
// the deposit's account foreign key
deposit.BankAccountId = bankAccount456.Id;
// account 123 is now empty and can be deleted safely, he thinks!
ctx.BankAccounts.Remove(bankAccount123);
ctx.SaveChanges();
}
他运行了测试,并且可以正常工作:
在将代码投入生产之前,他决定增加一点性能,但是-当然-不会更改测试逻辑来转移存款和删除帐户:
using (var ctx = new MyContext())
{
// he added this well-known line to get better performance!
ctx.Configuration.AutoDetectChangesEnabled = false;
var bankAccount123 = ctx.BankAccounts.Include(b => b.Deposits)
.Single(b => b.AccountNumber == 123);
var bankAccount456 = ctx.BankAccounts
.Single(b => b.AccountNumber == 456);
var deposit = bankAccount123.Deposits.Single();
deposit.BankAccountId = bankAccount456.Id;
ctx.BankAccounts.Remove(bankAccount123);
// he heard this line would be required when AutoDetectChanges is disabled!
ctx.ChangeTracker.DetectChanges();
ctx.SaveChanges();
}
在完成日常工作之前,他会在生产环境中运行代码。
第二天,史密斯先生给银行打电话:“我需要从我的456帐户中拿出50万!”客服人员说:“对不起,先生,但是您的帐户456上没有钱。”史密斯先生:“啊,好了,他们还没有转移这笔钱。然后,请从我的123帐户拿走这笔钱!” “抱歉,先生,但是您没有123帐户!”史密斯先生:“怎么了?”客户服务:“我可以在我的银行工具中看到您的所有帐户和存款,而您的单个帐户456上没有任何内容:”
当我们的程序员增加了一点点性能提升并使史密斯先生成为可怜的人时,出了什么问题?
将
AutoDetectChangesEnabled
设置为false
后,行为不同的重要行是ctx.BankAccounts.Remove(bankAccount123);
。现在,此行不再在内部调用DetectChanges
。结果是EF无法获得有关BankAccountId
实体中外键deposit
的更改的知识(发生在调用Remove
之前)。启用更改检测后,
Remove
将根据更改的外键(“关系修复”)调整整个对象图,即deposit.BankAccount
将被设置为bankAccount456
,该deposit
将被从bankAccount123.Deposits
集合中删除并添加到bankAccount456.Deposits
集合中。因为这没有发生,所以
Remove
将父bankAccount123
标记为Deleted
,并将deposit
(仍然是bankAccount123.Deposits
集合中的一个子项)也置于Deleted
状态。调用SaveChanges
时,两者都会从数据库中删除。尽管该示例看起来有些虚构,但我记得在禁用真实代码中的更改检测后,我也遇到了类似的“错误”,这需要一些时间来查找和理解。主要问题在于,即使变更后的代码未做任何更改,仍可以正常运行且已通过变更检测进行测试的代码可能不再起作用,并且在禁用变更检测后需要再次进行测试。也许必须修改代码以使其再次正常工作。 (在我们的示例中,程序员必须在
ctx.ChangeTracker.DetectChanges();
行之前添加Remove
来修复该错误。)这是MSDN页面正在谈论的可能的“细微错误”之一。可能还有更多。
关于c# - 禁用自动更改检测会在EF中引起哪些错误?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/23636920/