/// <inheritdoc /> public Task <IEnumerable <DomainModels.ModelsBase> > ReadAsync( ConfigurationFile configurationFile, string spreadsheetFile, CancellationToken cancellationToken) { List <DomainModels.ModelsBase> toReturn = new List <DomainModels.ModelsBase>(); if (configurationFile == null) { throw new ArgumentNullException(nameof(configurationFile)); } FileInfo fileInfo = new FileInfo(spreadsheetFile); long firstRow = configurationFile.FirstRow; long lastRow = configurationFile.LastRow; DomainModels.ModelsBase rowInstance = null; using (FileStream fileStream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) { using (IExcelDataReader excelDataReader = ExcelReaderFactory.CreateReader(fileStream)) { // Read the spreadsheet into memory and... DataSet dataSet = excelDataReader.AsDataSet(); // Select the right 'sheet'... string sheetName = configurationFile.SheetName; DataTable dataTable = dataSet.Tables[sheetName]; DataRow dataRow = null; // 2) While we're not on the last row... this.loggerWrapper.Debug("Parsing spreadsheet..."); for (int i = 0; (i < dataTable.Rows.Count) && (i <= lastRow); i++) { dataRow = dataTable.Rows[i]; // 1) Go to the first row. if (i >= (firstRow - 1)) { rowInstance = this.ReadRowAsync( configurationFile, i, dataRow); toReturn.Add(rowInstance); } } } } this.SaveExcelParseFailureLog(); return(Task.FromResult <IEnumerable <DomainModels.ModelsBase> >( toReturn)); }
private DomainModels.ModelsBase ReadRowAsync( ConfigurationFile configurationFile, int row, DataRow dataRow) { DomainModels.ModelsBase toReturn = null; List <DomainModels.ModelsBase> modelParts = new List <DomainModels.ModelsBase>(); // 3) Iterate through the configuration file and... DomainModels.ModelsBase modelsBase = null; foreach (ColumnMappingConfiguration columnMappingConfiguration in configurationFile.ColumnMappingConfigurations) { // .. instantiate new instances, based on the requested types. modelsBase = this.CreateAndPopulateModelsBaseInstance( columnMappingConfiguration, row, dataRow); modelParts.Add(modelsBase); } // Join the models up, based on the instance type present. // It can only be one. modelsBase = modelParts .SingleOrDefault(x => x is DomainModels.SchoolInformation); if (modelsBase != null) { // Then stitch up into a proper SchoolInformation instance. DomainModels.SchoolInformation schoolInformation = modelsBase as DomainModels.SchoolInformation; schoolInformation.BaselineFunding = modelParts .Select(x => x as DomainModels.Rates.BaselineFunding) .Single(x => x != null); schoolInformation.NotionalFunding = modelParts .Select(x => x as DomainModels.Rates.NotionalFunding) .Single(x => x != null); // Illustrative funding isn't available on the 2020 // spreadsheet. schoolInformation.IllustrativeFunding = modelParts .Select(x => x as DomainModels.Rates.IllustrativeFunding) .SingleOrDefault(x => x != null); toReturn = schoolInformation; } modelsBase = modelParts .SingleOrDefault(x => x is DomainModels.LocalAuthorityInformation); if (modelsBase != null) { // Then stitch up into a property LocalAuthorityInformation // instance. DomainModels.LocalAuthorityInformation localAuthorityInformation = modelsBase as DomainModels.LocalAuthorityInformation; localAuthorityInformation.ProvisionalFunding = modelParts .Select(x => x as DomainModels.Rates.ProvisionalFunding) .Single(x => x != null); toReturn = localAuthorityInformation; } if (modelsBase == null) { throw new NotImplementedException( $"No model of type " + $"{nameof(DomainModels.SchoolInformation)} present. Is " + $"there a different high-level entity we need to " + $"implement?"); } return(toReturn); }
private DomainModels.ModelsBase CreateAndPopulateModelsBaseInstance( ColumnMappingConfiguration columnMappingConfiguration, int row, DataRow dataRow) { DomainModels.ModelsBase toReturn = null; // 4) Use reflection to cycle through the properties, check if // it's in the configuration, if it is, read from the specified // column. Then set it. string modelType = columnMappingConfiguration.ModelType; Type type = this.allDomainModelTypes .Single(x => x.FullName == modelType); toReturn = (DomainModels.ModelsBase)Activator.CreateInstance(type); // Now stitch it up. Dictionary <string, int> columnMappings = columnMappingConfiguration.ColumnMappings; PropertyInfo propertyInfo = null; object value = null; Type actualModelType = null; Type actualValueType = null; int column; foreach (KeyValuePair <string, int> keyValuePair in columnMappings) { propertyInfo = type.GetProperty(keyValuePair.Key); actualModelType = propertyInfo.PropertyType; // We're at the mercy of ExcelDataReader with regards to the // types that come out. Therefore, we may need to perfrom some // parsing. If the types match, we're good, though. column = keyValuePair.Value; value = dataRow[column]; actualValueType = value.GetType(); if (actualModelType != actualValueType) { value = this.TryParsingUnboxedValue( actualModelType, row, column, value); } else { // Do nothing - the value is the required type. } if (value != null) { propertyInfo.SetValue(toReturn, value); } } return(toReturn); }
/// <inheritdoc /> public async Task <ProcessResponse> ProcessAsync( ProcessRequest processRequest, CancellationToken cancellationToken) { ProcessResponse toReturn = null; if (processRequest == null) { throw new ArgumentNullException(nameof(processRequest)); } // 1) Deserialise config. string configFile = processRequest.ConfigFile; ConfigurationFile configurationFile = await this.configurationFileReader.ReadAsync( configFile, cancellationToken) .ConfigureAwait(false); // 2) Take mappings and call spreadsheet parser. string spreadsheetFile = processRequest.SpreadsheetFile; IEnumerable <DomainModels.ModelsBase> modelsBases = await this.spreadsheetReader.ReadAsync( configurationFile, spreadsheetFile, cancellationToken) .ConfigureAwait(false); // 3) Take each entity and store in storage. // Clear storage first. await this.schoolInformationStorageAdapter .CreateTableAsync(cancellationToken) .ConfigureAwait(false); int year = configurationFile.Year; DomainModels.ModelsBase[] modelsBaseArray = modelsBases.ToArray(); int length = modelsBaseArray.Length; DomainModels.ModelsBase modelsBase = null; DomainModels.SchoolInformation schoolInformation = null; DomainModels.LocalAuthorityInformation localAuthorityInformation = null; for (int i = 0; i < length; i++) { modelsBase = modelsBaseArray[i]; if (modelsBase is DomainModels.SchoolInformation) { schoolInformation = modelsBase as DomainModels.SchoolInformation; if (schoolInformation.Urn.HasValue) { await this.schoolInformationStorageAdapter.CreateAsync( year, schoolInformation, cancellationToken) .ConfigureAwait(false); } else { this.loggerWrapper.Warning( $"A {nameof(DomainModels.SchoolInformation)} " + $"instance could not be inserted, as there " + $"wasn't a " + $"{nameof(DomainModels.SchoolInformation.Urn)}."); } } else if (modelsBase is DomainModels.LocalAuthorityInformation) { localAuthorityInformation = modelsBase as DomainModels.LocalAuthorityInformation; if (localAuthorityInformation.LaNumber.HasValue) { await this.localAuthorityInformationStorageAdapter.CreateAsync( year, localAuthorityInformation, cancellationToken) .ConfigureAwait(false); } else { this.loggerWrapper.Warning( $"A " + $"{nameof(DomainModels.LocalAuthorityInformation)} " + $"instance could not be inserted, as there " + $"wasn't an " + $"{nameof(DomainModels.LocalAuthorityInformation.LaNumber)}."); } } else { throw new NotImplementedException( "Storage not configured for this type. Please " + "implement."); } this.ReportInsertionProgress(i, length); } return(toReturn); }