3

Does anyone know the best way to create a SQL Server CE (Compact 3.5) table based on the schema of a DataTable at runtime? I don’t want to have to formulate a CREATE TABLE statement based on all the different possible datatypes, etc.

As a bonus – do you then know how to fill it directly from a datatable?

3 Answers 3

4

I've used and updated the Code from Ben Breen:

  • Changed GetSqlServerCETypeName to work with all types
  • Added a function fow a whole Dataset
  • And some minor tweaks

GetSqlDBTypeFromType

/// <summary>
    /// Gets the correct SqlDBType for a given .NET type. Useful for working with SQL CE.
    /// </summary>
    /// <param name="type">The .Net Type used to find the SqlDBType.</param>
    /// <returns>The correct SqlDbType for the .Net type passed in.</returns>
    public static SqlDbType GetSqlDBTypeFromType(Type type)
    {
        TypeConverter tc = TypeDescriptor.GetConverter(typeof(DbType));
        if (/*tc.CanConvertFrom(type)*/ true)
        {
            DbType dbType = (DbType)tc.ConvertFrom(type.Name);
            // A cheat, but the parameter class knows how to map between DbType and SqlDBType.
            SqlCeParameter param = new SqlCeParameter();
            param.DbType = dbType;
            return param.SqlDbType; // The parameter class did the conversion for us!!
        }
        else
        {
            throw new Exception("Cannot get SqlDbType from: " + type.Name);
        }
    }

GetSqlServerCETypeName

/// <summary>
    /// The method gets the SQL CE type name for use in SQL Statements such as CREATE TABLE
    /// </summary>
    /// <param name="dbType">The SqlDbType to get the type name for</param>
    /// <param name="size">The size where applicable e.g. to create a nchar(n) type where n is the size passed in.</param>
    /// <returns>The SQL CE compatible type for use in SQL Statements</returns>
    public static string GetSqlServerCETypeName(SqlDbType dbType, int size)
    {
        // Conversions according to: http://msdn.microsoft.com/en-us/library/ms173018.aspx
        bool max = (size == int.MaxValue) ? true : false;
        bool over4k = (size > 4000) ? true : false;

        if (size>0)
        {
            return string.Format(Enum.GetName(typeof(SqlDbType), dbType)+" ({0})", size); 
        }
        else
        {
            return Enum.GetName(typeof(SqlDbType), dbType);
        }
    }

GetCreateTableStatement

/// <summary>
    /// Genenerates a SQL CE compatible CREATE TABLE statement based on a schema obtained from
    /// a SqlDataReader or a SqlCeDataReader.
    /// </summary>
    /// <param name="tableName">The name of the table to be created.</param>
    /// <param name="schema">The schema returned from reader.GetSchemaTable().</param>
    /// <returns>The CREATE TABLE... Statement for the given schema.</returns>
    public static string GetCreateTableStatement(DataTable table)
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(string.Format("CREATE TABLE [{0}] (", table.TableName));

        foreach (DataColumn col in table.Columns)
        {
            SqlDbType dbType = GetSqlDBTypeFromType(col.DataType);
            builder.Append("[");
            builder.Append(col.ColumnName);
            builder.Append("]");
            builder.Append(" ");
            builder.Append(GetSqlServerCETypeName(dbType, col.MaxLength));
            builder.Append(", ");
        }

        if (table.Columns.Count > 0) builder.Length = builder.Length - 2;

        builder.Append(")");
        return builder.ToString();
    }

CreateFromDataset

public static void CreateFromDataset(DataSet set, SqlCeConnection conn)
    {
        conn.Open();
        SqlCeCommand cmd;
        foreach (DataTable table in set.Tables)
        {
            string createSql = copyDB.GetCreateTableStatement(table);
            Console.WriteLine(createSql);

            cmd = new SqlCeCommand(createSql, conn);
            Console.WriteLine(cmd.ExecuteNonQuery());
        }
        conn.Close();
    }

}
Sign up to request clarification or add additional context in comments.

Comments

3

I coded a reasonable solution, but was hoping to avoid case statements for the SQL types:

Firstly a neat trick to convert from a .NET type to a SqlDBType:

/// <summary>
/// Gets the correct SqlDBType for a given .NET type. Useful for working with SQL CE.
/// </summary>
/// <param name="type">The .Net Type used to find the SqlDBType.</param>
/// <returns>The correct SqlDbType for the .Net type passed in.</returns>
public static SqlDbType GetSqlDBTypeFromType(Type type)
{
    TypeConverter tc = TypeDescriptor.GetConverter(typeof(DbType));
    if (/*tc.CanConvertFrom(type)*/ true)
    {
        DbType dbType = (DbType)tc.ConvertFrom(type.Name);
        // A cheat, but the parameter class knows how to map between DbType and SqlDBType.
        SqlParameter param = new SqlParameter();
        param.DbType = dbType;
        return param.SqlDbType; // The parameter class did the conversion for us!!
    }
    else
    {
        throw new Exception("Cannot get SqlDbType from: " + type.Name);
    }
}

A case statement for the types for use in SQL Statements:

    /// <summary>
            /// The method gets the SQL CE type name for use in SQL Statements such as CREATE TABLE
            /// </summary>
            /// <param name="dbType">The SqlDbType to get the type name for</param>
            /// <param name="size">The size where applicable e.g. to create a nchar(n) type where n is the size passed in.</param>
            /// <returns>The SQL CE compatible type for use in SQL Statements</returns>
            public static string GetSqlServerCETypeName(SqlDbType dbType, int size)
            {
                // Conversions according to: http://msdn.microsoft.com/en-us/library/ms173018.aspx
                bool max = (size == int.MaxValue) ? true : false;
                bool over4k = (size > 4000) ? true : false;

                switch (dbType)
                {
                    case SqlDbType.BigInt:
                        return "bigint";
                    case SqlDbType.Binary:
                        return string.Format("binary ({0})", size);
                    case SqlDbType.Bit:
                        return "bit";
                    case SqlDbType.Char:
                        if (over4k) return "ntext";
                        else return string.Format("nchar({0})", size);
ETC...

Then finally the CREATE TABLE statement:

    /// <summary>
    /// Genenerates a SQL CE compatible CREATE TABLE statement based on a schema obtained from
    /// a SqlDataReader or a SqlCeDataReader.
    /// </summary>
    /// <param name="tableName">The name of the table to be created.</param>
    /// <param name="schema">The schema returned from reader.GetSchemaTable().</param>
    /// <returns>The CREATE TABLE... Statement for the given schema.</returns>
    public static string GetCreateTableStatement(string tableName, DataTable schema)
    {
        StringBuilder builder = new StringBuilder();
        builder.Append(string.Format("CREATE TABLE [{0}] (\n", tableName));

        foreach (DataRow row in schema.Rows)
        {
            string typeName = row["DataType"].ToString();
            Type type = Type.GetType(typeName);

            string name = (string)row["ColumnName"];
            int size = (int)row["ColumnSize"];

            SqlDbType dbType = GetSqlDBTypeFromType(type);

            builder.Append(name);
            builder.Append(" ");
            builder.Append(GetSqlServerCETypeName(dbType, size));
            builder.Append(", ");
        }

        if (schema.Rows.Count > 0) builder.Length = builder.Length - 2;

        builder.Append("\n)");
        return builder.ToString();
    }

2 Comments

Nice! But when you got an empty DataTable or multiple rows, it does not work properly. I replaced the "DataRow-foreach"-loop with a loop on the schema.Columns and use the ColumnName- and MaxLength-properties of each column.
You are also now creating tables without their identity column settings, etc. All that is available through a FillSchema call though, so you can build it up dynamically.
0

For those that want to do this on normal SQL Server setups (non-CE), I've managed to modify this to work with a SQL Server 2016 setup, when I needed to programmatically create database tables based off of giant csv files (using a 4.7.1 .NET Framework). Please note that this has a check on the max # of columns, but not the max # of rows, so you may hit an error if you don't account for that when dealing with large csv files.

using System.Data;
using System.Data.SqlClient;
using System.ComponentModel;
//
//
//
        private static DataTable GetDataTabletFromCSVFile(string csv_file_path)
        {
            DataTable csvData = new DataTable();
            try
            {
                using (TextFieldParser csvReader = new TextFieldParser(csv_file_path))
                {
                    csvReader.TextFieldType = FieldType.Delimited;
                    csvReader.SetDelimiters(new string[] { "," });
                    csvReader.HasFieldsEnclosedInQuotes = false;
                    string[] colFields = csvReader.ReadFields();
                    int columnCounter = 0;

                    foreach (string column in colFields)
                    {
                        if (columnCounter > 1023)
                        {
                            break; // the table has reached the maximum column size, either ignore the extra columns, or create additional linked tables (sounds like awful table design though).
                        }
                        DataColumn datecolumn = new DataColumn(column);
                        datecolumn.AllowDBNull = true;
                        csvData.Columns.Add(datecolumn);
                        columnCounter++;
                    }
                    while (!csvReader.EndOfData)
                    {
                        string[] fieldData = csvReader.ReadFields();
                        Array.Resize(ref fieldData, 1024);   //max number of columns is 1024 in SQL table, and we're not going through the trouble of making a Sparse table.
                        //Making empty value as null
                        for (int i = 0; i < fieldData.Length; i++)
                        {
                            if (fieldData[i] == "")
                            {
                                fieldData[i] = null;
                            }
                        }
                        csvData.Rows.Add(fieldData);
                    }
                }
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
            return csvData;
        }

        /** <summary>
        * Gets the correct SqlDBType for a given .NET type. Useful for working with SQL.
        * </summary>
        * <param name="type">The .Net Type used to find the SqlDBType.</param>
        * <returns>The correct SqlDbType for the .Net type passed in.</returns>
        */
        public static SqlDbType GetSqlDBTypeFromType(Type type)
        {
            TypeConverter tc = TypeDescriptor.GetConverter(typeof(DbType));

            DbType dbType = (DbType)tc.ConvertFrom(type.Name);
            // A cheat, but the parameter class knows how to map between DbType and SqlDBType.
            SqlParameter param = new SqlParameter();
            param.DbType = dbType;

            return param.SqlDbType; // The parameter class did the conversion for us!!

        }

        /**
        * <summary>
        * The method gets the SQL type name for use in SQL Statements such as CREATE TABLE
        * </summary>
        * <param name="dbType">The SqlDbType to get the type name for</param>
        * <param name="size">The size where applicable e.g. to create a nchar(n) type where n is the size passed in.</param>
        * <returns>A string of the SQL compatible type for use in SQL Statements</returns>
        */
        public static string GetSqlServerTypeName(SqlDbType dbType, int size)
        {
            // Conversions according to: https://learn.microsoft.com/en-us/dotnet/framework/data/adonet/sql-server-data-type-mappings
            bool max = (size == int.MaxValue || size == -1) ? true : false;
            string returnVal = "";

            if (max)
            {
                returnVal = Enum.GetName(typeof(SqlDbType), dbType) + " (max)";
            }
            else if (size > 0)
            {
                returnVal = string.Format(Enum.GetName(typeof(SqlDbType), dbType) + " ({0})", size);
            }
            else
            {
                returnVal = Enum.GetName(typeof(SqlDbType), dbType);
            }

            return returnVal;
        }

        /**
         * <summary>
        * Genenerates a SQL compatible CREATE TABLE statement based on a schema obtained from
        * a SqlDataTable.
        * </summary>
        * <param name="table">The name of the table to be created.</param>
        * <returns>The CREATE TABLE... Statement for the given data table.</returns>
        */
        public static string GetCreateTableStatement(DataTable table)
        {
            StringBuilder builder = new StringBuilder();
            builder.Append(string.Format("CREATE TABLE [{0}] (", table.TableName));
            int primaryCol = 0;

            foreach (DataColumn col in table.Columns)
            {
                SqlDbType dbType = GetSqlDBTypeFromType(col.DataType);
                builder.Append("[");
                builder.Append(col.ColumnName);
                builder.Append("]");
                builder.Append(" ");
                builder.Append(GetSqlServerTypeName(dbType, col.MaxLength));
                //if on first column, assume it's a "PRIMARY KEY" (for now)
                if(primaryCol == 0)
                {
                    builder.Append(" PRIMARY KEY");
                }
                builder.Append(", ");
                primaryCol++;
            }

            if (table.Columns.Count > 0) builder.Length = builder.Length - 2;

            builder.Append(")");
            return builder.ToString();
        }


        /**
         * <summary>
        * Genenerates a SQL compatible CREATE TABLE statement based on a schema obtained from
        * a SqlDataTable.
        * </summary>
        * <param name="dtable">The name of the table to be created.</param>
        * <param name="conn">The SQL Connection to the database that the table will be created in.</param>
         */
        public static void CreateFromDataTable(DataTable dTable, SqlConnection conn)
        {
            bool openedHere = false;
            if (conn.State == ConnectionState.Closed)
            {
                conn.Open();
                openedHere = true;
            }
            SqlCommand cmd;

            string createSql = GetCreateTableStatement(dTable);
            Console.WriteLine(createSql);

            cmd = new SqlCommand(createSql, conn);
            Console.WriteLine(cmd.ExecuteNonQuery());

            if (openedHere)
            {
                conn.Close();
            }
        }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.