【问题标题】:Creating a SQL Server table from a C# datatable从 C# 数据表创建 SQL Server 表
【发布时间】:2010-11-23 20:07:19
【问题描述】:

我有一个使用 C# 手动创建并加载数据的 DataTable。

在 SQL Server 2005 中使用 DataTable 中的列和数据创建表的最有效方法是什么?

【问题讨论】:

    标签: c# sql-server ado.net datatable


    【解决方案1】:
    public static string CreateTABLE(string tableName, DataTable table)
    {
        string sqlsc;
        sqlsc = "CREATE TABLE " + tableName + "(";
        for (int i = 0; i < table.Columns.Count; i++)
        {
            sqlsc += "\n [" + table.Columns[i].ColumnName + "] ";
            string columnType = table.Columns[i].DataType.ToString();
            switch (columnType)
            {
                case "System.Int32":
                    sqlsc += " int ";
                    break;
                case "System.Int64":
                    sqlsc += " bigint ";
                    break;
                case "System.Int16":
                    sqlsc += " smallint";
                    break;
                case "System.Byte":
                    sqlsc += " tinyint";
                    break;
                case "System.Decimal":
                    sqlsc += " decimal ";
                    break;
                case "System.DateTime":
                    sqlsc += " datetime ";
                    break;
                case "System.String":
                default:
                    sqlsc += string.Format(" nvarchar({0}) ", table.Columns[i].MaxLength == -1 ? "max" : table.Columns[i].MaxLength.ToString());
                    break;
            }
            if (table.Columns[i].AutoIncrement)
                sqlsc += " IDENTITY(" + table.Columns[i].AutoIncrementSeed.ToString() + "," + table.Columns[i].AutoIncrementStep.ToString() + ") ";
            if (!table.Columns[i].AllowDBNull)
                sqlsc += " NOT NULL ";
            sqlsc += ",";
        }
        return sqlsc.Substring(0,sqlsc.Length-1) + "\n)";
    }
    

    【讨论】:

    • 我相信这段代码(或一些代码审查的近似值 - 如果未使用,为什么要通过连接,为什么将类型与字符串比较等进行比较)比接受的答案更好。如果不事先在服务器上定义表值参数的类型,则接受的答案将不起作用,至少在我的情况下,这违背了能够及时创建表的意义。
    • 顺便说一句,如果您已经在 DataTable 上使用了 SqlDataAdapter.FillSchema() 并且正在恢复备份,则可以使用 DataTable.TableName 作为名称并删除参数。
    • 您必须将 nvarchar 大小乘以 2。否则您可能会遇到截断问题。出于性能原因,我还避免在循环内使用 string.format。 sqlsc += "nvarchar(" + (table.Columns[i].MaxLength == -1 || table.Columns[i].MaxLength > 4000 ? "max" : (table.Columns[i].MaxLength * 2) .ToString()) + ")";
    • 小数松散精度和比例
    【解决方案2】:

    在 SQL 中从客户端提供的 Datatable 对象定义创建表有点不寻常。表是 SQL 中精心设计的实体,在部署时考虑选择合适的磁盘,在设计时考虑索引以及正确建模数据库所涉及的所有问题。

    您最好解释一下您想要达到的目标,以便我们了解应该提供什么建议。

    附带说明,在 SQL 2008 中,有一种非常简单的方法可以从客户端定义的 Datatable 中创建表:将 DataTable 作为 Table 值参数传递,然后发出 SELECT * INTO &lt;tablename&gt; FROM @tvp,这将有效地传递定义将Datatable 其内容数据转换成SQL中的真实表。

    【讨论】:

    • +1 使用 TVF 的绝妙技巧!从来没有以这种方式考虑过该功能:-)但是正如您自己所说的那样-这可能几乎不会在现实生活中使用...
    • 其实这个在一个项目的研究阶段用的比较多。例如,我正在尝试将旧 FoxPro 数据库导入 SQL Server。我正在努力制作每一个专栏。目前,把那部分做好并不重要。重要的是让数据进入 SQL Server 以便我可以更轻松地对其进行操作——包括弄清楚我的字符串 cols 的宽度(现在,它们都是 varchar(max) 而我看到的是什么。
    • Leonard Lobel 写了关于 SQL Server 2K8 Table-Valued Parameters and C# Custom Iterators 的文章。据我了解,目标表和user-defined table type 必须已经存在于数据库中。如果这不是您的选择,使用 SQL Server 管理对象可能会有所帮助 - 请参阅 codeproject.com/Articles/17169/…
    • 我感觉这个解决方案违背了自动创建表的目的。如果非要手动创建自定义类型,还不如创建表!我希望能够有一段代码来嗅探表模式并尽其所知创建相应的表。
    • @DavidAirapetyan 你是对的。 Remus 的 tvp 参数提示不起作用,因为您必须提前在数据库端定义表类型,这正是用户试图解决的问题。
    【解决方案3】:

    我知道这个问题已经很老了,但我只是有一些非常相似的东西需要写。我采用了我所做的并更改了 Amin 和 rasputino 提供的示例,并创建了一个仅输出 SQL 的示例。我添加了一些功能并避免了串联,以帮助改进本来就表现不佳的流程。

    /// <summary>
    /// Inspects a DataTable and return a SQL string that can be used to CREATE a TABLE in SQL Server.
    /// </summary>
    /// <param name="table">System.Data.DataTable object to be inspected for building the SQL CREATE TABLE statement.</param>
    /// <returns>String of SQL</returns>
    public static string GetCreateTableSql(DataTable table)
    {
        StringBuilder sql = new StringBuilder();
        StringBuilder alterSql = new StringBuilder();
    
        sql.AppendFormat("CREATE TABLE [{0}] (", table.TableName);
    
        for (int i = 0; i < table.Columns.Count; i++)
        {
            bool isNumeric = false;
            bool usesColumnDefault = true;
    
            sql.AppendFormat("\n\t[{0}]", table.Columns[i].ColumnName);
    
            switch (table.Columns[i].DataType.ToString().ToUpper())
            {
                case "SYSTEM.INT16":
                    sql.Append(" smallint");
                    isNumeric = true;
                    break;
                case "SYSTEM.INT32":
                    sql.Append(" int");
                    isNumeric = true;
                    break;
                case "SYSTEM.INT64":
                    sql.Append(" bigint");
                    isNumeric = true;
                    break;
                case "SYSTEM.DATETIME":
                    sql.Append(" datetime");
                    usesColumnDefault = false;
                    break;
                case "SYSTEM.STRING":
                    sql.AppendFormat(" nvarchar({0})", table.Columns[i].MaxLength);
                    break;
                case "SYSTEM.SINGLE":
                    sql.Append(" single");
                    isNumeric = true;
                    break;
                case "SYSTEM.DOUBLE":
                    sql.Append(" double");
                    isNumeric = true;
                    break;
                case "SYSTEM.DECIMAL":
                    sql.AppendFormat(" decimal(18, 6)");
                    isNumeric = true;
                    break;
                default:
                    sql.AppendFormat(" nvarchar({0})", table.Columns[i].MaxLength);
                    break;
            }
    
            if (table.Columns[i].AutoIncrement)
            {
                sql.AppendFormat(" IDENTITY({0},{1})", 
                    table.Columns[i].AutoIncrementSeed, 
                    table.Columns[i].AutoIncrementStep);
            }
            else
            {
                // DataColumns will add a blank DefaultValue for any AutoIncrement column. 
                // We only want to create an ALTER statement for those columns that are not set to AutoIncrement. 
                if (table.Columns[i].DefaultValue != null)
                {
                    if (usesColumnDefault)
                    {
                        if (isNumeric)
                        {
                            alterSql.AppendFormat("\nALTER TABLE {0} ADD CONSTRAINT [DF_{0}_{1}]  DEFAULT ({2}) FOR [{1}];", 
                                table.TableName, 
                                table.Columns[i].ColumnName, 
                                table.Columns[i].DefaultValue);
                        }
                        else
                        {
                            alterSql.AppendFormat("\nALTER TABLE {0} ADD CONSTRAINT [DF_{0}_{1}]  DEFAULT ('{2}') FOR [{1}];", 
                                table.TableName, 
                                table.Columns[i].ColumnName, 
                                table.Columns[i].DefaultValue);
                        }
                    }
                    else
                    {
                        // Default values on Date columns, e.g., "DateTime.Now" will not translate to SQL.
                        // This inspects the caption for a simple XML string to see if there is a SQL compliant default value, e.g., "GETDATE()".
                        try
                        {
                            System.Xml.XmlDocument xml = new System.Xml.XmlDocument();
    
                            xml.LoadXml(table.Columns[i].Caption);
    
                            alterSql.AppendFormat("\nALTER TABLE {0} ADD CONSTRAINT [DF_{0}_{1}]  DEFAULT ({2}) FOR [{1}];", 
                                table.TableName, 
                                table.Columns[i].ColumnName, 
                                xml.GetElementsByTagName("defaultValue")[0].InnerText);
                        }
                        catch
                        {
                            // Handle
                        }
                    }
                }
            }
    
            if (!table.Columns[i].AllowDBNull)
            {
                sql.Append(" NOT NULL");
            }
    
            sql.Append(",");
        }
    
        if (table.PrimaryKey.Length > 0)
        {
            StringBuilder primaryKeySql = new StringBuilder();
    
            primaryKeySql.AppendFormat("\n\tCONSTRAINT PK_{0} PRIMARY KEY (", table.TableName);
    
            for (int i = 0; i < table.PrimaryKey.Length; i++)
            {
                primaryKeySql.AppendFormat("{0},", table.PrimaryKey[i].ColumnName);
            }
    
            primaryKeySql.Remove(primaryKeySql.Length - 1, 1);
            primaryKeySql.Append(")");
    
            sql.Append(primaryKeySql);
        }
        else
        {
            sql.Remove(sql.Length - 1, 1);
        }
    
        sql.AppendFormat("\n);\n{0}", alterSql.ToString());
    
        return sql.ToString();
    }
    

    这里有一个简单的测试来使用这个方法并获取SQL:

    DataTable table = new DataTable("Users");
    
    table.Columns.Add(new DataColumn()
    {
        ColumnName = "UserId",
        DataType = System.Type.GetType("System.Int32"),
        AutoIncrement = true,
        AllowDBNull = false,
        AutoIncrementSeed = 1,
        AutoIncrementStep = 1
    });
    
    table.Columns.Add(new DataColumn()
    {
        ColumnName = "UserName",
        DataType = System.Type.GetType("System.String"),
        AllowDBNull = true,
        DefaultValue = String.Empty,
        MaxLength = 50
    });
    
    table.Columns.Add(new DataColumn()
    {
        ColumnName = "LastUpdate",
        DataType = System.Type.GetType("System.DateTime"),
        AllowDBNull = false,
        DefaultValue = DateTime.Now, 
        Caption = "<defaultValue>GETDATE()</defaultValue>"
    });
    
    table.PrimaryKey = new DataColumn[] { table.Columns[0] };
    
    string sql = DataHelper.GetCreateTableSql(table);
    
    Console.WriteLine(sql);
    

    最后是输出:

    CREATE TABLE [Users] (
        [UserId] int IDENTITY(0,1) NOT NULL,
        [UserName] nvarchar(50),
        [LastUpdate] datetime NOT NULL,
        CONSTRAINT PK_Users PRIMARY KEY (UserId)
    );
    
    ALTER TABLE Users ADD CONSTRAINT [DF_Users_UserName]  DEFAULT ('') FOR [UserName];
    ALTER TABLE Users ADD CONSTRAINT [DF_Users_LastUpdate]  DEFAULT (GETDATE()) FOR[LastUpdate];
    

    我同意最初的回答,即数据管理不应随意进行。确实需要大量的思考才能使数据库保持平稳运行并在未来实现可维护性。但是,有时需要编码解决方案,我希望这可以帮助某人。

    【讨论】:

    • 对于任何看到这个(非常好的)解决方案的人来说只是几个改进:1. SYSTEM.DOUBLE 类型必须映射到 SQL Server 的 float 2. 添加 DEFAULT 时请检查是否为空(非空)值与if (isNumeric &amp;&amp; table.Columns[i].DefaultValue.ToString() != string.Empty)
    【解决方案4】:

    关于 Amin 的回答,我在他的代码中添加了主键。

    public static string CreateTABLEPablo(string connectionString, string tableName, System.Data.DataTable table)
    {
        string sqlsc;
        //using (System.Data.SqlClient.SqlConnection connection = new System.Data.SqlClient.SqlConnection(connectionString))
        using (System.Data.OleDb.OleDbConnection connection = new System.Data.OleDb.OleDbConnection(connectionString))
        {
            connection.Open();
            sqlsc = "CREATE TABLE " + tableName + "(";
            for (int i = 0; i < table.Columns.Count; i++)
            {
                sqlsc += "\n" + table.Columns[i].ColumnName;
                if (table.Columns[i].DataType.ToString().Contains("System.Int32"))
                    sqlsc += " int ";
                else if (table.Columns[i].DataType.ToString().Contains("System.DateTime"))
                    sqlsc += " datetime ";
                else if (table.Columns[i].DataType.ToString().Contains("System.String"))
                    sqlsc += " nvarchar(" + table.Columns[i].MaxLength.ToString() + ") ";
                else if (table.Columns[i].DataType.ToString().Contains("System.Single"))
                    sqlsc += " single ";
                else if (table.Columns[i].DataType.ToString().Contains("System.Double"))
                    sqlsc += " double ";
                else
                    sqlsc += " nvarchar(" + table.Columns[i].MaxLength.ToString() + ") ";
    
    
    
                if (table.Columns[i].AutoIncrement)
                    sqlsc += " IDENTITY(" + table.Columns[i].AutoIncrementSeed.ToString() + "," + table.Columns[i].AutoIncrementStep.ToString() + ") ";
                if (!table.Columns[i].AllowDBNull)
                    sqlsc += " NOT NULL ";
                sqlsc += ",";
            }
    
            string pks = "\nCONSTRAINT PK_" + tableName + " PRIMARY KEY (";
            for (int i = 0; i < table.PrimaryKey.Length; i++)
            {
                pks += table.PrimaryKey[i].ColumnName + ",";
            }
            pks = pks.Substring(0, pks.Length - 1) + ")";
    
            sqlsc += pks;
            connection.Close();
    
        }
        return sqlsc + ")";
    }
    

    【讨论】:

    • 如果表没有主键怎么办?您将添加一个带空括号 () 的约束。此外,您的约束总是在每个列名的末尾添加一个逗号,包括最后一个,这会破坏 SQL 脚本。
    【解决方案5】:

    这是我为工作而编写的一些代码。它已经过测试并在生产环境中用于脚本生成。 它正确处理DBNull 和主键,如果没有或只有一个,它不会失败。它也比这里的其他建议更高效,因为它使用StringBuilder,Linq 的Aggregate,并且不会重复调用ToString()

    注意:如果您的数据来自外部来源,请确保您的代码始终清理此方法的输入或检查此方法的输出,然后再针对您的数据库盲目执行生成的脚本。

        /// <summary>
        /// Creates a SQL script that creates a table where the columns matches that of the specified DataTable.
        /// </summary>
        public static string BuildCreateTableScript(DataTable Table)
        {
            if (!Helper.IsValidDatatable(Table, IgnoreZeroRows: true))
                return string.Empty;
    
            StringBuilder result = new StringBuilder();
            result.AppendFormat("CREATE TABLE [{1}] ({0}   ", Environment.NewLine, Table.TableName);
    
            bool FirstTime = true;
            foreach (DataColumn column in Table.Columns.OfType<DataColumn>())
            {
                if (FirstTime) FirstTime = false;
                else
                    result.Append("   ,");
    
                result.AppendFormat("[{0}] {1} {2} {3}",
                    column.ColumnName, // 0
                    GetSQLTypeAsString(column.DataType), // 1
                    column.AllowDBNull ? "NULL" : "NOT NULL", // 2
                    Environment.NewLine // 3
                );
            }
            result.AppendFormat(") ON [PRIMARY]{0}GO{0}{0}", Environment.NewLine);
    
            // Build an ALTER TABLE script that adds keys to a table that already exists.
            if (Table.PrimaryKey.Length > 0)
                result.Append(BuildKeysScript(Table));
    
            return result.ToString();
        }
    
        /// <summary>
        /// Builds an ALTER TABLE script that adds a primary or composite key to a table that already exists.
        /// </summary>
        private static string BuildKeysScript(DataTable Table)
        {
            // Already checked by public method CreateTable. Un-comment if making the method public
            // if (Helper.IsValidDatatable(Table, IgnoreZeroRows: true)) return string.Empty;
            if (Table.PrimaryKey.Length < 1) return string.Empty;
    
            StringBuilder result = new StringBuilder();
    
            if (Table.PrimaryKey.Length == 1)
                result.AppendFormat("ALTER TABLE {1}{0}   ADD PRIMARY KEY ({2}){0}GO{0}{0}", Environment.NewLine, Table.TableName, Table.PrimaryKey[0].ColumnName);
            else
            {
                List<string> compositeKeys = Table.PrimaryKey.OfType<DataColumn>().Select(dc => dc.ColumnName).ToList();
                string keyName = compositeKeys.Aggregate((a,b) => a + b);
                string keys = compositeKeys.Aggregate((a, b) => string.Format("{0}, {1}", a, b));
                result.AppendFormat("ALTER TABLE {1}{0}ADD CONSTRAINT pk_{3} PRIMARY KEY ({2}){0}GO{0}{0}", Environment.NewLine, Table.TableName, keys, keyName);
            }
    
            return result.ToString();
        }
    
        /// <summary>
        /// Returns the SQL data type equivalent, as a string for use in SQL script generation methods.
        /// </summary>
        private static string GetSQLTypeAsString(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)";
            }
        }
    

    生成的输出示例:

    CREATE TABLE [Order] (
       [OrderID] [bigint] UNSIGNED NOT NULL 
       ,[Description] [nvarchar](250) NULL 
       ,[Flag] [bit] NULL 
       ,[Quantity] [int] NULL 
       ,[Price] [decimal] NULL 
       ,[Customer] [nvarchar](MAX) NOT NULL 
    ) ON [PRIMARY]
    GO
    
    ALTER TABLE Order
       ADD CONSTRAINT pk_OrderIDCustomer PRIMARY KEY (OrderID, Customer)
    GO
    

    除了Helper.IsValidDatatable() 之外的所有内容都包含在内,但您明白了。这至少应该被一个空检查代替,并且可能检查零数据列。 事实上,如果你好奇的话,这段代码来自我的一个更大(但仍然不到 1000 行)的开源 C# 类库,它有助于将数据从 C# 类对象移动到 DataTable,然后再到 SQL 脚本并再次返回。它还包含几个帮助程序数据访问方法,用于更简洁的代码。我称它为 EntityJustworks,它也是 IsValidDatatable() 方法的主体所在(在 Helper.cs 类文件中)。您可以通过 CodePlex (https://entityjustworks.codeplex.com) 访问代码,也可以通过访问其博客文章 (https://csharpcodewhisperer.blogspot.com/2015/01/entity-justworks-class-to-sql.html) 查看 EntityJustworks 可以获取的所有其他位置(GitHub、Code.MSDN、Pastebin 等)的完整列表。

    【讨论】:

      【解决方案6】:

      您需要多高的效率?我可能会编写自己的 TSQL(基于DataTable 的列)来创建表 + 列,但是要填充它,您可以选择;如果行数适中,SqlDataAdapter 应该没问题。如果您有 很多 数据,那么 SqlBulkCopy 接受 DataTable 和表名...

      【讨论】:

      • 拥有 TSQL(基于 DataTable 的列)?
      【解决方案7】:

      我会根据 DataTable 构建一个 Create Table 语句并将其发送到数据库。您也可以使用 SMO(SQL Server 管理对象)。不知道什么是最快的。

      这绝对是可以进入框架级类以供重用的东西。

      以下链接包含有关如何执行此操作的信息(和SqlTableCreator 的代码示例):Creating a new table in SQL Server from ADO.NET DataTable。你可以找到SqlTableCreatorhereherehere的分叉。

      希望对您有所帮助。

      【讨论】:

        【解决方案8】:

        如果您指的是任意 ADO.Net DataTable,我认为您必须将其编码为 DDL“代码生成”工具,在构建“创建表...”时迭代 DataTables 的列集合DDL 语句。

        然后连接到需要的数据库,执行构造好的Create Table DDL语句。

        【讨论】:

          【解决方案9】:

          这是我为在 progres sql 中工作而编写的一些代码。

          int count = dataTable1.Columns.Count - 1;
          for (int i = 0; i < dataTable1.Columns.Count; i++)
          {
             if (i == count)
             {
                 name += dataTable1.Columns[i].Caption + " VARCHAR(50)";
             }
             else
             {
                 name += dataTable1.Columns[i].Caption + " VARCHAR(50)" + ", ";
             }
          }
          
          // Your SQL Command to create a table
          string createString = "CREATE TABLE " + tableName + " (" + name + ")";                     
          //SqlCommand create = new SqlCommand(createString, connection);
          NpgsqlCommand create = new NpgsqlCommand(createString, connection);
          connection.Open();
          create.ExecuteNonQuery();
          

          【讨论】:

            猜你喜欢
            • 2012-05-13
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2010-12-28
            • 2022-09-25
            • 1970-01-01
            相关资源
            最近更新 更多