C#MVC遍历列表并有效地更新每个记录

时间:2019-02-20 16:15:48

标签: c# performance entity-framework loops

我有一个存储在数据库中的“站点”列表。列表非常大,包含大约50,000多个记录。

我正在尝试遍历每条记录并对其进行更新。这需要花费很多时间,是否有更好,更有效的方法?

  using (IRISInSiteLiveEntities DB = new IRISInSiteLiveEntities())
        {
            var allsites = DB.Sites.ToList();

                foreach( var sitedata in allsites)
                {


                            var siterecord = DB.Sites.Find(sitedata.Id);

                            siterecord.CabinOOB = "Test";
                            siterecord.TowerOOB = "Test";
                            siterecord.ManagedOOB = "Test";
                            siterecord.IssueDescription = "Test";
                            siterecord.TargetResolutionDate = "Test";

                            DB.Entry(siterecord).State = EntityState.Modified;

                }

            DB.SaveChanges();
        }

我已经从代码中删节了。我正在使用的正确功能代码基本上是从Excel中拉出一个列表,然后匹配站点列表中的记录,并相应地更新每个记录。 DB.Find大大降低了循环速度。

 [HttpPost]
    public ActionResult UploadUpdateOOBList()
    {
        CheckPermissions("UpdateOOBList");

        string[] typesallowed = new string[] { ".xls", ".xlsx" };

        HttpPostedFileBase file = Request.Files[0];
        var fname = file.FileName;

        if (!typesallowed.Any(fname.Contains))
        {
            return Json("NotAllowed");
        }

        file.SaveAs(Server.MapPath("~/Uploads/OOB List/") + fname);

        //Create empty OOB data list
        List<OOBList.OOBDetails> oob_data = new List<OOBList.OOBDetails>();

        //Using ClosedXML rather than Interop Excel....
        //Interop Excel: 30 seconds for 750 rows
        //ClosedXML: 3 seconds for 750 rows
        string fileName = Server.MapPath("~/Uploads/OOB List/") + fname;
        using (var excelWorkbook = new XLWorkbook(fileName))
        {
            var nonEmptyDataRows = excelWorkbook.Worksheet(2).RowsUsed();

            foreach (var dataRow in nonEmptyDataRows)
            {
                //for row number check
                if (dataRow.RowNumber() >= 4 )
                {

                    string siteno = dataRow.Cell(1).GetValue<string>();
                    string sitename = dataRow.Cell(2).GetValue<string>();
                    string description = dataRow.Cell(4).GetValue<string>();
                    string cabinoob = dataRow.Cell(5).GetValue<string>();
                    string toweroob = dataRow.Cell(6).GetValue<string>();
                    string manageoob = dataRow.Cell(7).GetValue<string>();
                    string resolutiondate = dataRow.Cell(8).GetValue<string>();
                    string resolutiondate_converted = resolutiondate.Substring(resolutiondate.Length - 9);

                    oob_data.Add(new OOBList.OOBDetails
                    {
                        SiteNo = siteno,
                        SiteName = sitename,
                        Description = description,
                        CabinOOB = cabinoob,
                        TowerOOB = toweroob,
                        ManageOOB = manageoob,
                        TargetResolutionDate = resolutiondate_converted
                    });

                }
            }
        }

        //Now delete file.
        System.IO.File.Delete(Server.MapPath("~/Uploads/OOB List/") + fname);

        Debug.Write("DOWNLOADING LIST ETC....\n");

        using (IRISInSiteLiveEntities DB = new IRISInSiteLiveEntities())
        {
            var allsites = DB.Sites.ToList();

            //Loop through sites and the OOB list and if they match then tell us
            foreach( var oobdata in oob_data)
            {
                foreach( var sitedata in allsites)
                {

                    var indexof = sitedata.SiteName.IndexOf(' ');

                    if( indexof > 0 )
                    {
                        var OOBNo = oobdata.SiteNo;
                        var OOBName = oobdata.SiteName;
                        var SiteNo = sitedata.SiteName;
                        var split = SiteNo.Substring(0, indexof);

                        if (OOBNo == split && SiteNo.Contains(OOBName) )
                        {
                            var siterecord = DB.Sites.Find(sitedata.Id);

                            siterecord.CabinOOB = oobdata.CabinOOB;
                            siterecord.TowerOOB = oobdata.TowerOOB;
                            siterecord.ManagedOOB = oobdata.ManageOOB;
                            siterecord.IssueDescription = oobdata.Description;
                            siterecord.TargetResolutionDate = oobdata.TargetResolutionDate;

                            DB.Entry(siterecord).State = EntityState.Modified;

                            Debug.Write("Updated Site ID/Name Record: " + sitedata.Id + "/" + sitedata.SiteName);

                        }
                    }

                }
            }

            DB.SaveChanges();
        }

        var nowdate = DateTime.Now.ToString("dd/MM/yyyy");
        System.IO.File.WriteAllText(Server.MapPath("~/Uploads/OOB List/lastupdated.txt"),nowdate);

        return Json("Success");

    }

3 个答案:

答案 0 :(得分:2)

好像您正在使用Entity Framework(6或Core)。无论哪种情况,

var siterecord = DB.Sites.Find(sitedata.Id);

DB.Entry(siterecord).State = EntityState.Modified;

是多余的,因为siteData变量来自

var allsites = DB.Sites.ToList();

这不仅将整个Site表加载到内存中,而且EF更改跟踪器会保留对该列表中每个对象的引用。您可以使用

轻松地进行验证
var siterecord = DB.Sites.Find(sitedata.Id);
Debug.Assert(siterecord == sitedata);

Find(当数据已经在内存中时)和Entry方法本身很快。但是问题在于默认情况下它们会自动触发DetectChanges,从而导致二次时间复杂度-简单地说,就是很慢。

话虽如此,只需将它们删除:

if (OOBNo == split && SiteNo.Contains(OOBName))
{
    sitedata.CabinOOB = oobdata.CabinOOB;
    sitedata.TowerOOB = oobdata.TowerOOB;
    sitedata.ManagedOOB = oobdata.ManageOOB;
    sitedata.IssueDescription = oobdata.Description;
    sitedata.TargetResolutionDate = oobdata.TargetResolutionDate;

    Debug.Write("Updated Site ID/Name Record: " + sitedata.Id + "/" + sitedata.SiteName);    
}

通过这种方式,EF仅检测一次更改(在SaveChanges之前),并且仅更新已修改的记录字段。

答案 1 :(得分:0)

因此,首先您应该将HTTPPost设置为异步函数 更多信息https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

然后您应该做的是创建任务并将它们添加到列表中。然后,通过调用Task.WaitAll()

等待它们完成(如果您需要/需要)。

https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.waitall?view=netframework-4.7.2

这将使您的代码在多个线程上并行运行,从而大大优化了性能。

例如,您还可以使用linq通过执行大致如下所示的操作来减小所有站点的大小

var sitedataWithCorrectNames = allsites.Where(x => x //在这里评估您的状况)

https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/ef/language-reference/supported-and-unsupported-linq-methods-linq-to-entities

然后使用现在的foreach(sitedataWithCorrectNames中的sitedate)开始foreach(var oobdata)

SiteNo.Contains(OOBName)相同

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/getting-started-with-linq

P.S。大多数db sdk还提供异步功能,因此也请使用这些功能。 P.P.S.我没有IDE,所以我盯着代码,但是链接应该为您提供大量示例。如果您需要更多帮助,请回复。

答案 2 :(得分:0)

我遵循了Ivan Stoev的建议,并通过删除DB.Find和EntitySate Modified更改了代码-与之前的15分钟相比,现在花费了大约一分半钟。非常令人惊讶,因为我不知道您实际上并不需要更新记录。聪明。现在的代码是:

module.exports = (req, res, next) => {

    if (req.path == "/business") {
        res.status(201);

        res.jsonp({
            id: "a23e1b13-cf69-461c-aa8a-a0eb99e41350",
            name: req.body['name'],
            revision: "1"
        });

        next();
    }
    else {
        next();
    }
}