【问题标题】:Problem trying to clone a Project Server database using OData and Entity Framework [duplicate]尝试使用 OData 和实体框架克隆 Project Server 数据库时出现问题 [重复]
【发布时间】: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=&gt;ids.Contains(rep.ID)) 将被翻译成WHERE ID iN (@id1, @id2, @id3.....)
  • 您可以使用例如 Dapper 在一行中执行查询并返回您可以传递给 SqlBulkCopy 的 DbDataReader
  • 阅读重复的问题。大约 10 行代码完成了您尝试做的事情,而且他们实际上做到了 更快。 SqlBulkCopy 将使用bcpBULK INSERT 使用的相同机制和最小日志记录以连续流的形式发送数据。通过将数据写入临时表并更新目标表,无需从数据库中读取数据。服务器上两个连接表之间的单个大更新不会使用任何带宽,也不必等待数据到达服务器。避免了并行执行多个UPDATE操作引起的并发冲突

标签: c# entity-framework odata parallel.foreach project-server


【解决方案1】:

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

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();

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

//更新 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();

【讨论】:

  • 远程报告数据来自 OData API。 Microsoft Project Server 2016 使用基于 SharePoint 的方法,并将其项目数据存储在自己的数据库中。但是,我已经获得了一些我必须使用的 API 的网络凭据。但是获取数据很容易,唯一的问题是,这个 API 设置为我自己的语言(匈牙利语,数据字段映射到匈牙利语),因此我必须将其恢复为英语。没关系,我做映射和数据检索很快。
  • 我需要在遥控器中设置 r.ReportsId。没有它,它就行不通。原因是,在我的数据库中(第二个,“在上下文中”),关键字段是 ReportsID 而不是 AssignmentGUID。因为在 Project Server 中(从我获取数据),关键字段是 AssignmentGUID,ReportsID 不存在。这就是为什么我通过 AssignmentGUID 进行搜索,然后确定对象的 ReportsID,并将其设置为与 AssignmentGUID 匹配的原始对象。
  • @Newbie1001 令人困惑的部分是您如何能够与报告来自的数据库与“reportdataindatabase”在相同的上下文中进行对话,您如何混合访问......您的示例循环报告,但此实体类型来自 OData API,然后您绑定到本地上下文(unitOfWorkTAFeedBack)。如果可以通过 dbcontext 访问,为什么还要使用 OData API?
  • @Newbie1001 更新问题并明确说明数据来自 Project Server,而不是数据库,并且您使用的是 OData。这个问题以目前的形式毫无意义。它甚至与 EF 无关——您使用的是 OData,根本不是实体框架
  • 无论如何,Parallel.ForEach 本身就是一个错误,会减慢速度。它甚至不需要。 OData 允许在查询中使用多个 ID。即使您并行执行 100 个 SELECT,Project Server 仍然必须针对其数据库执行 100 个 SELECT。对于任何操作,DbContext NOT 是线程安全的。即使是内存修改。并且在 DbContext 上放置一个仅重命名 EF 自己的方法的“存储库”充其量是浪费。
猜你喜欢
  • 2017-02-04
  • 1970-01-01
  • 2016-08-16
  • 2021-09-13
  • 2011-09-20
  • 1970-01-01
  • 2016-03-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多