【发布时间】:2021-01-13 10:32:45
【问题描述】:
我无法使用 Parallel.Foreach 更新我的实体。我拥有的程序通过使用 foreach 更新实体可以正常工作,但是如果我使用 Parallel.Foreach 它会给我这样的错误:“参数异常:已添加具有相同键的项目”。我不知道它为什么会发生,它不应该是线程安全的吗?或者为什么给我这个错误?如何解决这个问题?
程序本身从数据库中获取一些数据并将其复制到另一个数据库中。如果数据行以相同的 guid 存在(见下文),并且状态不变,则必须更新第二个匹配的数据行。如果存在匹配并且状态发生更改,则必须忽略修改。最后,如果在第二个数据库中没有匹配,则将数据行插入到第二个数据库中。 (同步两个数据库)。我只是想以某种方式加速这个过程,这就是我首先想到并行处理的原因。
(如果重要的话,我使用 Autofac 作为 IoC 容器和依赖注入)
这里是尝试更新的代码sn-p:
/* @param reports: data from the first database */
public string SynchronizeData(List<Reports> reports, int statusid)
{
// reportdataindatabase - the second database data, List() actually selects all, see next code snippet
List<Reports> reportdataindatabase = unitOfWorkTAFeedBack.ReportsRepository.List().ToList();
int allcount = reports.Count;
int insertedcount = 0;
int updatedcount = 0;
int ignoredcount = 0;
// DOES NOT WORK, GIVES THE ERROR
Parallel.ForEach(reports, r =>
{
var guid = reportdataindatabase.FirstOrDefault(x => x.AssignmentGUID == r.AssignmentGUID);
if (guid == null)
{
unitOfWorkTAFeedBack.ReportsRepository.Add(r); // an insert on the repository
insertedcount++;
}
else
{
if (guid.StatusId == statusid)
{
r.ReportsID = guid.ReportsID;
unitOfWorkTAFeedBack.ReportsRepository.Update(r); // update on the repo
updatedcount++;
}
else
{
ignoredcount++;
}
}
});
/* WORKS PERFECTLY BUT RELATIVELY SLOW - takes 80 seconds to update 1287 records
foreach (Reports r in reports)
{
var guid = reportdataindatabase.FirstOrDefault(x => x.AssignmentGUID == r.AssignmentGUID); // find match between the two databases
if (guid == null)
{
unitOfWorkTAFeedBack.ReportsRepository.Add(r); // no match, insert
insertedcount++;
}
else
{
if (guid.StatusId == statusid)
{
r.ReportsID = guid.ReportsID;
unitOfWorkTAFeedBack.ReportsRepository.Update(r);
updatedcount++;
}
else
{
ignoredcount++;
}
}
} */
unitOfWorkTAFeedBack.Commit(); // this only calls SaveChanges() on DbContext object
int allprocessed = insertedcount + updatedcount + ignoredcount;
string result = "Synchronization finished. " + allprocessed + " reports processed out of " + allcount + ", "
+ insertedcount + " has been inserted, " + updatedcount + " has been updated and "
+ ignoredcount + " has been ignored. \n Press a button to dismiss this window." ;
return result;
}
程序在 Update 方法中在这个 Repository 类上中断(使用 Parallel.Foreach,标准 foreach 没有问题):
public class EntityFrameworkReportsRepository : IReportsRepository
{
private readonly TAFeedBackContext tAFeedBackContext;
public EntityFrameworkReportsRepository(TAFeedBackContext tAFeedBackContext)
{
this.tAFeedBackContext = tAFeedBackContext;
}
public void Add(Reports r)
{
tAFeedBackContext.Reports.Add(r);
}
public void Delete(int Id)
{
var obj = tAFeedBackContext.Reports.Find(Id);
tAFeedBackContext.Reports.Remove(obj);
}
public Reports Get(int Id)
{
var obj = tAFeedBackContext.Reports.Find(Id);
return obj;
}
public IQueryable<Reports> List()
{
return tAFeedBackContext.Reports.AsNoTracking();
}
public void Update(Reports r)
{
var entry = tAFeedBackContext.Entry(r); // The Program Breaks At This Point!
if (entry.State == EntityState.Detached)
{
tAFeedBackContext.Reports.Attach(r);
tAFeedBackContext.Entry(r).State = EntityState.Modified;
}
else
{
tAFeedBackContext.Entry(r).CurrentValues.SetValues(r);
}
}
}
【问题讨论】:
-
尝试并行执行 N 更新比执行一批 N 更新要糟糕得多。你不能通过运行更多的坏数据访问代码来修复它,这只会在已经很糟糕的性能之上增加并发冲突
-
unitOfWorkTAFeedBack.ReportsRepository.Add我怀疑这是线程安全的。 -
你为什么要加载所有项目。同时而不是使用一个查询?你遇到过真正的问题吗?不知道如何使用多个ID?
myContext.Reports.Where(rep=>ids.Contains(rep.ID))将被翻译成WHERE ID iN (@id1, @id2, @id3.....) -
您可以使用例如 Dapper 在一行中执行查询并返回您可以传递给 SqlBulkCopy 的 DbDataReader
-
阅读重复的问题。大约 10 行代码完成了您尝试做的事情,而且他们实际上做到了 更快。 SqlBulkCopy 将使用
bcp或BULK INSERT使用的相同机制和最小日志记录以连续流的形式发送数据。通过将数据写入临时表并更新目标表,无需从数据库中读取数据。服务器上两个连接表之间的单个大更新不会使用任何带宽,也不必等待数据到达服务器。避免了并行执行多个UPDATE操作引起的并发冲突
标签: c# entity-framework odata parallel.foreach project-server