Click here to Skip to main content
15,881,781 members
Articles / Programming Languages / C#

Cinchoo ETL - XML Writer

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
4 Mar 2017CPOL22 min read 8.3K   8  
Simple XML Writer for .NET

Contents

  1. Introduction
  2. Requirement
  3. "Hello World!" Sample
    1. Quick write - Data First Approach
    2. Code First Approach
    3. Configuration First Approach
  4. Writing All Records
  5. Write Records Manually
  6. Customize XML Record
  7. Customize XML Fields
    1. DefaultValue
    2. ChoFallbackValue
    3. Type Converters
    4. Validations
  8. Callback Mechanism
    1. BeginWrite
    2. EndWrite
    3. BeforeRecordWrite
    4. AfterRecordWrite
    5. RecordWriteError
    6. BeforeRecordFieldWrite
    7. AfterRecordFieldWrite
    8. RecordWriteFieldError
  9. Customization
  10. Using Dynamic Object
  11. Exceptions
  12. Using MetadataType Annotation
  13. Configuration Choices
    1. Manual Configuration
    2. Auto Map Configuration
    3. Attaching MetadataType class
  14. ToText Helper Method
  15. Writing DataReader Helper Method
  16. Writing DataTable Helper Method
  17. Advanced Topics
    1. Override Converters Format Specs
    2. Currency Support
    3. Enum Support
    4. Boolean Support
    5. DateTime Support
  18. History

1. Introduction

ChoETL is an open source ETL (extract, transform and load) framework for .NET. It is a code based library for extracting data from multiple sources, transforming, and loading into your very own data warehouse in .NET environment. You can have data in your data warehouse in no time.

This article talks about using XmlWriter component offered by ChoETL framework. It is a simple utility class to save XML data to a file.

UPDATE: Corresponding XmlReader article can be found here.

Features

  • Follows XML standard file rules. Gracefully handles data fields that contain commas and line breaks.
  • In addition to comma, most delimiting characters can be used, including tab delimited fields.
  • Supports culture specific date, currency and number formats while generating files.
  • Supports different character encoding.
  • Provides fine control of date, currency, enum, boolean, number formats when writing files.
  • Detailed and robust error handling, allowing you to quickly find and fix problems.
  • Shortens your development time.

2. Requirement

This framework library is written in C# using .NET 4.5 Framework.

3. "Hello World!" Sample

  • Open VS.NET 2013 or higher
  • Create a sample VS.NET (.NET Framework 4.5) Console Application project
  • Install ChoETL via Package Manager Console using Nuget Command: Install-Package ChoETL
  • Use the ChoETL namespace

Let's begin by looking into a simple example of generating the below XML file having two nodes.

Listing 3.1 Sample XML Data File
XML
<Employees>
    <Employee>
        <Id>1</Id>
        <Name>Tom</Name>
    </Employee>
    <Employee>
        <Id>1</Id>
        <Name>Mark</Name>
    </Employee>
</Employees>

There are a number of ways you can get the XML file to be created with minimal setup.

3.1. Quick write - Data First Approach

This is the zero-config and quickest way to create XML file in no time. No typed POCO object is needed. The sample code below shows how to generate sample XML file using dynamic objects.

Listing 3.1.1 Write List of Objects to XML File
C#
List<ExpandoObject> objs = new List<ExpandoObject>();
dynamic rec1 = new ExpandoObject();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
dynamic rec2 = new ExpandoObject();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoXmlWriter("Emp.xml").WithXPath("Employees/Employee"))
{
    parser.Write(objs);
}

In the above sample, we give the list of objects to XmlWriter at one pass to write them to XML file.

Listing 3.1.2 Write Each Object to XML File
C#
using (var parser = new ChoXmlWriter("Emp.xml").WithXPath("Employees/Employee"))
{
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 1;
    rec1.Name = "Mark";
    parser.Write(item);

    dynamic rec1 = new ExpandoObject();
    rec1.Id = 2;
    rec1.Name = "Jason";
    parser.Write(item);
}

In the above sample, we take control of constructing, passing each and individual record to the XmlWriter to generate the XML file using Write overload.

3.2. Code First Approach

This is another zeo-config way to generate XML file using typed POCO class. First, define a simple POCO class to match the underlying XML file layout.

Listing 3.2.1 Simple POCO Entity Class
C#
public partial class EmployeeRecSimple
{
    public int Id { get; set; }
    public string Name { get; set; } 
}

In the above, the POCO class defines two properties matching the sample XML file template.

Listing 3.2.2 Saving to XML File
C#
List<EmployeeRecSimple> objs = new List<EmployeeRecSimple>();

EmployeeRecSimple rec1 = new EmployeeRecSimple();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRecSimple rec2 = new EmployeeRecSimple();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoXmlWriter<EmployeeRecSimple>
      ("Emp.xml").WithXPath("Employees/Employee"))
{
    parser.Write(objs);
}

The above sample shows how to create XML file from typed POCO class objects.

3.3. Configuration First Approach

In this model, we define the XML configuration with all the necessary parameters along with XML columns required to generate the sample XML file.

Listing 3.3.1 Define XML Configuration
C#
ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.XmlRecordFieldConfigurations.Add(new ChoXmlRecordFieldConfiguration("Id"));
config.XmlRecordFieldConfigurations.Add(new ChoXmlRecordFieldConfiguration("Name"));

In the above, the class defines two XML properties matching the sample XML file template.

Listing 3.3.2 Generate XML File without POCO Object
C#
List<ExpandoObject> objs = new List<ExpandoObject>();

dynamic rec1 = new ExpandoObject();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
dynamic rec2 = new ExpandoObject();
rec2.Id = 2;
rec2.Name = "Tom";
objs.Add(rec2);
 
using (var parser = new ChoXmlWriter("Emp.xml", config).WithXPath("Employees/Employee"))
{
    parser.Write(objs);
}

The above sample code shows how to generate XML file from list of dynamic objects using predefined XML configuration setup. In the XmlWriter constructor, we specified the XML configuration object to obey the XML layout schema while creating the file. If there is any mismatch in the name or count of XML columns, it will be reported as an error and stops the writing process.

Listing 3.3.3 Saving XML file with POCO Object
C#
List<EmployeeRecSimple> objs = new List<EmployeeRecSimple>();

EmployeeRecSimple rec1 = new EmployeeRecSimple();
rec1.Id = 1;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRecSimple rec2 = new EmployeeRecSimple();
rec2.Id = 2;
rec2.Name = "Jason";
objs.Add(rec2);
 
using (var parser = new ChoXmlWriter<EmployeeRecSimple>
      ("Emp.xml", config).WithXPath("Employees/Employee"))
{
    parser.Write(objs);
}

The above sample code shows how to generate XML file from list of POCO objects with XML configuration object. In the XmlWriter constructor, we specified the XML configuration object.

3.4. Code First with Declarative Configuration

This is the combined approach to define POCO entity class along with attaching XML configuration parameters declaratively. id is required column and name is optional value column with default value "XXXX". If name is not present, it will take the default value.

Listing 3.4.1 Define POCO Object
C#
public class EmployeeRec
{
    [ChoXmlElementRecordField]
    [Required]
    public int? Id
    {
        get;
        set;
    }

    [ChoXmlElementRecordField]
    [DefaultValue("XXXX")]
    public string Name
    {
        get;
        set;
    }
}

The code above illustrates about defining POCO object with necessary attributes required to generate XML file. The first thing defines property for each record field with ChoXmlElementRecordFieldAttribute to qualify for XML record mapping. Id is a required property. We decorated it with RequiredAttribute. Name is given default value using DefaultValueAttribute. It means that if the Name value is not set in the object, XmlWriter spits the default value 'XXXX' to the file.

To define a property as XML attribute, use ChoXmlAttributeRecordFieldAttribute to it.

It is very simple and ready to save XML data in no time.

Listing 3.4.2 Saving XML File with POCO Object
C#
List<EmployeeRec> objs = new List<EmployeeRec>();

EmployeeRec rec1 = new EmployeeRec();
rec1.Id = 10;
rec1.Name = "Mark";
objs.Add(rec1);
 
EmployeeRec rec2 = new EmployeeRec();
rec2.Id = 200;
rec2.Name = "Lou";
objs.Add(rec2);
 
using (var parser = new ChoXmlWriter<EmployeeRec>
      ("Emp.xml").WithXPath("Employees/Employee"))
{
    parser.Write(objs);
}

We start by creating a new instance of ChoXmlWriter object. That's all. All the heavy lifting of generating XML data from the objects is done by the writer under the hood.

By default, XmlWriter discovers and uses default configuration parameters while saving XML file. These can be overridable according to your needs. The following sections will give you in-depth details about each configuration attributes.

4. Writing All Records

It is as easy as setting up POCO object match up with XML file structure, construct the list of objects and pass it to XmlWriter's Write method. This will write the entire list of objects into XML file in one single call.

Listing 4.1 Write to XML File
C#
List<EmployeeRec> objs = new List<EmployeeRec>();
//Construct and attach objects to this list
...

using (var parser = new ChoXmlWriter<EmployeeRec>
                    ("Emp.xml").WithXPath("Employees/Employee"))
{
    parser.Write(objs);
}

or:

Listing 4.2 Writer to XML File Stream
C#
List<EmployeeRec> objs = new List<EmployeeRec>();
//Construct and attach objects to this list
...

using (var tx = File.OpenWrite("Emp.xml"))
{
    using (var parser = 
           new ChoXmlWriter<EmployeeRec>(tx).WithXPath("Employees/Employee"))
    {
        parser.Write(objs);
    }
}

This model keeps your code elegant, clean, easy to read and maintain.

5. Write Records Manually

This is an alternative way to write each and individual record to XML file in case when the POCO objects are constructed in a disconnected way.

Listing 5.1 Writing to XML File
C#
var writer = new ChoXmlWriter<EmployeeRec>("Emp.xml");
writer.WithXPath("Employees/Employee");

EmployeeRec rec1 = new EmployeeRec();
rec1.Id = 10;
rec1.Name = "Mark";
 
writer.Write(rec1);

EmployeeRec rec2 = new EmployeeRec();
rec1.Id = 11;
rec1.Name = "Top"; 

writer.Write(rec2);

6. Customize XML Record

Using ChoXmlRecordObjectAttribute, you can customize the POCO entity object declaratively.

Listing 6.1 Customizing POCO object for Each Record
C#
[ChoXmlRecordObject(Encoding = "Encoding.UTF32", 
ErrorMode = ChoErrorMode.IgnoreAndContinue, 
            IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All)]
public class EmployeeRec
{
    [ChoXmlElementRecordField]
    public int Id { get; set; }
    [ChoXmlElementRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

Here are the available attributes to carry out customization of XML load operation on a file.

  • XPath - Optional. XPath expression used to generate root and child elements. Usually, the format of the expressions will be 'RootName/ElementName'. Ex. 'Employees/Employee'. If not specified, 'Root' will be used as Root element, 'XElement' will be used for each object.
  • EOLDelimiter - N/A
  • Culture - The culture info used to read and write.
  • IgnoreEmptyLine - N/A
  • Comments - N/A
  • QuoteChar - N/A
  • QuoteAllFields - N/A
  • Encoding - The encoding of the XML file.
  • ColumnCountStrict - This flag indicates if an exception should be thrown if XML field configuration mismatch with the data object members.
  • ColumnOrderStrict - N/A
  • BufferSize - The size of the internal buffer that is used when reader is from the StreamWriter.
  • ErrorMode - This flag indicates if an exception should be thrown if writing and an expected field is failed to write. This can be overridden per property. Possible values are:
    • IgnoreAndContinue - Ignore the error, record will be skipped and continue with next.
    • ReportAndContinue - Report the error to POCO entity if it is of IChoNotifyRecordWrite type
    • ThrowAndStop - Throw the error and stop the execution
  • IgnoreFieldValueMode - N/A
  • ObjectValidationMode - A flag to let the reader know about the type of validation to be performed with record object. Possible values are:
    • Off - No object validation performed. (Default)
    • MemberLevel - Validation performed before each XML property gets written to the file.
    • ObjectLevel - Validation performed before all the POCO properties are written to the file.

7. Customize XML Fields

For each XML column, you can specify the mapping in POCO entity property using ChoXmlElementRecordFieldAttribute/ChoXmlAttributeRecordFieldAttribute.

Listing 7.1 Customizing POCO Object for XML Columns
C#
[ChoXmlFileHeader]
public class EmployeeRec
{
    [ChoXmlElementRecordField]
    public int Id { get; set; }
    [ChoXmlElementRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}

Here are the available members to add some customization to it for each property:

  • FieldName - XML element/attribute name. If not specified, object property name will be used.
  • FillChar - Padding character used when size of the column value is short of the column size. Default is ' ', padding will be off.
  • FieldValueJustification - Column value alignment. Default is Left.
  • FieldValueTrimOption - N/A.
  • Truncate - This flag tells the writer to truncate the XML column value if it over the column size. Default is false.
  • Size - Size of XML column value.
  • QuoteField - N/A
  • ErrorMode - This flag indicates if an exception should be thrown if writing and an expected field failed to convert and write. Possible values are:
    • IgnoreAndContinue - Ignore the error and continue to load other properties of the record.
    • ReportAndContinue - Report the error to POCO entity if it is of IChoRecord type.
    • ThrowAndStop - Throw the error and stop the execution.
  • IgnoreFieldValueMode - N/A

7.1. DefaultValue

Any POCO entity property can be specified with default value using System.ComponentModel.DefaultValueAttribute. It is the value used to write when the XML value is null (controlled via IgnoreFieldValueMode).

7.2. ChoFallbackValue

Any POCO entity property can be specified with fallback value using ChoETL.ChoFallbackValueAttribute. It is the value used and set to the property when the XML value failed to convert as text. Fallback value only set when ErrorMode is either IgnoreAndContinue or ReportAndContinue.

7.3. Type Converters

Most of the primitive types are automatically converted to string/text and saved to XML file. If the values of the XML field aren't automatically converted into the text value, you can specify a custom / built-in .NET converters to convert the value to text. These can be either IValueConverter or TypeConverter converters.

The methods to use to convert/format property values to text are IValueConverter.ConvertBack() or TypeConvert.ConvertTo().

Listing 7.3.1 Specifying Type Converters
C#
[ChoXmlFileHeader]
public class EmployeeRec
{
    [ChoXmlElementRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    public int Id { get; set; }
    [ChoXmlElementRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    public string Name { get; set; }
}
Listing 7.3.2 IntConverter Implementation
C#
public class IntConverter : IValueConverter
{
    public object Convert
    (object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
 
    public object ConvertBack
    (object value, Type targetType, object parameter, CultureInfo culture)
    {
        int intValue = (int)value;
        return intValue.ToString("D4");
    }
}

In the example above, we defined custom IntConverter class. And showed how to format 'Id' XML property with leading zeros.

7.4. Validations

XmlWriter leverages both System.ComponentModel.DataAnnotations and Validation Block validation attributes to specify validation rules for individual fields of POCO entity. Refer to the MSDN site for a list of available DataAnnotations validation attributes.

Listing 7.4.1 Using Validation Attributes in POCO Entity
C#
[ChoXmlFileHeader]
[ChoXmlRecordObject(Encoding = "Encoding.UTF32", 
       ErrorMode = ChoErrorMode.IgnoreAndContinue,
       IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, 
       ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec
{
    [ChoXmlElementRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
 
    [ChoXmlElementRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
}

In the example above, used Range validation attribute for Id property. Required validation attribute to Name property. XmlWriter performs validation on them before saving the data to file when Configuration.ObjectValidationMode is set to ChoObjectValidationMode.MemberLevel or ChoObjectValidationMode.ObjectLevel.

In some cases, you may want to take control and perform manual self validation within the POCO entity class. This can be achieved by inheriting POCO object from IChoValidatable interface.

Listing 7.4.2 Manual Validation on POCO Entity
C#
[ChoXmlFileHeader]
[ChoXmlRecordObject(Encoding = "Encoding.UTF32", 
       ErrorMode = ChoErrorMode.IgnoreAndContinue,
       IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, 
       ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec : IChoValidatable
{
    [ChoXmlElementRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
 
    [ChoXmlElementRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool TryValidate
           (object target, ICollection<ValidationResult> validationResults)
    {
        return true;
    }
 
    public bool TryValidateFor
    (object target, string memberName, 
     ICollection<ValidationResult> validationResults)
    {
        return true;
    }
 
    public void Validate(object target)
    {
    }
 
    public void ValidateFor(object target, string memberName)
    {
    }
}

The sample above shows how to implement custom self-validation in POCO object.

IChoValidatable interface exposes the below methods:

  • TryValidate - Validate the entire object, return true if all validation passed. Otherwise, return false.
  • Validate - Validate the entire object, throw exception if validation is not passed.
  • TryValidateFor - Validate a specific property of the object, return true if all validation passed. Otherwise return false.
  • ValidateFor - Validate specific property of the object, throw exception if validation is not passed.

8. Callback Mechanism

XmlWriter offers industry standard XML data file generation out of the box to handle most of the needs. If the generation process is not handling any of your needs, you can use the callback mechanism offered by XmlWriter to handle such situations. In order to participate in the callback mechanism, either POCO entity object or DataAnnotation's MetadataType type object must be inherited by IChoNotifyRecordWrite interface.

Tip: Any exceptions raised out of these interface methods will be ignored.

IChoNotifyRecordWrite exposes the below methods:

  • BeginWrite - Invoked at the begin of the XML file write
  • EndWrite - Invoked at the end of the XML file write
  • BeforeRecordWrite - Raised before the XML record write
  • AfterRecordWrite - Raised after XML record write
  • RecordWriteError - Raised when XML record errors out while writing
  • BeforeRecordFieldWrite - Raised before XML column value write
  • AfterRecordFieldWrite - Raised after XML column value write
  • RecordFieldWriteError - Raised when XML column value errors out while writing
Listing 8.1 Direct POCO Callback Mechanism Implementation
C#
[ChoXmlFileHeader]
[ChoXmlRecordObject(Encoding = "Encoding.UTF32", 
                    ErrorMode = ChoErrorMode.IgnoreAndContinue,
       IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, 
                  ThrowAndStopOnMissingField = false)]
public partial class EmployeeRec : IChoNotifyrRecordWrite
{
    [ChoXmlElementRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
    
    [ChoXmlElementRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }

    public bool AfterRecordFieldWrite
           (object target, int index, string propName, object value)
    {
        throw new NotImplementedException();
    }

    public bool AfterRecordWrite(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordFieldWrite
           (object target, int index, string propName, ref object value)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordWrite(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool BeginWrite(object source)
    {
        throw new NotImplementedException();
    }

    public void EndWrite(object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordFieldWriteError
    (object target, int index, string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }

    public bool RecordWriteError
    (object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
}
Listing 8.2 MetaDataType Based Callback Mechanism Implementation
C#
[ChoXmlFileHeader]
[ChoXmlRecordObject(Encoding = "Encoding.UTF32", 
       ErrorMode = ChoErrorMode.IgnoreAndContinue,
       IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, 
       ThrowAndStopOnMissingField = false)]
public class EmployeeRecMeta : IChoNotifyRecordWrite
{
    [ChoXmlElementRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoXmlElementRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
 
    public bool AfterRecordFieldWrite
           (object target, int index, string propName, object value)
    {
        throw new NotImplementedException();
    }

    public bool AfterRecordWrite(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordFieldWrite
    (object target, int index, string propName, ref object value)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordWrite(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool BeginWrite(object source)
    {
        throw new NotImplementedException();
    }

    public void EndWrite(object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordFieldWriteError
    (object target, int index, string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }

    public bool RecordWriteError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
} 

[MetadataType(typeof(EmployeeRecMeta))]
public partial class EmployeeRec
{
    [ChoXmlElementRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, int.MaxValue, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }
    
    [ChoXmlElementRecordField]
    [Required]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
}

8.1 BeginWrite

This callback invoked once at the beginning of the XML file write. source is the XML file stream object. In here, you have chance to inspect the stream, return true to continue the XML generation. Return false to stop the generation.

Listing 10.1.1 BeginWrite Callback Sample
C#
public bool BeginWrite(object source)
{
    StreamReader sr = source as StreamReader;
    return true;
}

8.2 EndWrite

This callback invoked once at the end of the XML file generation. source is the XML file stream object. In here, you have a chance to inspect the stream, do any post steps to be performed on the stream.

Listing 10.2.1 EndWrite Callback Sample
C#
public void EndWrite(object source)
{
    StreamReader sr = source as StreamReader;
}

8.3 BeforeRecordWrite

This callback invoked before each POCO record object is written to XML file. target is the instance of the POCO record object. index is the line index in the file. source is the XML record line. In here, you have a chance to inspect the POCO object, and generate the XML record line if needed.

Tip: If you want to skip the record from writing, set the source to null.

Tip: If you want to take control of XML record line generation, set the source to valid XML record line text.

Return true to continue the load process, otherwise return false to stop the process.

Listing 8.3.1 BeforeRecordWrite Callback Sample
C#
public bool BeforeRecordWrite(object target, int index, ref object source)
{
    source = "1,Raj";
    return true;
}

8.4 AfterRecordWrite

This callback invoked after each POCO record object is written to XML file. target is the instance of the POCO record object. index is the line index in the file. source is the XML record line. In here, you have a chance to do any post step operation with the record line.

Return true to continue the load process, otherwise return false to stop the process.

Listing 8.4.1 AfterRecordWrite Callback Sample
C#
public bool AfterRecordWrite(object target, int index, object source)
{
    string line = source as string;
    return true;
}

8.5 RecordWriteError

This callback invoked if error encountered while writing POCO record object. target is the instance of the POCO record object. index is the line index in the file. source is the XML record line. ex is the exception object. In here, you have a chance to handle the exception. This method invoked only when Configuration.ErrorMode is ReportAndContinue.

Return true to continue the load process, otherwise return false to stop the process.

Listing 8.5.1 RecordWriteError Callback Sample
C#
public bool RecordLoadError(object target, int index, object source, Exception ex)
{
    string line = source as string;
    return true;
}

8.6 BeforeRecordFieldWrite

This callback invoked before each XML record column is written to XML file. target is the instance of the POCO record object. index is the line index in the file. propName is the XML record property name. value is the XML column value. In here, you have a chance to inspect the XML record property value and perform any custom validations, etc.

Return true to continue the load process, otherwise return false to stop the process.

Listing 8.6.1 BeforeRecordFieldWrite Callback Sample
C#
public bool BeforeRecordFieldWrite
(object target, int index, string propName, ref object value)
{
    return true;
}

8.7 AfterRecordFieldWrite

This callback invoked after each XML record column value is written to XML file. target is the instance of the POCO record object. index is the line index in the file. propName is the XML record property name. value is the XML column value. Any post field operation can be performed here, like computing other properties, validations, etc.

Return true to continue the load process, otherwise return false to stop the process.

Listing 8.7.1 AfterRecordFieldWrite Callback Sample
C#
public bool AfterRecordFieldWrite
(object target, int index, string propName, object value)
{
    return true;
}

8.8 RecordWriteFieldError

This callback is invoked when an error is encountered while writing XML record column value. target is the instance of the POCO record object. index is the line index in the file. propName is the XML record property name. value is the XML column value. ex is the exception object. In here, you have a chance to handle the exception. This method is invoked only after the below two sequences of steps have been performed by the XmlReader:

  • XmlWriter looks for FallbackValue value of each XML property. If present, it tries to use it to write.
  • If the FallbackValue value is not present and the Configuration.ErrorMode is specified as ReportAndContinue, this callback will be executed.

Return true to continue the load process, otherwise return false to stop the process.

Listing 8.8.1 RecordFieldWriteError Callback Sample
C#
public bool RecordFieldWriteError
(object target, int index, string propName, object value, Exception ex)
{
    return true;
}

9. Customization

XmlWriter automatically detects and loads the configuration settings from POCO entity. At runtime, you can customize and tweak these parameters before XML generation. XmlWriter exposes Configuration property, it is of ChoXmlRecordConfiguration object. Using this property, you can perform the customization.

Listing 9.1 Customizing XmlWriter at Run-time
C#
class Program
{
    static void Main(string[] args)
    {        
        List<ExpandoObject> objs = new List<ExpandoObject>();
        dynamic rec1 = new ExpandoObject();
        rec1.Id = 1;
        rec1.Name = "Mark";
        objs.Add(rec1);

        dynamic rec2 = new ExpandoObject();
        rec2.Id = 2;
        rec2.Name = "Jason";
        objs.Add(rec2);

        using (var parser = new ChoXmlWriter("Emp.xml").WithXPath("Employees/Employee"))
        {
            parser.Configuration.ColumnCountStrict = true;
            parser.Write(objs);
        }
    }
}

10. Using Dynamic Object

So far, the article explained about using XmlWriter with POCO object. XmlWriter also supports generating XML file without POCO entity objects It leverages .NET dynamic feature. The sample below shows how to generate XML stream using dynamic objects. The XML schema is determined from the first object. If there is a mismatch found in the dynamic objects member values, an error will be raised and stops the generation process.

The sample below shows it:

Listing 10.1 Generating XML File from Dynamic Objects
C#
class Program
{
    static void Main(string[] args)
    {        
        List<ExpandoObject> objs = new List<ExpandoObject>();
        dynamic rec1 = new ExpandoObject();
        rec1.Id = 1;
        rec1.Name = "Mark";
        objs.Add(rec1);

        dynamic rec2 = new ExpandoObject();
        rec2.Id = 2;
        rec2.Name = "Jason";
        objs.Add(rec2);

        using (var parser = new ChoXmlWriter("Emp.xml").WithXPath("Employees/Employee"))
        {
            parser.Configuration.ColumnCountStrict = true;
            parser.Write(objs);
        }
    }
}

11. Exceptions

XmlReader throws different types of exceptions in different situations:

  • ChoParserException - XML file is bad and the parser is not able to recover.
  • ChoRecordConfigurationException - Any invalid configuration settings are specified, this exception will be raised.
  • ChoMissingRecordFieldException - A property is missing for an XML column, this exception will be raised.

12. Using MetadataType Annotation

Cinchoo ETL works better with data annotation's MetadataType model. It is a way to attach MetaData class to data model class. In this associated class, you provide additional metadata information that is not in the data model. Its role is to add attribute to a class without having to modify this one. You can add this attribute that takes a single parameter to a class that will have all the attributes. This is useful when the POCO classes are auto generated (by Entity Framework, MVC, etc.) by an automatic tool. This is why the second class comes into play. You can add new stuff without touching the generated file. Also, this promotes modularization by separating the concerns into multiple classes.

For more information about it, please search on MSDN.

Listing 12.1 MetadataType Annotation Usage Sample
C#
[MetadataType(typeof(EmployeeRecMeta))]
public class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

[ChoXmlFileHeader]
[ChoXmlRecordObject(Encoding = "Encoding.UTF32", ErrorMode = ChoErrorMode.ThrowAndStop,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false, 
    ObjectValidationMode = ChoObjectValidationMode.MemberLevel)]
public class EmployeeRecMeta : IChoNotifyRecordWrite, IChoValidatable
{
    [ChoXmlElementRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    [ChoFallbackValue(1)]
    public int Id { get; set; }

    [ChoXmlElementRecordField]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
    public bool AfterRecordFieldWrite
    (object target, int index, string propName, object value)
    {
        throw new NotImplementedException();
    }

    public bool AfterRecordWrite(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordFieldWrite
    (object target, int index, string propName, ref object value)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordWrite(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool BeginWrite(object source)
    {
        throw new NotImplementedException();
    }

    public void EndWrite(object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordFieldWriteError
    (object target, int index, string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }

    public bool RecordWriteError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
 
    public bool TryValidate
    (object target, ICollection<ValidationResult> validationResults)
    {
        return true;
    }
 
    public bool TryValidateFor
    (object target, string memberName, ICollection<ValidationResult> validationResults)
    {
        return true;
    }
 
    public void Validate(object target)
    {
    }
 
    public void ValidateFor(object target, string memberName)
    {
    }
}

In the above, EmployeeRec is the data class. It contains only domain specific properties and operations. Mark it as a very simple class to look at it.

We separate the validation, callback mechanism, configuration, etc. into metadata type class, EmployeeRecMeta.

13. Configuration Choices

If the POCO entity class is an auto-generated class or exposed via library or it is a sealed class, it limits you to attach XML schema definition to it declaratively. In such a case, you can choose one of the options below to specify XML layout configuration.

  • Manual Configuration
  • Auto Map Configuration
  • Attaching MetadataType class

I'm going to show you how to configure the below POCO entity class on each approach.

Listing 13.1 Sealed POCO Entity Class
C#
public sealed class EmployeeRec
{
    public int Id { get; set; }
    public string Name { get; set; }
}

13.1 Manual Configuration

Define a brand new configuration object from scratch and add all the necessary XML fields to the ChoXmlConfiguration.XmlRecordFieldConfigurations collection property. This option gives you greater flexibility to control the configuration of XML parsing. But the downside is the possibility of making mistakes and it is hard to manage them if the XML file layout is large.

Listing 13.1.1 Manual Configuration
C#
ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.XmlFileHeaderConfiguration.HasHeaderRecord = true;
config.ThrowAndStopOnMissingField = true;
config.XmlRecordFieldConfigurations.Add(new ChoXmlRecordFieldConfiguration("Id"));
config.XmlRecordFieldConfigurations.Add(new ChoXmlRecordFieldConfiguration("Name"));

13.2 Auto Map Configuration

This is an alternative approach and very less error-prone method to auto map the XML columns for the POCO entity class.

First, define a schema class for EmployeeRec POCO entity class as below:

Listing 13.2.1 Auto Map Class
C#
public class EmployeeRecMap
{
    [ChoXmlElementRecordField]
    public int Id { get; set; }
 
    [ChoXmlElementRecordField]
    public string Name { get; set; } 
}

Then you can use it to auto map XML columns by using ChoXmlRecordConfiguration.MapRecordFields method.

Listing 13.2.2 Using Auto Map Configuration
C#
ChoXmlRecordConfiguration config = new ChoXmlRecordConfiguration();
config.MapRecordFields<EmployeeRecMap>();

foreach (var e in new ChoXmlReader<EmployeeRec>
        ("Emp.xml", config).WithXPath("Employees/Employee")) 
    Console.WriteLine(e.ToString());

13.3 Attaching MetadataType Class

This is another approach to attach MetadataType class for POCO entity object. The previous approach simple cares for auto mapping of XML columns only. Other configuration properties like property converters, parser parameters, default/fallback values, etc. are not considered.

This model accounts for everything by defining MetadataType class and specifying the XML configuration parameters declaratively. This is useful when your POCO entity is sealed and not partial class. Also, it is one of the favorable and less error-prone approaches to configure XML parsing of POCO entity.

Listing 13.3.1 Define MetadataType Class
C#
[ChoXmlFileHeader()]
[ChoXmlRecordObject(Encoding = "Encoding.UTF32", 
 ErrorMode = ChoErrorMode.ReportAndContinue,
IgnoreFieldValueMode = ChoIgnoreFieldValueMode.All, ThrowAndStopOnMissingField = false, 
    ObjectValidationMode = ChoObjectValidationMode.MemberLevel)]
public class EmployeeRecMeta : IChoNotifyRecordWrite, IChoValidatable
{
    [ChoXmlElementRecordField]
    [ChoTypeConverter(typeof(IntConverter))]
    [Range(1, 1, ErrorMessage = "Id must be > 0.")]
    //[ChoFallbackValue(1)]
    public int Id { get; set; }
    [ChoXmlElementRecordField]
    [StringLength(1)]
    [DefaultValue("ZZZ")]
    [ChoFallbackValue("XXX")]
    public string Name { get; set; }
    public bool AfterRecordFieldWrite
    (object target, int index, string propName, object value)
    {
        throw new NotImplementedException();
    }

    public bool AfterRecordWrite(object target, int index, object source)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordFieldWrite
    (object target, int index, string propName, ref object value)
    {
        throw new NotImplementedException();
    }

    public bool BeforeRecordWrite(object target, int index, ref object source)
    {
        throw new NotImplementedException();
    }

    public bool BeginWrite(object source)
    {
        throw new NotImplementedException();
    }

    public void EndWrite(object source)
    {
        throw new NotImplementedException();
    }

    public bool RecordFieldWriteError
    (object target, int index, string propName, object value, Exception ex)
    {
        throw new NotImplementedException();
    }

    public bool RecordWriteError(object target, int index, object source, Exception ex)
    {
        throw new NotImplementedException();
    }
 
    public bool TryValidate
    (object target, ICollection<ValidationResult> validationResults)
    {
        return true;
    }
 
    public bool TryValidateFor
    (object target, string memberName, ICollection<ValidationResult> validationResults)
    {
        return true;
    }
 
    public void Validate(object target)
    {
    }
 
    public void ValidateFor(object target, string memberName)
    {
    }
}
Listing 13.3.2 Attaching MetadataType Class
C#
//Attach metadata 
ChoMetadataObjectCache.Default.Attach<EmployeeRec>(new EmployeeRecMeta());

using (var tx = File.OpenWrite("Emp.xml"))
{
    using (var parser = 
           new ChoXmlWriter<EmployeeRec>(tx).WithXPath("Employees/Employee"))
    {
        parser.Write(objs);
    }
}

14. ToText Helper Method

This is a little nifty helper method to generate XML formatted output from list of objects. It helps you to run and play with different options to see the XML output quickly in test environment.

C#
static void ToTextTest()
{
    List<EmployeeRec> objs = new List<EmployeeRec>();
    EmployeeRec rec1 = new EmployeeRec();
    rec1.Id = 10;
    rec1.Name = "Mark";
    objs.Add(rec1);
 
    EmployeeRec rec2 = new EmployeeRec();
    rec2.Id = 200;
    rec2.Name = "Lou";
    objs.Add(rec2);
 
    Console.WriteLine(ChoXmlWriter.ToText(objs, "Employees/Employee"));
}

15. Writing DataReader Helper Method

This helper method lets you to create XML file / stream from ADO.NET DataReader.

C#
static void WriteDataReaderTest()
{
    string connString = @"Data Source=(localdb)\v11.0;
                        Initial Catalog=TestDb;Integrated Security=True";
 
    SqlConnection conn = new SqlConnection(connString);
    conn.Open();
    SqlCommand cmd = new SqlCommand("SELECT * FROM Members", conn);
    IDataReader dr = cmd.ExecuteReader();
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = 
           new ChoXmlWriter(writer, config).WithXPath("Employees/Employee"))
    {
        parser.Write(dr);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

16. Writing DataTable Helper Method

This helper method lets you to create XML file / stream from ADO.NET DataTable.

C#
static void WriteDataTableTest()
{
    string connString = @"Data Source=(localdb)\v11.0;
                        Initial Catalog=TestDb;Integrated Security=True";

    SqlConnection conn = new SqlConnection(connString);
    conn.Open();
    SqlCommand cmd = new SqlCommand("SELECT * FROM Members", conn);
    SqlDataAdapter da = new SqlDataAdapter(cmd);
    DataTable dt = new DataTable();
    da.Fill(dt);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = 
           new ChoXmlWriter(writer, config).WithXPath("Employees/Employee"))
    {
        parser.Write(dt);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

17. Advanced Topics

17.1 Override Converters Format Specs

Cinchoo ETL automatically parses and converts each XML column values to the corresponding XML column's underlying data type seamlessly. Most of the basic .NET types are handled automatically without any setup needed.

This is achieved through two key settings in the ETL system:

  1. ChoXmlRecordConfiguration.CultureInfo - Represents information about a specific culture including the names of the culture, the writing system, and the calendar used, as well as access to culture-specific objects that provide information for common operations, such as formatting dates and sorting strings. Default is 'en-US'.
  2. ChoTypeConverterFormatSpec - It is global format specifier class holds all the intrinsic .NET types formatting specs.

In this section, I'm going to talk about changing the default format specs for each .NET intrinsic data types according to parsing needs.

ChoTypeConverterFormatSpec is singleton class, the instance is exposed via 'Instance' static member. It is thread local, means that there will be separate instance copy kept on each thread.

There are two sets of format specs members given to each intrinsic type, one for loading and another one for writing the value, except for Boolean, Enum, DataTime types. These types have only one member for both loading and writing operations.

Specifying each intrinsic data type format specs through ChoTypeConverterFormatSpec will impact system wide, i.e., by setting ChoTypeConverterFormatSpec.IntNumberStyle = NumberStyles.AllowParentheses, will impact all integer members of XML objects to allow parentheses. If you want to override this behavior and take control of specific XML data member to handle its own unique parsing of XML value from global system wide setting, it can be done by specifying TypeConverter at the XML field member level. Refer to section 7.4 for more information.

Listing 17.1.1 ChoTypeConverterFormatSpec Members
C#
public class ChoTypeConverterFormatSpec
{
    public static readonly ThreadLocal<ChoTypeConverterFormatSpec> Instance = 
    new ThreadLocal<ChoTypeConverterFormatSpec>(() => new ChoTypeConverterFormatSpec());
 
    public string DateTimeFormat { get; set; }
    public ChoBooleanFormatSpec BooleanFormat { get; set; }
    public ChoEnumFormatSpec EnumFormat { get; set; }
 
    public NumberStyles? CurrencyNumberStyle { get; set; }
    public string CurrencyFormat { get; set; }
 
    public NumberStyles? BigIntegerNumberStyle { get; set; }
    public string BigIntegerFormat { get; set; }
 
    public NumberStyles? ByteNumberStyle { get; set; }
    public string ByteFormat { get; set; }
 
    public NumberStyles? SByteNumberStyle { get; set; }
    public string SByteFormat { get; set; }
 
    public NumberStyles? DecimalNumberStyle { get; set; }
    public string DecimalFormat { get; set; }
 
    public NumberStyles? DoubleNumberStyle { get; set; }
    public string DoubleFormat { get; set; }
 
    public NumberStyles? FloatNumberStyle { get; set; }
    public string FloatFormat { get; set; }
 
    public string IntFormat { get; set; }
    public NumberStyles? IntNumberStyle { get; set; }
 
    public string UIntFormat { get; set; }
    public NumberStyles? UIntNumberStyle { get; set; }
 
    public NumberStyles? LongNumberStyle { get; set; }
    public string LongFormat { get; set; }
 
    public NumberStyles? ULongNumberStyle { get; set; }
    public string ULongFormat { get; set; }
 
    public NumberStyles? ShortNumberStyle { get; set; }
    public string ShortFormat { get; set; }
 
    public NumberStyles? UShortNumberStyle { get; set; }
    public string UShortFormat { get; set; }
}

The sample below shows how to load XML data stream having 'se-SE' (Swedish) culture specific data using XmlReader. Also the input feed comes with 'EmployeeNo' values containing parentheses. In order to make the load successful, we have to set the ChoTypeConverterFormatSpec.IntNumberStyle to NumberStyles.AllowParenthesis.

Listing 17.1.2 Using ChoTypeConverterFormatSpec in Code
C#
static void FormatSpecDynamicTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "d";
    ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.YOrN;
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    objs.Add(rec2);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoXmlWriter(writer).WithXPath("Employees/Employee"))
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

17.2 Currency Support

Cinchoo ETL provides ChoCurrency object to read and write currency values in XML files. ChoCurrency is a wrapper class to hold the currency value in decimal type along with support of serializing them in text format during XML load.

Listing 17.2.1 Using Currency Members in Dynamic Model
C#
static void CurrencyDynamicTest()
{
    ChoTypeConverterFormatSpec.Instance.CurrencyFormat = "C2";
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoXmlWriter(writer).WithXPath("Employees/Employee"))
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

The sample above shows how to output currency values using dynamic object model. As the currency output will have thousand comma separator, this will fail to generate XML file. To overcome this issue, we specify the writer to quote all fields.

P.S.: The format of the currency value is figured by XmlReader through ChoRecordConfiguration.Culture and ChoTypeConverterFormatSpec.CurrencyFormat.

The sample below shows how to use ChoCurrency XML field in POCO entity class.

Listing 17.2.2 Using Currency members in POCO Model
C#
public class EmployeeRecWithCurrency
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ChoCurrency Salary { get; set; }
}
 
static void CurrencyPOCOTest()
{
    List<EmployeeRecWithCurrency> objs = new List<EmployeeRecWithCurrency>();
    EmployeeRecWithCurrency rec1 = new EmployeeRecWithCurrency();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    EmployeeRecWithCurrency rec2 = new EmployeeRecWithCurrency();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoXmlWriter<EmployeeRecWithCurrency>
                        (writer).WithXPath("Employees/Employee"))
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

17.3 Enum Support

Cinchoo ETL implicitly handles parsing/writing of enum column values from XML files. If you want to fine control the parsing of these values, you can specify them globally via ChoTypeConverterFormatSpec.EnumFormat. Default is ChoEnumFormatSpec.Value.

FYI, changing this value will impact system wide.

There are three possible values can be used:

  1. ChoEnumFormatSpec.Value - Enum value is used for parsing.
  2. ChoEnumFormatSpec.Name - Enum key name is used for parsing.
  3. ChoEnumFormatSpec.Description - If each enum key is decorated with DescriptionAttribute, its value will be use for parsing.
Listing 17.3.1 Specifying Enum Format Specs During Parsing
C#
public enum EmployeeType
{
    [Description("Full Time Employee")]
    Permanent = 0,
    [Description("Temporary Employee")]
    Temporary = 1,
    [Description("Contract Employee")]
    Contract = 2
}
 
static void EnumTest()
{
    ChoTypeConverterFormatSpec.Instance.EnumFormat = ChoEnumFormatSpec.Description;
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    rec1.Status = EmployeeType.Permanent;
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    rec2.Status = EmployeeType.Contract;
    objs.Add(rec2);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoXmlWriter(writer).WithXPath("Employees/Employee"))
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

17.4 Boolean Support

Cinchoo ETL implicitly handles parsing/writing of boolean XML column values from XML files. If you want to fine control the parsing of these values, you can specify them globally via ChoTypeConverterFormatSpec.BooleanFormat. Default value is ChoBooleanFormatSpec.ZeroOrOne.

FYI, changing this value will impact system wide.

There are four possible values can be used:

  1. ChoBooleanFormatSpec.ZeroOrOne - '0' for false. '1' for true.
  2. ChoBooleanFormatSpec.YOrN - 'Y' for true, 'N' for false.
  3. ChoBooleanFormatSpec.TrueOrFalse - 'True' for true, 'False' for false.
  4. ChoBooleanFormatSpec.YesOrNo - 'Yes' for true, 'No' for false.
Listing 17.4.1 Specifying Boolean Format Specs During Parsing
C#
static void BoolTest()
{
    ChoTypeConverterFormatSpec.Instance.BooleanFormat = ChoBooleanFormatSpec.YOrN;
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    rec1.Status = EmployeeType.Permanent;
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    rec2.Status = EmployeeType.Contract;
    objs.Add(rec2);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoXmlWriter(writer).WithXPath("Employees/Employee"))
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

17.5 DateTime Support

Cinchoo ETL implicitly handles parsing/writing of datetime XML column values from XML files using system Culture or custom set culture. If you want to fine control the parsing of these values, you can specify them globally via ChoTypeConverterFormatSpec.DateTimeFormat. Default value is 'd'.

FYI, changing this value will impact system wide.

You can use any valid standard or custom datetime .NET format specification to parse the datetime XML values from the file.

Listing 17.5.1 Specifying datetime Format Specs during Parsing
C#
static void DateTimeDynamicTest()
{
    ChoTypeConverterFormatSpec.Instance.DateTimeFormat = "MMM dd, yyyy";
 
    List<ExpandoObject> objs = new List<ExpandoObject>();
    dynamic rec1 = new ExpandoObject();
    rec1.Id = 10;
    rec1.Name = "Mark";
    rec1.JoinedDate = new DateTime(2001, 2, 2);
    rec1.IsActive = true;
    rec1.Salary = new ChoCurrency(100000);
    objs.Add(rec1);
 
    dynamic rec2 = new ExpandoObject();
    rec2.Id = 200;
    rec2.Name = "Lou";
    rec2.JoinedDate = new DateTime(1990, 10, 23);
    rec2.IsActive = false;
    rec2.Salary = new ChoCurrency(150000);
    objs.Add(rec2);
 
    using (var stream = new MemoryStream())
    using (var reader = new StreamReader(stream))
    using (var writer = new StreamWriter(stream))
    using (var parser = new ChoXmlWriter(writer).WithXPath("Employees/Employee"))
    {
        parser.Write(objs);
 
        writer.Flush();
        stream.Position = 0;
 
        Console.WriteLine(reader.ReadToEnd());
    }
}

The sample above shows how to generate custom datetime values to XML file.

18. History

  • 7th March, 2017: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --