尝试使用 OData 和实体框架克隆 Project Server 数据库时出现问题

问题描述

我在使用 Parallel.Foreach 更新我的实体时遇到问题。我拥有的程序通过使用 foreach 来更新实体可以正常工作,但是如果我使用 Parallel.Foreach 它会给我这样的错误:“参数异常:已经添加了具有相同键的项目”。我不知道为什么会这样,它不应该是线程安全的吗?或者为什么给我这个错误?如何解决这个问题?

程序本身从数据库获取一些数据并将其复制到另一个数据库中。如果数据行存在相同的 guid(见下文),并且状态不变,则必须更新第二个匹配数据行。如果匹配,并且状态发生变化,则必须忽略修改。最后,如果在第二个数据库中没有匹配项,则将数据行插入到第二个数据库中。 (同步两个数据库)。我只是想以某种方式加快进程,这就是我首先想到并行处理的原因。

(如果重要的话,我使用 Autofac 作为 IoC 容器和依赖项注入)

这是尝试更新的代码片段:

     /* @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);
        }
    }


}

解决方法

请记住,很难给出完整的答案,因为有些事情我需要澄清……但评论应该有助于构建图片。

Parallel.ForEach(reports,r => //Parallel.ForEach is not the answer..
{
    //reportdataindatabase is done..before so ok here
    // do you really want FirstOrDefault vs SingleOrDefault
    var guid = reportdataindatabase.FirstOrDefault(x => x.AssignmentGUID == r.AssignmentGUID);

    if (guid == null)
    {
        // this is done on the context not the DB,unresolved..(excuted)
        unitOfWorkTAFeedBack.ReportsRepository.Add(r); // an insert on the repository
        //insertedcount++; u would need a lock
    }
    else
    {
        if (guid.StatusId == statusid)
        {
            r.ReportsID = guid.ReportsID;
            // this is done on the context not the DB,unresolved..(excuted)
            unitOfWorkTAFeedBack.ReportsRepository.Update(r); // update on the repo
            //updatedcount++; u would need a lock
        }
        else
        {
            //ignoredcount++; u would need a lock
        }
    }
});

这里的问题......因为reportdataindatabase可以包含两次相同的键...... 并且上下文仅在事后更新,也就是当它到达这里时..

unitOfWorkTAFeedBack.Commit();

它可能被同一个实体调用了两次 如上所述(提交)是工作所在......在并行执行上面的添加/更新不会为您节省任何实时时间,因为那部分很快..

//更新 1287 条记录需要 80 秒...看起来确实很长... //列出reportdataindatabase = unitOfWorkTAFeedBack.ReportsRepository.List().ToList();

//PS 添加如何检索报告.. 你想要类似的东西

TAFeedBackContext db = new TAFeedBackContext();
var remoteReports = DatafromAnotherPLace //include how this was retrieved;
var localReports = TAFeedBackContext.Reports.ToList(); //these are tracked.. (by default)
foreach (var item in remoteReports)
{
    //i assume more than one is invalid.
    var localEntity = localReports.SingleOrDefault(x => x.AssignmentGUID == item.AssignmentGUID); 
    if (localEntity == null)
    {
        //add as it doenst exist 
        TAFeedBackContext.Reports.Add(new Report() { *set fields* });       
    }
    else
    {
        if (localEntity.StatusId == statusid) //only update if status is the passed in status.
        {
            //why are you modifying the remote entity
            item.ReportsID = localEntity.ReportsID;
            
            //update remove entity?,i get the impression its from a different context,//if not then cool,but you need to show how reports is retrieved
            
        }
        else
        {
            
        }

    }

} 

TAFeedBackContext.SaveChanges();