/// /////////////////////////////////////////////////////////////////////// /// ReadData /// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="fileName"> /// Name of the file associated with the stream. /// null if there is no such file. /// Used solely when throwing an exception. /// </param> /// <param name="stream"> /// All data is read from this stream. /// /// This is a StreamReader rather then a TextReader, /// because we need to be able to seek back to the start of the /// stream, and you can't do that with a TextReader (or s StringReader). /// </param> /// <param name="fileDescription"></param> /// <returns></returns> private IEnumerable <T> ReadData <T>( string fileName, StreamReader stream, CsvFileDescription fileDescription) where T : class, new() { // If T implements IDataRow, then we're reading raw data rows bool readingRawDataRows = typeof(IDataRow).IsAssignableFrom(typeof(T)); // The constructor for FieldMapper_Reading will throw an exception if there is something // wrong with type T. So invoke that constructor before you open the file, because if there // is an exception, the file will not be closed. // // If T implements IDataRow, there is no need for a FieldMapper, because in that case we're returning // raw data rows. FieldMapper_Reading <T> fm = null; if (!readingRawDataRows) { fm = new FieldMapper_Reading <T>(fileDescription, fileName, false); } // ------- // Each time the IEnumerable<T> that is returned from this method is // accessed in a foreach, ReadData is called again (not the original Read overload!) // // So, open the file here, or rewind the stream. bool readingFile = !string.IsNullOrEmpty(fileName); if (readingFile) { stream = new StreamReader( fileName, fileDescription.TextEncoding, fileDescription.DetectEncodingFromByteOrderMarks); } else { // Rewind the stream if ((stream == null) || (!stream.BaseStream.CanSeek)) { throw new BadStreamException(); } stream.BaseStream.Seek(0, SeekOrigin.Begin); } // ---------- CsvStream cs = new CsvStream(stream, null, fileDescription.SeparatorChar); // If we're reading raw data rows, instantiate a T so we return objects // of the type specified by the caller. // Otherwise, instantiate a DataRow, which also implements IDataRow. IDataRow row = null; if (readingRawDataRows) { row = new T() as IDataRow; } else { row = new DataRow(); } AggregatedException ae = new AggregatedException(typeof(T).ToString(), fileName, fileDescription.MaximumNbrExceptions); try { bool firstRow = true; while (cs.ReadRow(ref row)) { // Skip empty lines. // Important. If there is a newline at the end of the last data line, the code // thinks there is an empty line after that last data line. if ((row.Count == 1) && ((row[0].Value == null) || (string.IsNullOrEmpty(row[0].Value.Trim())))) { continue; } if (firstRow && fileDescription.FirstLineHasColumnNames) { if (!readingRawDataRows) { fm.ReadNames(row); } } else { T obj = default(T); try { if (readingRawDataRows) { obj = row as T; } else { obj = fm.ReadObject(row, ae); } } catch (AggregatedException ae2) { // Seeing that the AggregatedException was thrown, maximum number of exceptions // must have been reached, so rethrow. // Catch here, so you don't add an AggregatedException to an AggregatedException throw ae2; } catch (Exception e) { // Store the exception in the AggregatedException ae. // That way, if a file has many errors leading to exceptions, // you get them all in one go, packaged in a single aggregated exception. ae.AddException(e); } yield return(obj); } firstRow = false; } } finally { if (readingFile) { stream.Close(); } // If any exceptions were raised while reading the data from the file, // they will have been stored in the AggregatedException ae. // In that case, time to throw ae. ae.ThrowIfExceptionsStored(); } }
/// /////////////////////////////////////////////////////////////////////// /// ReadObject /// /// <summary> /// Creates an object of type T from the data in row and returns that object. /// /// </summary> /// <param name="row"></param> /// <param name="ae"></param> /// <returns></returns> public T ReadObject(IDataRow row, AggregatedException ae) { if (row.Count > IndexToInfo.Length) { // Too many fields throw new TooManyDataFieldsException(typeof(T).ToString(), row[0].LineNbr, FileName); } // ----- T obj = new T(); for (int i = 0; i < row.Count; i++) { TypeFieldInfo tfi = IndexToInfo[i]; if (FileDescription.EnforceCsvColumnAttribute && (!tfi.HasColumnAttribute)) { // enforcing column attr, but this field/prop has no column attr. // So there are too many fields in this record. throw new TooManyNonCsvColumnDataFieldsException(typeof(T).ToString(), row[i].LineNbr, FileName); } // ----- if ((!FileDescription.FirstLineHasColumnNames) && (tfi.Index == CsvColumnAttribute.McDefaultFieldIndex)) { // First line in the file does not have field names, so we're // depending on the FieldIndex of each field in the type // to ensure each value is placed in the correct field. // However, now hit a field where there is no FieldIndex. throw new MissingFieldIndexException(typeof(T).ToString(), row[i].LineNbr, FileName); } // ----- // value to put in the object string value = row[i].Value; if (value == null) { if (!tfi.CanBeNull) { ae.AddException( new MissingRequiredFieldException( typeof(T).ToString(), tfi.Name, row[i].LineNbr, FileName)); } } else { try { Object objValue; // Normally, either tfi.typeConverter is not null, // or tfi.parseNumberMethod is not null. // if (tfi.TypeConverter != null) { objValue = tfi.TypeConverter.ConvertFromString( null, FileDescription.FileCultureInfo, value); } else if (tfi.ParseNumberMethod != null) { objValue = tfi.ParseNumberMethod.Invoke( tfi.FieldType, new Object[] { value, tfi.InputNumberStyle, FileDescription.FileCultureInfo }); } else { // No TypeConverter and no Parse method available. // Try direct approach. objValue = value; } if (tfi.MemberInfo is PropertyInfo) { ((PropertyInfo)tfi.MemberInfo).SetValue(obj, objValue, null); } else { ((FieldInfo)tfi.MemberInfo).SetValue(obj, objValue); } } catch (Exception e) { if (e is TargetInvocationException) { e = e.InnerException; } if (e is FormatException) { e = new WrongDataFormatException( typeof(T).ToString(), tfi.Name, value, row[i].LineNbr, FileName, e); } ae.AddException(e); } } } // Visit any remaining fields in the type for which no value was given // in the data row, to see whether any of those was required. // If only looking at fields with CsvColumn attribute, do ignore // fields that don't have that attribute. for (int i = row.Count; i < IndexToInfo.Length; i++) { TypeFieldInfo tfi = IndexToInfo[i]; if (((!FileDescription.EnforceCsvColumnAttribute) || tfi.HasColumnAttribute) && (!tfi.CanBeNull)) { ae.AddException( new MissingRequiredFieldException( typeof(T).ToString(), tfi.Name, row[row.Count - 1].LineNbr, FileName)); } } return(obj); }