C # MVC Прокручивать список и эффективно обновлять каждую запись - PullRequest
0 голосов
/ 20 февраля 2019

У меня есть список «сайтов», которые хранятся в моей базе данных.Список ОЧЕНЬ большой и содержит около 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 голосов
/ 20 февраля 2019

Похоже, вы используете 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 change tracker хранит ссылку на каждый объект из этого списка.Вы можете легко проверить, что с помощью

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), а также обновит только измененные поля записи.

0 голосов
/ 21 февраля 2019

Я последовал совету Ивана Стоева и изменил код, удалив DB.Find и EntitySate Modified - теперь это занимает около полутора минут по сравнению с 15 минутами раньше.Очень удивительно, так как я не знал, что вам на самом деле не требуется обновлять записи.Умная.Код сейчас:

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

            Debug.Write("Starting Site Update loop...");

            //Loop through sites and the OOB list and if they match then tell us
            //750 records takes around 15-20 minutes.

            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) )
                        {
                            sitedata.CabinOOB = oobdata.CabinOOB;
                            sitedata.TowerOOB = oobdata.TowerOOB;
                            sitedata.ManagedOOB = oobdata.ManageOOB;
                            sitedata.IssueDescription = oobdata.Description;
                            sitedata.TargetResolutionDate = oobdata.TargetResolutionDate;

                            Debug.Write("Thank you, next: " + sitedata.Id + "\n");
                        }
                    }

                }
            }

            DB.SaveChanges();
        }
0 голосов
/ 20 февраля 2019

Итак, прежде всего вы должны превратить ваш 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 (var oobdata) с текущим foreach (размещенным в sitedataWithCorrectNames)

То же самое относится и к SiteNo.Contains (OOBName)

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

PS Большинство db sdk также предоставляют асинхронные функции, поэтому используйте их также.PPS У меня не было IDE, поэтому я посмотрел код, но ссылки должны предоставить вам множество примеров.Ответьте, если вам нужна дополнительная помощь.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...