Необходимо справиться с тяжелым ответом Linq-2-SQL в фоновом режиме - PullRequest
1 голос
/ 04 июня 2011

У меня тут небольшая дилемма.У меня есть эта настройка с включенным BackgroundWorker:

    #region Locum List
    private Object DoWorkMain_LocumList() {
        #region Query
        var locums = from locum in DbContext.Locums
                                 where
                                    locum.IsActive == true &&
                                    locum.IsAdminMarkedComplete == true &&
                                    locum.IsLocumsExciteBan == false &&
                                    locum.IsGPHCBan == false &&
                                    locum.LocumWorkingPreferenceID == 1
                                 select new {
                                     LocumID = locum.OID,
                                     LocumName = locum.FirstName + " " + locum.LastName,
                                     locum.MobileNumber,
                                     locum.Email,
                                     Gender = locum.Gender ? "Male" : "Female",
                                     locum.DateofBirth,
                                     LocumType = locum.LocumType.Name,
                                     Distance = DbContext.GetDistanceFromCache(TextAddressPostCode.Text.Trim(), locum.AddressInfo.Postcode),
                                     Address = String.Format("{0} {1} {2} {3}",
                                                            locum.AddressInfo.House.Length == 0 ? String.Empty : locum.AddressInfo.House + ", ",
                                                            locum.AddressInfo.Street.Length == 0 ? String.Empty : locum.AddressInfo.Street + ", ",
                                                            locum.AddressInfo.Area.Length == 0 ? String.Empty : locum.AddressInfo.Area + ", ",
                                                            locum.AddressInfo.Postcode ?? String.Empty),
                                     Postcode = locum.AddressInfo.Postcode,
                                     City = locum.AddressInfo.City.Name,
                                     County = locum.AddressInfo.City.County.Name,
                                     locum.SystemUserID,
                                     Status = DbContext.GetJobPermanentProcessLatestStatus(VaccanyID, locum.OID)
                                 };
        #endregion

        DataTable LocumListX = new DataTable("LocumList");
        DataColumn PrimaryColumn = LocumListX.Columns.Add("LocumID", typeof(Int64));
        LocumListX.Columns.Add("LocumName", typeof(String));
        LocumListX.Columns.Add("MobileNumber", typeof(String));
        LocumListX.Columns.Add("Email", typeof(String));
        LocumListX.Columns.Add("Gender", typeof(String));
        LocumListX.Columns.Add("DateofBirth", typeof(DateTime));
        LocumListX.Columns.Add("LocumType", typeof(String));
        LocumListX.Columns.Add("Distance", typeof(Decimal));
        LocumListX.Columns.Add("Address", typeof(String));
        LocumListX.Columns.Add("Postcode", typeof(String));
        LocumListX.Columns.Add("City", typeof(String));
        LocumListX.Columns.Add("County", typeof(String));
        LocumListX.Columns.Add("SystemUserID", typeof(Int64));
        LocumListX.Columns.Add("Status", typeof(String));

        LocumListX.PrimaryKey = new DataColumn[] { PrimaryColumn };

        int iCurrentRowIndex = 0;

        #region DataTable
        int LocumListXRowsCount = locums.Count();

        foreach (var item in locums) {
            DataRow newRow = LocumListX.NewRow();

            newRow["LocumID"] = item.LocumID;
            newRow["LocumName"] = item.LocumName;
            newRow["MobileNumber"] = item.MobileNumber;
            newRow["Email"] = item.Email;
            newRow["Gender"] = item.Gender;
            newRow["DateofBirth"] = item.DateofBirth;
            newRow["LocumType"] = item.LocumType;
            newRow["Distance"] = item.Distance;
            newRow["Address"] = item.Address;
            newRow["Postcode"] = item.Postcode;
            newRow["City"] = item.City;
            newRow["County"] = item.County;
            newRow["SystemUserID"] = item.SystemUserID;
            newRow["Status"] = item.Status;

            LocumListX.Rows.Add(newRow);

            iCurrentRowIndex++;
            BackgroundWorkerLocumList.ReportProgress((int)(iCurrentRowIndex * 100F / (LocumListXRowsCount - 1)));
        }
        #endregion

        LocumListXRowsCount = LocumListX.Rows.Count;
        iCurrentRowIndex = 0;

        foreach (DataRow Row in LocumListX.Rows) {
            if (Convert.ToDecimal(Row["Distance"]) >= 0) {
                iCurrentRowIndex++;
                continue;
            }

            Row["Distance"] = GetDistanceBetween(Row);

            iCurrentRowIndex++;
            BackgroundWorkerLocumList.ReportProgress((int)(iCurrentRowIndex * 100F / (LocumListXRowsCount - 1)));

            if (BackgroundWorkerLocumList.CancellationPending) {
                return null;
            }
        }

        return LocumListX;
    }

    private void BackgroundWorkerLocumList_DoWork(object sender, DoWorkEventArgs e) {
        BackgroundWorker backgroundWorkerLocumList = sender as BackgroundWorker;

        try {
            if (backgroundWorkerLocumList != null) {
                backgroundWorkerLocumList.ReportProgress(0);

                if (backgroundWorkerLocumList.CancellationPending) {
                    e.Cancel = true;
                    return;
                }

                e.Result = DoWorkMain_LocumList();
            }
        }
        catch (Exception ex) {
            e.Result = ex;
        }
    }

    private void BackgroundWorkerLocumList_ProgressChanged(object sender, ProgressChangedEventArgs e) {
        ProgressBarLocumList.EditValue = e.ProgressPercentage;
    }

    private void BackgroundWorkerLocumList_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
        LoadLocumList_ResetProgress();

        if (e.Cancelled) {
            XtraMessageBox.Show("The task has been canceled");
        }
        else if (e.Error != null) {
            FormHelpers.ShowErrorMessageBox("Error while looking up distances.", Text, e.Error);
        }
        else {
            #region Grid
            GridLocumList.DataSource = e.Result as DataTable;
            LoadLocumListGridSetup();
            #endregion
        }

        ButtonRefreshLocumList.Enabled = true;
    }
    #endregion

Что происходит, если исходный LINQ должен возвращать от 1000 до 1500 записей.Поэтому, когда на более позднем этапе я выполняю For For для преобразования LINQ в DataTable (foreach (var item in locums)), я сталкиваюсь с серьезными паузами, и вскоре поток просто умирает без какого-либо предупреждения.Я вручную преобразовываю LINQ в DataTable, потому что раньше я использовал метод расширения MoreLinq, но это также заняло очень много времени, поскольку отчеты о ходе работ не требовались, а моему клиенту это не нравится.

У меня есть две идеи и третья вещьЭто сильная головная боль.

One: Если я могу перечислить результат с предложением where.уникальный столбец - LocumID.Если я смогу создать Список всех LocumID из результатов 1000-1500, а затем использовать Для каждого в Списке LocumID, чтобы я имел дело с одной строкой LINQ за раз, чтобы построить свой DataTable.

Два: Реализуйте .Skip () и .Take () для обработки данных в виде кусков по 50.

Есть предложения?

С уважением.

1 Ответ

2 голосов
/ 08 июня 2011

Поскольку многопоточные приложения не следует воспринимать легкомысленно, я просто дам схему того, как я мог бы думать об этой проблеме и избегать использования кода, когда возникают проблемы с параллелизмом.

По сути, я бы начал с коллекции ( ObservableCollection была бы удобна ) для хранения фактической коллекции объектов, которые будет содержать ваша сетка.

Затем в рабочем потоке я бы запустил ваш цикл для загрузки данных. Использование .Skip () /. Take () с индексатором для отслеживания вашей текущей страницы:

...

const int PageSize = 50;  // Or whatever you find works best, of course
int CurrentPage = 0;
int ReturnedCount = 0;

do
{
    var ReturnedData = GetNextSetOfData (CurrentPage, PageSize);
    ReturnedCount = ReturnedData.Count ();

    StoreReturnedData (ReturnedData);

    ++CurrentPage;
}
while (ReturnedCount > 0)

...

object GetNextSetOfData (int Page, int PageSize)
{
    var MyQuery = // Place your LINQ query here and add
                  // .Skip (Page * PageSize).Take (PageSize)
                  // and return the result;

    return MyQuery;
}

...

void StoreReturnedData (object Data)
{
    // Append your data to the end of the ObservableCollection (or whatever you're using)
    // here.  Be sure to use locks where you need to and all the tools to communicate
    // concurrently with your program
}

Если вы идете по наблюдаемому маршруту, вы можете просто обработать обновление, взять объекты, создать из них строки, а затем добавить их в DataTable и затем удалить их из ObservableCollection. Вы можете сделать DataTable доступным только для чтения, пока вы загружаете данные, чтобы пользователь, конечно, не стал связываться с данными, когда вы добавляете строки.

Проблема с этим, однако, в том, что вы имеете дело с параллелизмом, который, как вы знаете, может быть легко испорчен, и я не специалист по данному вопросу, поэтому я избегу любых попыток поговорить об этом .

Поскольку ваш запрос довольно громоздкий и его так часто называют, я бы также посмотрел на создание скомпилированного запроса , чтобы ускорить его еще немного.

...