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?