/// <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);
        }