views:

1773

answers:

2

Does anyone know the best way to create a SqlCe (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 data-types, etc.

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

A: 

I think step 1 would be to call the WriteXml() method on your DataTable, which will export the data and the schema either to a file or to a stream. Step 2 would be importing the XML from step 1 into your SqlCe database (I'm not sure how to do Step 2, but I'm sure it just involves creating the right type of SqlCe object and loading the XML).

MusiGenesis
Yet another "what the hell?" down-vote.
MusiGenesis
Maybe because you said "insert magic" and then said that you don't know how to do said magic... I would downvote because you give advice without knowing how to do it.
Jason Short
+1  A: 

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();
    }
Ben Breen
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.
Lars
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.
Jason Short