Speeding up formatting of excel sheet using ClosedXML

30 Views Asked by At

For the creation and formatting of my excel sheet I have these methods for formatting the worksheet:

    private void StylesRatesWorksheet(IXLWorksheet ratesWorksheet, List<object> rateTypesTurnedOn, List<string> flexibleRateProperties)
    {

        IXLRange ratesRange = ratesWorksheet.RangeUsed();
        IXLTable ratesTable = ratesRange.AsTable();

        ratesWorksheet.Columns().AdjustToContents();

        IXLCell scheduleCodeColumn = ratesTable.HeadersRow().CellsUsed(c => c.Value.ToString() == "Schedule Code").Single();

        //Go through Schedule code column and add a new blank row when the code changes 
        HashSet<string> encounteredScheduleCodes = new HashSet<string>();
        string firstScheduleCodeValue = ratesTable.Row(scheduleCodeColumn.Address.RowNumber + 1).Cell(scheduleCodeColumn.Address.ColumnNumber).Value.ToString();

        foreach (IXLTableRow row in ratesTable.DataRange.RowsUsed())
        {
            // Retrieve the value of the current cell outside the loop
            string currentScheduleCode = row.Cell(scheduleCodeColumn.Address.ColumnNumber).Value.ToString();

            // Check if the schedule code is encountered for the first time
            if (encounteredScheduleCodes.Add(currentScheduleCode))
            {
                // Check if the encountered schedule code is not the first one
                if (currentScheduleCode != firstScheduleCodeValue)
                    //if not, insert blank row
                    ratesTable.Row(row.RowNumber()).InsertRowsAbove(1).First();           
            }
        }

        List<string> distinctScheduleCodes = ratesTable.DataRange.RowsUsed().Select(row => row.Cell(scheduleCodeColumn.Address.ColumnNumber).Value.ToString()).Distinct().ToList();
        
        //Go through each unique schedule code, find out which rate types are turned on and make the appropriate cells editable if there are any
        foreach (string scheduleCode in distinctScheduleCodes)
        {
            //Find the RateType object that matches the current schedule code
            object matchingRateType = rateTypesTurnedOn
                .Single(rt =>
                {
                    PropertyInfo scheduleCodeProp = rt.GetType().GetProperty("ScheduleCode");
                    //Check if the property value matches the schedule code
                    return scheduleCodeProp.GetValue(rt)?.ToString() == scheduleCode;
                });

            if (matchingRateType != null)
            {
                PropertyInfo rateTypeProp = matchingRateType.GetType().GetProperty("RateTypes");

                List<string> rateTypes = (List<string>)rateTypeProp.GetValue(matchingRateType);

                //Find the rows in the worksheet where the value in the column "Schedule Code" matches the schedule code
                IEnumerable<IXLTableRow> matchingRows = ratesTable.DataRange.RowsUsed()
                    .Where(row => row.Cell(scheduleCodeColumn.Address.ColumnNumber).Value.ToString() == scheduleCode);

                foreach (string rateType in rateTypes)
                {
                    // For each rate type, find matching cells in the header row where the header contains the rate type
                    matchingRows.SelectMany(matchingRow =>
                        ratesTable.HeadersRow().CellsUsed(c => c.Value.ToString().Contains(rateType, StringComparison.OrdinalIgnoreCase))
                            // Create an anonymous object with ColumnNumber and MatchingCell properties
                            .Select(cell => new { cell.Address.ColumnNumber, MatchingCell = matchingRow.Cell(cell.Address.ColumnNumber) }))
                        .ToList()
                        // For each entry, unlock the corresponding cell in the matching row
                        .ForEach(entry => entry.MatchingCell.Style.Protection.SetLocked(false));
                }
            }
        }

        List<string> codeHeaders = new List<string> { "Rates Code", "Schedule Code", "R6 Code", "R7 Code", "DICODE" };
        string includedColumn = "Included";
        int lastRowUsed = ratesWorksheet.LastRowUsed().RowNumber();

        foreach (IXLCell headerCell in ratesTable.HeadersRow().CellsUsed())
        {
            string columnHeader = headerCell.Value.ToString();

            if (flexibleRateProperties.Contains(columnHeader))
            {
                headerCell.Style.Fill.BackgroundColor = GetRandomColour();
                string flexibleRateColumnLetter = headerCell.WorksheetColumn().ColumnLetter();
                //setting the range of cells to be affected, e.g A1:A40
                IXLRange flexibleRateValidationRange = ratesWorksheet.Range(
                    flexibleRateColumnLetter + (ratesTable.HeadersRow().RowNumber() + 1) + ":" + flexibleRateColumnLetter + lastRowUsed);

                //setting format for cells to decimal places
                flexibleRateValidationRange.Style.NumberFormat.NumberFormatId = 2;

                //only allowing numbers to be entered
                IXLDataValidation flexibleRateDataValidation = flexibleRateValidationRange.CreateDataValidation();
                flexibleRateDataValidation.Custom("ISNUMBER(VALUE(INDIRECT(ADDRESS(ROW(), COLUMN()))))");
                flexibleRateDataValidation.ErrorStyle = XLErrorStyle.Stop;
                flexibleRateDataValidation.ErrorTitle = "Not a number";
                flexibleRateDataValidation.ErrorMessage = "Cell can only contain numbers";

                //setting protected cells within flexible columns to have the colour grey 
                IXLTableField flexibleColumn = ratesTable.Field(columnHeader);

                int columnIndex = flexibleColumn.Index + 1;
                IXLCells cellsInFlexibleColumn = ratesTable.DataRange.RowsUsed().Cells();

                foreach (IXLCell cell in cellsInFlexibleColumn.Where(c => c.WorksheetColumn().ColumnNumber() == columnIndex && c.Style.Protection.Locked))
                {
                    cell.Style.Fill.BackgroundColor = XLColor.Gray;
                }
            }
            else
            {
                headerCell.Style.Fill.BackgroundColor = XLColor.LightYellow;
            }

            if (columnHeader.ToLower() == includedColumn.ToLower())
            {
                string includedColumnLetter = ratesTable.HeadersRow().CellsUsed(c => c.Value.ToString() == "Included").Single().WorksheetColumn().ColumnLetter();

                //Setting each cell within included column to have a dropdown with the option of yes or no
                IXLRangeColumn column = ratesTable.Column(includedColumnLetter);
                List<string> options = new List<string> { "Yes", "No" };
                string validOptions = $"\"{string.Join(",", options)}\"";
                IXLCells nonEmptyCells = column.CellsUsed(XLCellsUsedOptions.Contents);

                foreach(IXLCell cell in nonEmptyCells)
                {
                    cell.CreateDataValidation().List(validOptions, true);
                    cell.Style.Fill.BackgroundColor = XLColor.LightGreen;
                    cell.Style.Protection.SetLocked(false);
                }
            }

            if (codeHeaders.Contains(columnHeader))
            {
                string codeColumnLetter = headerCell.WorksheetColumn().ColumnLetter();
                IXLCells cellsUnderCodeColumn = ratesTable.Column(codeColumnLetter).CellsUsed(c => c.Address.RowNumber > ratesTable.HeadersRow().RowNumber());
                cellsUnderCodeColumn.Style.Fill.BackgroundColor = XLColor.LightYellow;
            }
        }

        //Going through each cell within the worksheet and setting the border
        IXLCells allCells = ratesWorksheet.Cells();
        allCells.Style.Border.SetOutsideBorder(XLBorderStyleValues.Thin);
        allCells.Style.Border.SetOutsideBorderColor(XLColor.LightGray);
    }

    private static XLColor lastColour;
    private static XLColor firstColour;

    private XLColor GetRandomColour()
    {
        List<XLColor> availableColours = new List<XLColor>
        {
            XLColor.Blue,
            XLColor.Orange,
            XLColor.Green,
            XLColor.Red
        };

        //Insert removed colour back to start of list
        if (!availableColours.Contains(firstColour))
            availableColours.Insert(0, firstColour);

        // Remove the last colour from the available colours to avoid repetition
        availableColours.Remove(lastColour);

        // Get a random colour from the remaining available colours
        Random random = new Random();
        XLColor randomColour = availableColours[random.Next(availableColours.Count)];

        // Set the last and first colour to the selected colour
        lastColour = randomColour;
        firstColour = randomColour;

        return randomColour;
    }

What am I trying to do is see if there is anything I can do to improve the performance of the StyleRatesWorksheet method? Depending on the amount of data getting inserted it can take between 3-5 minutes to run. When I don't include the formatting, the spreadsheet is created in less than 10 seconds so it's this that is slowing it down. Is there anything obvious here I can do to get it to run quicker?

0

There are 0 best solutions below