sábado, 11 de julio de 2015

DTO, database to entity class mapper

namespace EntityJustWorks.SQL
{
    using Microsoft.CSharp;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.Diagnostics;
    using System.IO;
    using System.Reflection;

    public static class Convert

    {
        /// <summary> 
        /// Generates a C# class code file from a Database given an SQL connection string and table name.
        /// </summary>
        public static string SQLToCSharp(string ConnectionString, string TableName)
        {
            DataTable table = Query.QueryToDataTable(ConnectionString, "SELECT TOP 1 * FROM [{0}]", TableName);
            return Code.DatatableToCSharp(table);
        }

        /// <summary>

        /// Creates an SQL table from a class object.
        /// </summary>
        public static bool ClassToSQL<T>(string ConnectionString, params T[] ClassCollection) where T : class
        {
            string createTableScript = Script.CreateTable<T>(ClassCollection);
            return (Query.ExecuteNonQuery(ConnectionString, createTableScript) == -1);
        }
    }

    /// <summary>

    /// DataTable/Class Mapping Class
    /// </summary>
    public static class Map
    {
        /// <summary>
        /// Fills properties of a class from a row of a DataTable where the name of the property matches the column name from that DataTable.
        /// It does this for each row in the DataTable, returning a List of classes.
        /// </summary>
        /// <typeparam name="T">The class type that is to be returned.</typeparam>
        /// <param name="Table">DataTable to fill from.</param>
        /// <returns>A list of ClassType with its properties set to the data from the matching columns from the DataTable.</returns>
        public static IList<T> DatatableToClass<T>(DataTable Table) where T : class, new()
        {
            if (!Helper.IsValidDatatable(Table))
                return new List<T>();

            Type classType = typeof(T);

            IList<PropertyInfo> propertyList = classType.GetProperties();

            // Parameter class has no public properties.

            if (propertyList.Count == 0)
                return new List<T>();

            List<string> columnNames = Table.Columns.Cast<DataColumn>().Select(column => column.ColumnName).ToList();


            List<T> result = new List<T>();

            try
            {
                foreach (DataRow row in Table.Rows)
                {
                    T classObject = new T();
                    foreach (PropertyInfo property in propertyList)
                    {
                        if (property != null && property.CanWrite)   // Make sure property isn't read only
                        {
                            if (columnNames.Contains(property.Name))  // If property is a column name
                            {
                                if (row[property.Name] != System.DBNull.Value)   // Don't copy over DBNull
                                {
                                    object propertyValue = System.Convert.ChangeType(
                                            row[property.Name],
                                            property.PropertyType
                                        );
                                    property.SetValue(classObject, propertyValue, null);
                                }
                            }
                        }
                    }
                    result.Add(classObject);
                }
                return result;
            }
            catch
            {
                return new List<T>();
            }
        }

        /// <summary>

        /// Creates a DataTable from a class type's public properties and adds a new DataRow to the table for each class passed as a parameter.
        /// The DataColumns of the table will match the name and type of the public properties.
        /// </summary>
        /// <param name="ClassCollection">A class or array of class to fill the DataTable with.</param>
        /// <returns>A DataTable who's DataColumns match the name and type of each class T's public properties.</returns>
        public static DataTable ClassToDatatable<T>(params T[] ClassCollection) where T : class
        {
            DataTable result = ClassToDatatable<T>();

            if (Helper.IsValidDatatable(result, IgnoreRows: true))

                return new DataTable();
            if (Helper.IsCollectionEmpty(ClassCollection))
                return result;   // Returns and empty DataTable with columns defined (table schema)

            foreach (T classObject in ClassCollection)

            {
                ClassToDataRow(ref result, classObject);
            }

            return result;

        }

        /// <summary>

        /// Creates a DataTable from a class type's public properties. The DataColumns of the table will match the name and type of the public properties.
        /// </summary>
        /// <typeparam name="T">The type of the class to create a DataTable from.</typeparam>
        /// <returns>A DataTable who's DataColumns match the name and type of each class T's public properties.</returns>
        public static DataTable ClassToDatatable<T>() where T : class
        {
            Type classType = typeof(T);
            DataTable result = new DataTable(classType.UnderlyingSystemType.Name);

            foreach (PropertyInfo property in classType.GetProperties())

            {
                DataColumn column = new DataColumn();
                column.ColumnName = property.Name;
                column.DataType = property.PropertyType;
                
                if (Helper.IsNullableType(column.DataType) && column.DataType.IsGenericType)
                {   
                    // If Nullable<>, this is how we get the underlying Type...
                    column.DataType = column.DataType.GenericTypeArguments.FirstOrDefault();
                }
                else
                {   
                    // True by default, so set it false
                    column.AllowDBNull = false;
                }

                // Add column

                result.Columns.Add(column);
            }
            return result;
        }

        /// <summary>

        /// Adds a DataRow to a DataTable from the public properties of a class.
        /// </summary>
        /// <param name="Table">A reference to the DataTable to insert the DataRow into.</param>
        /// <param name="ClassObject">The class containing the data to fill the DataRow from.</param>
        private static void ClassToDataRow<T>(ref DataTable Table, T ClassObject) where T : class
        {
            DataRow row = Table.NewRow();
            foreach (PropertyInfo property in typeof(T).GetProperties())
            {
                if (Table.Columns.Contains(property.Name))
                {
                    if (Table.Columns[property.Name] != null)
                    {
                        row[property.Name] = property.GetValue(ClassObject, null);
                    }
                }
            }
            Table.Rows.Add(row);
        }
    }

    /// <summary>

    /// SQL Query Helper Class
    /// </summary>
    public static class Query
    {
        /// <summary>
        /// Runs a SQL query and returns the results as a List of the specified class
        /// </summary>
        /// <typeparam name="T">The type the result will be returned as.</typeparam>
        /// <param name="ConnectionString">The SQL connection string.</param>
        /// <param name="FormatString_Query">A SQL command that will be passed to string.Format().</param>
        /// <param name="FormatString_Parameters">The parameters for string.Format().</param>
        /// <returns>A List of classes that represent the records returned.</returns>
        public static IList<T> QueryToClass<T>(string ConnectionString, string FormatString_Query, params object[] FormatString_Parameters) where T : class, new()
        {
            IList<T> result = new List<T>();
            DataTable tableQueryResult = QueryToDataTable(ConnectionString, string.Format(FormatString_Query, FormatString_Parameters));
            if (Helper.IsValidDatatable(tableQueryResult))
            {
                result = Map.DatatableToClass<T>(tableQueryResult);
            }
            return result;
        }

        /// <summary>

        /// Executes an SQL query and returns the results as a DataTable.
        /// </summary>
        /// <param name="ConnectionString">The SQL connection string.</param>
        /// <param name="FormatString_Query">A SQL command that will be passed to string.Format().</param>
        /// <param name="FormatString_Parameters">The parameters for string.Format().</param>
        /// <returns>The results of the query as a DataTable.</returns>
        public static DataTable QueryToDataTable(string ConnectionString, string FormatString_Query, params object[] FormatString_Parameters)
        {
            try
            {
                DataTable result = new DataTable();

                using (SqlConnection sqlConnection = new SqlConnection(ConnectionString))

                {
                    sqlConnection.Open();

                    using (SqlCommand sqlCommand = sqlConnection.CreateCommand())

                    {
                        sqlCommand.CommandText = string.Format(FormatString_Query, FormatString_Parameters);
                        sqlCommand.CommandType = CommandType.Text;

                        SqlDataAdapter sqlAdapter = new SqlDataAdapter(sqlCommand);

                        sqlAdapter.Fill(result);
                    }
                }
                return result;
            }
            catch
            {
                return new DataTable();
            }
        }

        /// <summary>

        /// Executes a query, and returns the first column of the first row in the result set returned by the query.
        /// </summary>
        /// <typeparam name="T">The type the result will be returned as.</typeparam>
        /// <param name="ConnectionString">>The SQL connection string.</param>
        /// <param name="FormatString_Query">The SQL query as string.Format string.</param>
        /// <param name="FormatString_Parameters">The string.Format parameters.</param>
        /// <returns>The  first column of the first row in the result, converted and casted to type T.</returns>
        public static T QueryToScalarType<T>(string ConnectionString, string FormatString_Query, params object[] FormatString_Parameters)
        {
            try
            {
                object result = new object();
                using (SqlConnection sqlConnection = new SqlConnection(ConnectionString))
                {
                    sqlConnection.Open();

                    using (SqlCommand sqlCommand = sqlConnection.CreateCommand())

                    {
                        sqlCommand.CommandText = string.Format(FormatString_Query, FormatString_Parameters);
                        sqlCommand.CommandType = CommandType.Text;

                        result = System.Convert.ChangeType(sqlCommand.ExecuteScalar(), typeof(T));

                    }
                }
                return (T)result;
            }
            catch
            {
                return (T)new object();
            }
        }

        /// <summary>

        /// Executes a non-query SQL command, such as INSERT or DELETE
        /// </summary>
        /// <param name="ConnectionString">The connection string.</param>
        /// <param name="FormatString_Command">The SQL command, as a format string.</param>
        /// <param name="FormatString_Parameters">The parameters for the format string.</param>
        /// <returns>The number of rows affected, or -1 on errors.</returns>
        public static int ExecuteNonQuery(string ConnectionString, string FormatString_Command, params object[] FormatString_Parameters)
        {
            try
            {
                int rowsAffected = 0;

                using (SqlConnection sqlConnection = new SqlConnection(ConnectionString))

                {
                    sqlConnection.Open();
                    using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
                    {
                        string commandText = string.Format(FormatString_Command, FormatString_Parameters);

                        sqlCommand.CommandText = commandText;

                        sqlCommand.CommandType = CommandType.Text;
                        rowsAffected = sqlCommand.ExecuteNonQuery();
                    }
                }

                return rowsAffected;

            }
            catch
            {
                return 0;
            }
        }
    }

    /// <summary>

    /// SQL Script Generation Class
    /// </summary>
    public static class Script
    {
        /// <summary>
        /// Creates a SQL script that inserts the values of the specified classes' public properties into a table.
        /// </summary>
        public static string InsertInto<T>(params T[] ClassObjects) where T : class
        {
            DataTable table = Map.ClassToDatatable<T>(ClassObjects);
            return InsertInto(table);   // We don't need to check IsValidDatatable() because InsertInto does
        }

        /// <summary>

        /// Creates a SQL script that inserts the cell values of a DataTable's DataRows into a table.
        /// </summary>
        public static string InsertInto(DataTable Table)
        {
            if (!Helper.IsValidDatatable(Table))
                return string.Empty;

            StringBuilder result = new StringBuilder();

            foreach (DataRow row in Table.Rows)
            {
                if (row == null || row.Table.Columns.Count < 1 || row.ItemArray.Length < 1)
                    return string.Empty;

                string columns = Helper.RowToColumnString(row);

                string values = Helper.RowToValueString(row);

                if (string.IsNullOrWhiteSpace(columns) || string.IsNullOrWhiteSpace(values))

                    return string.Empty;

                result.AppendFormat("INSERT INTO [{0}] {1} VALUES {2}", Table.TableName, columns, values);

            }

            return result.ToString();

        }

        /// <summary>

        /// Creates a SQL script that creates a table where the column names match the specified class's public properties.
        /// </summary>
        public static string CreateTable<T>(params T[] ClassObjects) where T : class
        {
            DataTable table = Map.ClassToDatatable<T>(ClassObjects);
            return Script.CreateTable(table);
        }

        /// <summary>

        /// Creates a SQL script that creates a table where the columns matches that of the specified DataTable.
        /// </summary>
        public static string CreateTable(DataTable Table)
        {
            if (Helper.IsValidDatatable(Table, IgnoreRows: true))
                return string.Empty;

            StringBuilder result = new StringBuilder();

            result.AppendFormat("CREATE TABLE [{0}] ({1}", Table.TableName, Environment.NewLine);

            bool FirstTime = true;

            foreach (DataColumn column in Table.Columns.OfType<DataColumn>())
            {
                if (FirstTime) FirstTime = false;
                else
                    result.Append(",");

                result.AppendFormat("[{0}] {1} {2}NULL{3}",

                    column.ColumnName,
                    GetDataTypeString(column.DataType),
                    column.AllowDBNull ? "" : "NOT ",
                    Environment.NewLine
                );
            }
            result.AppendFormat(") ON [PRIMARY]{0}GO", Environment.NewLine);

            return result.ToString();

        }

        /// <summary>

        /// Returns the SQL data type equivalent, as a string for use in SQL script generation methods.
        /// </summary>
        private static string GetDataTypeString(Type DataType)
        {
            switch (DataType.Name)
            {
                case "Boolean": return "[bit]";
                case "Char": return "[char]";
                case "SByte": return "[tinyint]";
                case "Int16": return "[smallint]";
                case "Int32": return "[int]";
                case "Int64": return "[bigint]";
                case "Byte": return "[tinyint] UNSIGNED";
                case "UInt16": return "[smallint] UNSIGNED";
                case "UInt32": return "[int] UNSIGNED";
                case "UInt64": return "[bigint] UNSIGNED";
                case "Single": return "[float]";
                case "Double": return "[double]";
                case "Decimal": return "[decimal]";
                case "DateTime": return "[datetime]";
                case "Guid": return "[uniqueidentifier]";
                case "Object": return "[variant]";
                case "String": return "[nvarchar](250)";
                default: return "[nvarchar](MAX)";
            }
        }
    }

    /// <summary>

    /// Helper Functions. Conversion, Validation
    /// </summary>
    public static class Helper
    {
        /// <summary>
        /// Indicates whether a specified DataTable is null, has zero columns, or (optionally) zero rows.
        /// </summary>
        /// <param name="Table">DataTable to check.</param>
        /// <param name="IgnoreRows">When set to true, the function will return true even if the table's row count is equal to zero.</param>
        /// <returns>False if the specified DataTable null, has zero columns, or zero rows, otherwise true.</returns>
        public static bool IsValidDatatable(DataTable Table, bool IgnoreRows = false)
        {
            if (Table == null) return false;
            if (Table.Columns.Count == 0) return false;
            if (!IgnoreRows && Table.Rows.Count == 0) return false;
            return true;
        }

        /// <summary>

        /// Indicates whether a specified Enumerable collection is null or an empty collection.
        /// </summary>
        /// <typeparam name="T">The specified type contained in the collection.</typeparam>
        /// <param name="Input">An Enumerator to the collection to check.</param>
        /// <returns>True if the specified Enumerable collection is null or empty, otherwise false.</returns>
        public static bool IsCollectionEmpty<T>(IEnumerable<T> Input)
        {
            return (Input == null || Input.Count() < 1) ? true : false;
        }

        /// <summary>

        ///  Indicates whether a specified Type can be assigned null.
        /// </summary>
        /// <param name="Input">The Type to check for nullable property.</param>
        /// <returns>True if the specified Type can be assigned null, otherwise false.</returns>
        public static bool IsNullableType(Type Input)
        {
            if (!Input.IsValueType) return true; // Reference Type
            if (Nullable.GetUnderlyingType(Input) != null) return true; // Nullable<T>
            return false;   // Value Type
        }

        /// <summary>

        /// Returns all the column names of the specified DataRow in a string delimited like and SQL INSERT INTO statement.
        /// Example: ([FullName], [Gender], [BirthDate])
        /// </summary>
        /// <returns>A string formatted like the columns specified in an SQL 'INSERT INTO' statement.</returns>
        public static string RowToColumnString(DataRow Row)
        {
            IEnumerable<string> Collection = Row.ItemArray.Select(item => item as String);
            return ListToDelimitedString(Collection, "([", "], [", "])");
        }

        /// <summary>

        /// Returns all the values the specified DataRow in as a string delimited like and SQL INSERT INTO statement.
        /// Example: ('John Doe', 'M', '10/3/1981'')
        /// </summary>
        /// <returns>A string formatted like the values specified in an SQL 'INSERT INTO' statement.</returns>
        public static string RowToValueString(DataRow Row)
        {
            IEnumerable<string> Collection = GetDatatableColumns(Row.Table).Select(c => c.ColumnName);
            return ListToDelimitedString(Collection, "('", "', '", "')");
        }

        /// <summary>

        /// Enumerates a collection as delimited collection of strings.
        /// </summary>
        /// <typeparam name="T">The Type of the collection.</typeparam>
        /// <param name="Collection">An Enumerator to a collection to populate the string.</param>
        /// <param name="Prefix">The string to prefix the result.</param>
        /// <param name="Delimiter">The string that will appear between each item in the specified collection.</param>
        /// <param name="Postfix">The string to postfix the result.</param>         
        public static string ListToDelimitedString<T>(IEnumerable<T> Collection, string Prefix, string Delimiter, string Postfix)
        {
            if (IsCollectionEmpty<T>(Collection)) return string.Empty;

            StringBuilder result = new StringBuilder();

            foreach (T item in Collection)
            {
                if (result.Length != 0)
                    result.Append(Delimiter);   // Add comma

                result.Append(EscapeSingleQuotes(item as String));

            }
            if (result.Length < 1) return string.Empty;

            result.Insert(0, Prefix);

            result.Append(Postfix);

            return result.ToString();

        }

        /// <summary>

        /// Returns an enumerator, which supports a simple iteration over a collection of all the DataColumns in a specified DataTable.
        /// </summary>
        public static IEnumerable<DataColumn> GetDatatableColumns(DataTable Input)
        {
            if (Input == null || Input.Columns.Count < 1) return new List<DataColumn>();
            return Input.Columns.OfType<DataColumn>().ToList();
        }

        /// <summary>

        /// Returns an enumerator, which supports a simple iteration over a collection of all the DataRows in a specified DataTable.
        /// </summary>
        public static IEnumerable<DataRow> GetDatatableRows(DataTable Input)
        {
            if (!IsValidDatatable(Input)) return new List<DataRow>();
            return Input.Rows.OfType<DataRow>().ToList();
        }

        /// <summary>

        /// Returns a new string in which all occurrences of the single quote character in the current instance are replaced with a back-tick character.
        /// </summary>
        public static string EscapeSingleQuotes(string Input)
        {
            return Input.Replace('\'', '`'); // Replace with back-tick
        }
    }

    /// <summary>

    /// C# Code Generation Class
    /// </summary>
    public static class Code
    {
        /// <summary> 
        /// Generates a C# class code file from a DataTable.
        /// </summary>
        public static string DatatableToCSharp(DataTable Table)
        {
            string className = Table.TableName;

            if (string.IsNullOrWhiteSpace(className))

            {
                return "// Class cannot be created: DataTable.TableName must have a value to use as the name of the class";
            }

            // Create the class

            CodeTypeDeclaration classDeclaration = CreateClass(className);

            // Add public properties

            foreach (DataColumn column in Table.Columns)
            {
                classDeclaration.Members.Add(CreateProperty(column.ColumnName, column.DataType));
            }

            // Add Class to Namespace

            string namespaceName = new StackFrame(2).GetMethod().DeclaringType.Namespace;// "EntityJustWorks.AutoGeneratedClassObject";
            CodeNamespace codeNamespace = new CodeNamespace(namespaceName);
            codeNamespace.Types.Add(classDeclaration);

            // Generate code

            string filename = string.Format("{0}.{1}.cs", namespaceName, className);
            CreateCodeFile(filename, codeNamespace);

            // Return filename

            return filename;
        }

        #region Private Members

        private static CodeTypeDeclaration CreateClass(string name)
        {
            CodeTypeDeclaration result = new CodeTypeDeclaration(name);
            result.Attributes = MemberAttributes.Public;
            result.Members.Add(CreateConstructor(name)); // Add class constructor
            return result;
        }

        private static CodeConstructor CreateConstructor(string className)

        {
            CodeConstructor result = new CodeConstructor();
            result.Attributes = MemberAttributes.Public;
            result.Name = className;
            return result;
        }

        private static CodeMemberField CreateProperty(string name, Type type)

        {
            // This is a little hack. Since you cant create auto properties in CodeDOM,
            //  we make the getter and setter part of the member name.
            // This leaves behind a trailing semicolon that we comment out.
            //  Later, we remove the commented out semicolons.
            string memberName = name + "\t{ get; set; }//";

            CodeMemberField result = new CodeMemberField(type, memberName);

            result.Attributes = MemberAttributes.Public | MemberAttributes.Final;
            return result;
        }

        private static void CreateCodeFile(string filename, CodeNamespace codeNamespace)

        {
            // CodeGeneratorOptions so the output is clean and easy to read
            CodeGeneratorOptions codeOptions = new CodeGeneratorOptions();
            codeOptions.BlankLinesBetweenMembers = false;
            codeOptions.VerbatimOrder = true;
            codeOptions.BracingStyle = "C";
            codeOptions.IndentString = "\t";

            // Create the code file

            using (TextWriter textWriter = new StreamWriter(filename))
            {
                CSharpCodeProvider codeProvider = new CSharpCodeProvider();
                codeProvider.GenerateCodeFromNamespace(codeNamespace, textWriter, codeOptions);
            }

            // Correct our little auto-property 'hack'

            File.WriteAllText(filename, File.ReadAllText(filename).Replace("//;", ""));
        }
        #endregion
    }
}


source link