Click here to Skip to main content
15,895,777 members
Articles / Web Development / HTML

Transformalizing NorthWind

Rate me:
Please Sign up or sign in to vote.
4.95/5 (29 votes)
24 Jul 2014GPL37 min read 57.8K   341   53  
Combining de-normalization, transformation, replication, and awesome-ness.
#region License
// /*
// See license included in this library folder.
// */
#endregion

using System;
using System.Collections;
using System.Data;
using System.Data.OleDb;
using System.IO;
using Transformalize.Libs.FileHelpers.Enums;
using Transformalize.Libs.FileHelpers.ErrorHandling;

namespace Transformalize.Libs.FileHelpers.DataLink.Storage
{
    /// <summary>
    ///     <para>
    ///         This class implements the <see cref="DataStorage" /> for Microsoft Excel Files using a OleDbConnection
    ///     </para>
    /// </summary>
    public sealed class ExcelStorageOleDb : DataStorage
    {
        private static string CreateConnectionString(string file, bool hasHeaders)
        {
            var mConnectionString = "Provider=Microsoft.Jet.OleDb.4.0;Data Source={0};Extended Properties=\"Excel 8.0;{1}\"";
            var extProps = string.Empty;

            if (hasHeaders)
                extProps += " HDR=YES;";
            else
                extProps += " HDR=NO;";


            return String.Format(mConnectionString, file, extProps);
        }

        /// <summary>
        ///     An useful method to direct extract a DataTable from an Excel File without need to instanciate anything.
        /// </summary>
        /// <param name="file">The Excel file to read.</param>
        /// <param name="row">The initial row (the first is 1)</param>
        /// <param name="col">The initial column (the first is 1)</param>
        /// <param name="hasHeader">Indicates is there ir a header row.</param>
        /// <returns>The DataTable generated reading the excel file at the specified position.</returns>
        public static DataTable ExtractDataTable(string file, int row, int col, bool hasHeader)
        {
            OleDbConnection connExcel;
            //private OleDbDataAdapter mDaExcel;

            connExcel = new OleDbConnection(CreateConnectionString(file, hasHeader));
            connExcel.Open();
            var res = new DataTable();

            var sheetName = GetFirstSheet(connExcel);

            var sheet = sheetName + (sheetName.EndsWith("$") ? "" : "$");
            var command = String.Format("SELECT * FROM [{0}]", sheet);

            var cm = new OleDbCommand(command, connExcel);
            var da = new OleDbDataAdapter(cm);
            da.Fill(res);

            connExcel.Close();
            return res;
        }

        #region "  Constructors  "

        /// <summary>Create a new ExcelStorage to work with the specified type</summary>
        /// <param name="recordType">The type of records.</param>
        public ExcelStorageOleDb(Type recordType) : base(recordType)
        {
        }

        /// <summary>Create a new ExcelStorage to work with the specified type</summary>
        /// <param name="recordType">The type of records.</param>
        /// <param name="startRow">The row of the first data cell. Begining in 1.</param>
        /// <param name="startCol">The column of the first data cell. Begining in 1.</param>
        public ExcelStorageOleDb(Type recordType, int startRow, int startCol) : this(recordType)
        {
            mStartColumn = startCol;
            mStartRow = startRow;
        }

        /// <summary>Create a new ExcelStorage to work with the specified type</summary>
        /// <param name="recordType">The type of records.</param>
        /// <param name="startRow">The row of the first data cell. Begining in 1.</param>
        /// <param name="startCol">The column of the first data cell. Begining in 1.</param>
        /// <param name="fileName">The file path to work with.</param>
        public ExcelStorageOleDb(Type recordType, string fileName, int startRow, int startCol)
            : this(recordType, startRow, startCol)
        {
            mFileName = fileName;
        }

        #endregion

        #region "  Private Fields  "

        private OleDbConnection mCnExcel;
        private OleDbDataAdapter mDaExcel;
        private DataSet mDsExcel;
        private DataTable mDtExcel;
        private string mFileName = String.Empty;
        private string mSheetName = String.Empty;
        private int mStartColumn = 1;
        private int mStartRow = 1;

        #endregion

        #region "  Public Properties  "

        private bool mOverrideFile = true;

        /// <summary>The Start Row where is the data. Starting at 1.</summary>
        public int StartRow
        {
            get { return mStartRow; }
            set { mStartRow = value; }
        }

        /// <summary>The Start Column where is the data. Starting at 1.</summary>
        public int StartColumn
        {
            get { return mStartColumn; }
            set { mStartColumn = value; }
        }

        /// <summary>
        ///     Indicates if in the StartColumn and StartRow has headers or not.
        /// </summary>
        public bool HasHeaderRow { get; set; }

        /// <summary>The Excel File Name.</summary>
        public string FileName
        {
            get { return mFileName; }
            set { mFileName = value; }
        }

        /// <summary>The Excel Sheet Name, if empty means the current worksheet in the file.</summary>
        public string SheetName
        {
            get { return mSheetName; }
            set { mSheetName = value; }
        }

        /// <summary>Indicates what the Storage does if the file exist.</summary>
        public bool OverrideFile
        {
            get { return mOverrideFile; }
            set { mOverrideFile = value; }
        }

        ///// <summary>Indicates the Connection String to Open the Excel File.</summary>
        //public string ConnectionString
        //{
        //    get { return mConnectionString; }
        //    set { mConnectionString = value; }
        //}

        #endregion

        #region "  OpenConnection  "

        private void OpenConnection()
        {
            try
            {
                if (mCnExcel != null && mCnExcel.State == ConnectionState.Open)
                {
                    mCnExcel.Close();
#if NET_2_0
                mCnExcel.Dispose();
#endif
                }

                mCnExcel = new OleDbConnection(CreateConnectionString(mFileName, HasHeaderRow));
                mCnExcel.Open();
                mDtExcel = new DataTable();
            }
            catch
            {
                throw;
            }
        }

        #endregion

        #region "  CloseAndCleanUp  "

        private void CloseAndCleanUp()
        {
            if (mCnExcel != null && mCnExcel.State == ConnectionState.Open)
            {
                mCnExcel.Close();
                //mCnExcel.Dispose();
            }
        }

        #endregion

        #region "  OpenWorkbook  "

        private void OpenWorkbook()
        {
            var info = new FileInfo(mFileName);
            if (info.Exists == false)
                throw new FileNotFoundException("Excel File '" + mFileName + "' not found.", mFileName);


            OpenConnection();


            if (mSheetName == null || mSheetName == string.Empty)
            {
                string sheet;
                sheet = GetFirstSheet(mCnExcel);
                mSheetName = sheet;
            }

            try
            {
                var sheet = SheetName + (mSheetName.EndsWith("$") ? "" : "$");
                var command = String.Format("SELECT * FROM [{0}]", sheet);
                var cm = new OleDbCommand(command, mCnExcel);
                mDaExcel = new OleDbDataAdapter(cm);
                mDsExcel = new DataSet();
                mDaExcel.Fill(mDsExcel);
                mDtExcel = mDsExcel.Tables[0];
            }
            catch
            {
                throw new BadUsageException("The sheet '" + mSheetName + "' was not found in the workbook.");
            }
        }

        private static string GetFirstSheet(OleDbConnection conn)
        {
#if NET_2_0
			DataTable dt = conn.GetSchema("Tables");
#else
            var dt = conn.GetOleDbSchemaTable(OleDbSchemaGuid.Tables, null);
#endif

            if (dt != null && dt.Rows.Count > 0)
                return dt.Rows[0]["TABLE_NAME"].ToString();
            else
                throw new BadUsageException("A Valid sheet was not found in the workbook.");
        }

        #endregion

        #region "  CreateWorkbook methods  "

        private void OpenOrCreateWorkbook(string filename)
        {
            if (!File.Exists(filename))
                CreateWorkbook();

            OpenWorkbook();
        }

        private void CreateWorkbook()
        {
            if (mCnExcel != null && mCnExcel.State == ConnectionState.Open)
            {
                mCnExcel.Close();
#if NET_2_0
                mCnExcel.Dispose();
#endif
            }

            //Delete the file if Exists
            if (File.Exists(mFileName) && mOverrideFile)
            {
                File.Delete(mFileName);
            }

            OpenConnection();

            var cmCreate = "";
            if (mSheetName == null || mSheetName == string.Empty)
            {
                mSheetName = "Sheet1$";
            }


            var cm = new OleDbCommand();
            cm.Connection = mCnExcel;
            cmCreate = string.Format("CREATE TABLE [{0}] (", mSheetName.Replace("$", ""));
            foreach (var field in RecordType.GetFields())
            {
                cmCreate += field.Name + " char(255),";
            }
            if (cmCreate.EndsWith(","))
            {
                cmCreate = cmCreate.Substring(0, cmCreate.Length - 1);
            }
            cmCreate += ")";

            cm.CommandText = cmCreate;
            cm.ExecuteNonQuery();

            mCnExcel.Close();
        }

        #endregion

        #region "  SaveWorkbook  "

        /// <summary>
        ///     Update Rows in Workbook and Close the File
        /// </summary>
        private void SaveWorkbook()
        {
            #region Build and Add the Insert Command to DataAdapter

            var cmdIns = string.Format("INSERT INTO [{0}] (", mSheetName + (mSheetName.EndsWith("$") ? "" : "$"));
            var values = "";
            foreach (DataColumn col in mDtExcel.Columns)
            {
                cmdIns += col.ColumnName + ",";
                values += "?,";
            }
            cmdIns = cmdIns.Substring(0, cmdIns.Length - 1); //Clean the ","
            values = values.Substring(0, values.Length - 1); //Clean the ","
            cmdIns += ") VALUES( " + values + ")";

            var cmd = new OleDbCommand(cmdIns, mCnExcel);
            mDaExcel.InsertCommand = cmd;
            foreach (DataColumn col in mDtExcel.Columns)
            {
                cmd.Parameters.AddWithValue("@" + col.ColumnName, col.DataType).SourceColumn = col.ColumnName;
            }

            #endregion

            mDaExcel.Update(mDtExcel);

            CloseAndCleanUp();
        }

        #endregion

        #region "  RowValues  "

        private object[] RowValues(int row, int startCol, int numberOfCols)
        {
            if (mDtExcel == null)
            {
                return null;
            }
            object[] res;

            if (numberOfCols == 1)
            {
                if (startCol <= mDtExcel.Columns.Count)
                {
                    res = new[] {mDtExcel.Rows[row][startCol - 1]};
                }
                else
                {
                    throw new BadUsageException("The Start Column is Greater Than the Columns Count in " + mSheetName);
                }
            }
            else
            {
                res = new object[numberOfCols];
                var counter = 0;
                var colPos = startCol - 1;

                //If the numbers of selected columns is Greater Than columns in DataTable
                while (counter < res.Length && colPos < mDtExcel.Columns.Count)
                {
                    res[counter] = mDtExcel.Rows[row][colPos];
                    counter++;
                    colPos++;
                }
            }

            return res;
        }

        private void WriteRowValues(object[] values, int row, int startCol)
        {
            if (mDtExcel == null)
                return;

            DataRow dtRow;
            if (row > mDtExcel.Rows.Count - 1)
            {
                dtRow = mDtExcel.NewRow();
                mDtExcel.Rows.Add(dtRow);
            }
            else
            {
                dtRow = mDtExcel.Rows[row];
            }

            var colPos = startCol - 1;
            var i = 0;
            while (colPos < mDtExcel.Columns.Count && i < values.Length)
            {
                dtRow[colPos] = values[i];
                colPos++;
                i++;
            }
        }

        #endregion

        #region "  InsertRecords  "

        /// <summary>Insert all the records in the specified Excel File.</summary>
        /// <param name="records">The records to insert.</param>
        public override void InsertRecords(object[] records)
        {
            if (records == null || records.Length == 0)
                return;

            try
            {
                var recordNumber = 0;
                Notify(mNotifyHandler, mProgressMode, 0, records.Length);

                OpenOrCreateWorkbook(mFileName);

                mDtExcel = mDsExcel.Tables[0].Clone();

                //Verify Properties Limits.
                ValidatePropertiesForInsert(records);

                for (var row = mStartRow - 1; row < records.Length; row++)
                {
                    recordNumber++;
                    Notify(mNotifyHandler, mProgressMode, recordNumber, records.Length);

                    WriteRowValues(RecordToValues(records[row]), row, mStartColumn);
                }

                SaveWorkbook();
            }
            catch
            {
                throw;
            }
            finally
            {
                CloseAndCleanUp();
            }
        }

        #endregion

        #region "  ExtractRecords  "

        /// <summary>Returns the records extracted from Excel file.</summary>
        /// <returns>The extracted records.</returns>
        public override object[] ExtractRecords()
        {
            var res = new ArrayList();

            try
            {
                Notify(mNotifyHandler, mProgressMode, 0, -1);

                var colValues = new object[mRecordInfo.mFieldCount];

                OpenWorkbook();

                //Verify Properties Limits.
                ValidatePropertiesForExtract();

                //mStartRow-1 because rows start at 0, and the user
                //can be assign StartRow =2 
                for (var recordNumber = mStartRow - 1; recordNumber < mDtExcel.Rows.Count; recordNumber++)
                {
                    try
                    {
                        Notify(mNotifyHandler, mProgressMode, recordNumber, -1);

                        colValues = RowValues(recordNumber, mStartColumn, mRecordInfo.mFieldCount);

                        var record = ValuesToRecord(colValues);
                        res.Add(record);
                    }
                    catch (Exception ex)
                    {
                        switch (mErrorManager.ErrorMode)
                        {
                            case ErrorMode.ThrowException:
                                throw;
                            case ErrorMode.IgnoreAndContinue:
                                break;
                            case ErrorMode.SaveAndContinue:
                                AddError(recordNumber + 1, ex, string.Empty);
                                break;
                        }
                    }
                }
            }
            catch
            {
                throw;
            }
            finally
            {
                CloseAndCleanUp();
            }

            return (object[]) res.ToArray(RecordType);
        }

        #endregion

        #region Validation

        private void ValidatePropertiesForExtract()
        {
            if (mFileName == String.Empty)
                throw new BadUsageException("You need to specify the WorkBookFile of the ExcelDataLink.");

            if (mStartRow <= 0)
                throw new BadUsageException("The StartRow Property is Invalid. Must be Greater or Equal Than 1.");

            if (mStartRow > mDtExcel.Rows.Count)
                throw new BadUsageException("The StartRow Property is Invalid. Must be Less or Equal to Worksheet row's count.");

            if (mStartColumn <= 0)
                throw new BadUsageException("The StartColumn Property is Invalid. Must be Greater or Equal Than 1.");

            if (mStartColumn > mDtExcel.Columns.Count)
                throw new BadUsageException("The StartColumn Property is Invalid. Must be Less or Equal To Worksheet Column's count.");
        }

        private void ValidatePropertiesForInsert(object[] records)
        {
            if (mFileName == String.Empty)
                throw new BadUsageException("You need to specify the WorkBookFile of the ExcelDataLink.");

            if (mStartRow <= 0)
                throw new BadUsageException("The StartRow Property is Invalid. Must be Greater or Equal Than 1.");

            if (mStartRow > records.Length)
                throw new BadUsageException("The StartRow Property is Invalid. Must be Less or Equal to Worksheet row's count.");

            if (mStartColumn <= 0)
                throw new BadUsageException("The StartColumn Property is Invalid. Must be Greater or Equal Than 1.");

            if (mStartColumn > mRecordInfo.mFieldCount)
                throw new BadUsageException("The StartColumn Property is Invalid. Must be Less or Equal To Record Type Propertie's count.");
        }

        #endregion
    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU General Public License (GPLv3)


Written By
Software Developer (Senior)
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