从C#datatable创建SQL Server表

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

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

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

更好地解释你想要达到的目标,这样我们才能理解给出的建议。

作为旁注,在SQL 2008中有一种非常简单的方法可以从客户端定义的Datatable创建表:将DataTable作为Table值参数传递,然后发出SELECT * INTO FROM @tvp ,这将有效将Datatable及其内容数据的定义传输到SQL中的实际表中。

 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)"; } 

我知道这个问题相当陈旧,但我只是有一些我需要写的东西。 我接受了我所做的并改变了Amin和rasputino提供的示例,并创建了一个只输出SQL的示例。 我添加了一些function并避免连接,以帮助改进本身就是表现不佳的流程。

 ///  /// Inspects a DataTable and return a SQL string that can be used to CREATE a TABLE in SQL Server. ///  /// System.Data.DataTable object to be inspected for building the SQL CREATE TABLE statement. /// String of SQL 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, eg, "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, eg, "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 = "GETDATE()" }); 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]; 

我同意最初的答案,即声明数据管理不应该随意进行。 确实需要花费大量精力来保持数据库平稳运行并在将来实现可维护性。 但是,有时候编码解决方案是必要的,我希望这可以帮助某人。

关于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 + ")"; } 

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

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

  ///  /// Creates a SQL script that creates a table where the columns matches that of the specified DataTable. ///  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()) { 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(); } ///  /// Builds an ALTER TABLE script that adds a primary or composite key to a table that already exists. ///  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 compositeKeys = Table.PrimaryKey.OfType().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(); } ///  /// Returns the SQL data type equivalent, as a string for use in SQL script generation methods. ///  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()之外, Helper.IsValidDatatable()所有内容都包括在内,但您明白了。 这至少应该用空检查替换,并且可能检查零DataColumns。 事实上,如果你好奇,这段代码来自我的一个更大的(但仍然不到1000行)开源C#类库,它有助于将数据从C#类对象移动到DataTable,然后再移动到SQL脚本。 它还包含几个辅助数据访问方法,以获得更简洁的代码。 我称之为EntityJustworks,它也是方法体IsValidDatatable()的主体(在Helper.cs类文件中)。 您可以通过CodePlex( https://entityjustworks.codeplex.com )访问代码,或查看可通过访问其博客文章获取EntityJustworks的所有其他地方(GitHub,Code.MSDN,Pastebin等)的完整列表( http://www.csharpprogramming.tips/2015/01/entity-justworks-class-to-sql.html )。

我只是基于DataTable构建一个Create Table语句并将其发送到Database。 您也可以使用SMO(SQL Server Managment对象)。 不确定什么是最快的。

这肯定是可以进入框架级别类以供重用的东西。

以下链接包含有关如何执行此操作的信息(以及SqlTableCreator代码示例): 从ADO.NET DataTable在SQL Server中创建新表 。 你可以在这里 , 这里和这里找到SqlTableCreator分支。

希望有所帮助。

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

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

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