我有使用Entity Framework 5的数据库清理作业。有一些业务规则来确定是否应该从数据库中删除实体,如果应删除一个父实体,也应删除各个相关实体。作业首先创建一个DbContext,对其执行所有其他查询和.Remove(someObj)操作。每次使用.Remove()将父项及其所有相关实体标记为删除时,我都会调用.SaveChanges()。我已经设置了db.Configuration.AutoDetectChangesEnabled = false;
我注意到性能会随着时间的推移而显着减慢。
例如,在具有5000个实体的数据库中,应删除500个,执行时间约为1:30。我应该修改规则,以便删除1000,执行时间最长为7分钟!
我试着注意不要在上下文中加载超过必要的数据,并尽量减少执行到数据库的查询数量。尽管如此,第一个100比同一个工作中的最后100个要快得多。
为什么呢?总内存使用量和对数据库执行的查询数似乎是合理的。
将工作分成较小的块(例如100),并为每个块创建一个新的DbContext,似乎没有帮助。
我还有其他可以考虑的事情吗?它可能与数据库或索引有关吗?
代码(它应该为每10个父迭代创建一个新的DbContext并在每个父代上调用.SaveChanges()):
public void DeleteIndividuals(siteId)
{
deleteShippedIndividuals(siteId, 30);
}
private void deleteShippedIndividuals(Guid siteId, int numberOfDaysUntilDelete)
{
double? numberOfDaysUntilDelete_double = Convert.ToDouble(numberOfDaysUntilDelete);
bool cont = true;
do
{
db = new TrackAndTraceEntities();
db.Configuration.AutoDetectChangesEnabled = false;
//Get Top level parents with status SHIPPED
var topLevelIndividuals = (from i in db.Individuals
where i.IndividualStatusType.Id == TrackAndTraceConstants.IndividualStatusId.SHIPPED
&& i.Parent == null
&& i.SiteId == siteId
&& DateTime.Now > SqlFunctions.DateAdd("day", numberOfDaysUntilDelete_double, i.LastUpdate)
orderby i.LastUpdate
select i).Take(10).ToList();
if (topLevelIndividuals.Count > 0)
{
cont = true;
}
else
{
cont = false;
}
//For each top level parent where has expire date is passed
foreach (Individual topLevelIndividual in topLevelIndividuals)
{
deleteShippedIndividual(topLevelIndividual, numberOfDaysUntilDelete);
}
} while (cont);
}
private void deleteShippedIndividual(Individual topLevelIndividual, int numberOfDaysUntilDelete)
{
//Get all children on all levels in one big list
List<Individual> allChildren = new List<Individual>();
getAllChildren(topLevelIndividual, allChildren);
//Iterate all children and check if all are not in status SHIPPED or have not expired
foreach (Individual child in allChildren)
{
if (child.IndividualStatusType.Id != TrackAndTraceConstants.IndividualStatusId.SHIPPED)
{
//If structure has any child where status != SHIPPED, don't delete.
return;
}
if (!(DateTime.Now > child.LastUpdate.AddDays(numberOfDaysUntilDelete)))
{
//If structure has any child that has not yet expired, don't delete.
return;
}
}
//Else delete entrie structure
deleteEntireStructure(topLevelIndividual);
}
private void getAllChildren(Individual parent, List<Individual> target)
{
var children = db.Individuals.Include("IndividualStatusType").Include("IndividualTests").Include("IndividualAdditionalNumbers").Where(i => i.Parent.Id == parent.Id).ToList();
foreach (Individual child in children)
{
target.Add(child);
getAllChildren(child, target);
}
}
private void deleteEntireStructure(Individual topLevelIndividual)
{
try
{
//Structure must be deleted bottom up, so start with all leaf children
if (topLevelIndividual.ChildIndividuals != null && topLevelIndividual.ChildIndividuals.Count > 0)
{
deleteLeafChildren(topLevelIndividual.ChildIndividuals.ToList());
}
//Finally delete the top parent
doDeleteIndividual(topLevelIndividual);
//And save changes
db.SaveChanges();
}
catch (Exception ex)
{
//Log and continue
string shortMessage = "Failed to delete individual structure (top parent serialnumber = " + topLevelIndividual.SerialNo + ", site = " + topLevelIndividual.SiteId.ToString();
string details = getExceptionDetails(ex);
this.textBox1.Text += DateTime.Now + ": " + shortMessage + Environment.NewLine + details + Environment.NewLine + Environment.NewLine;
}
}
private void deleteLeafChildren(List<Individual> children)
{
foreach (Individual child in children)
{
if (child.ChildIndividuals != null && child.ChildIndividuals.Count > 0)
{
deleteLeafChildren(child.ChildIndividuals.ToList());
}
doDeleteIndividual(child);
}
}
private void doDeleteIndividual(Individual individual)
{
//Delete all additional numbers
foreach (IndividualAdditionalNumber i in individual.IndividualAdditionalNumbers.ToList())
{
db.IndividualAdditionalNumbers.Remove(i);
}
individual.IndividualAdditionalNumbers = new List<IndividualAdditionalNumber>();
// Delete tests
foreach (IndividualTest i in individual.IndividualTests.ToList())
{
db.IndividualTests.Remove(i);
}
individual.IndividualTests = new List<IndividualTest>();
db.Individuals.Remove(individual);
}
答案 0 :(得分:0)
唯一可能发生的事情是上下文中包含太多关于被修改的实体的信息,这些实体执行的次数越来越多。
确保在每次迭代时初始化上下文,并确保旧上下文超出范围,以便GC可以收集它。
我建议的是,你会绕过“正常”方式删除实体并使用类似的东西
context.Database.ExecuteSqlCommand("DELETE FROM "+table+" WHERE something="+value);
虽然您必须进行所有适当的检查,并确保以正确的顺序执行查询。
上述方法还确保您可以执行一个查询,而不是每行执行一次查询。
尝试这样的事情(我有类似的问题,这个解决方案运作得很好)
class deleter
{
DbContext context = new DbContext();
private void ExecuteInIterations(){
List<int> idsToDelete = context.ItemsToDelete.Where(......).Select(x=>x.Id).ToList();
for(/*Number of items*/)
{
context = new DbContext();
ExecuteOneIteration(idsToDelete,startIndex,endIndex);
}
}
private void ExecuteOneIteration(List<int> idsToDelete, int start, int end){
// Do whatever in here
}
}
看到您的代码后,我建议进行以下更改
private void deleteShippedIndividuals(Guid siteId, int numberOfDaysUntilDelete)
{
double? numberOfDaysUntilDelete_double = Convert.ToDouble(numberOfDaysUntilDelete);
bool cont = true;
do
{
using(var db = new TrackAndTraceEntities())
{
db.Configuration.AutoDetectChangesEnabled = false;
//Get Top level parents with status SHIPPED
var topLevelIndividuals = (from i in db.Individuals
where i.IndividualStatusType.Id == TrackAndTraceConstants.IndividualStatusId.SHIPPED
&& i.Parent == null
&& i.SiteId == siteId
&& DateTime.Now > SqlFunctions.DateAdd("day", numberOfDaysUntilDelete_double, i.LastUpdate)
orderby i.LastUpdate
select i).Take(10).ToList();
if (topLevelIndividuals.Count > 0)
cont = true;
else
cont = false;
//For each top level parent where has expire date is passed
foreach (Individual topLevelIndividual in topLevelIndividuals)
{
deleteShippedIndividual(topLevelIndividual, numberOfDaysUntilDelete);
}
} while (cont);
}
}