【发布时间】:2010-11-23 20:07:19
【问题描述】:
我有一个使用 C# 手动创建并加载数据的 DataTable。
在 SQL Server 2005 中使用 DataTable 中的列和数据创建表的最有效方法是什么?
【问题讨论】:
标签: c# sql-server ado.net datatable
我有一个使用 C# 手动创建并加载数据的 DataTable。
在 SQL Server 2005 中使用 DataTable 中的列和数据创建表的最有效方法是什么?
【问题讨论】:
标签: c# sql-server ado.net datatable
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)";
}
【讨论】:
在 SQL 中从客户端提供的 Datatable 对象定义创建表有点不寻常。表是 SQL 中精心设计的实体,在部署时考虑选择合适的磁盘,在设计时考虑索引以及正确建模数据库所涉及的所有问题。
您最好解释一下您想要达到的目标,以便我们了解应该提供什么建议。
附带说明,在 SQL 2008 中,有一种非常简单的方法可以从客户端定义的 Datatable 中创建表:将 DataTable 作为 Table 值参数传递,然后发出 SELECT * INTO <tablename> FROM @tvp,这将有效地传递定义将Datatable 和其内容数据转换成SQL中的真实表。
【讨论】:
我知道这个问题已经很老了,但我只是有一些非常相似的东西需要写。我采用了我所做的并更改了 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];
我同意最初的回答,即数据管理不应随意进行。确实需要大量的思考才能使数据库保持平稳运行并在未来实现可维护性。但是,有时需要编码解决方案,我希望这可以帮助某人。
【讨论】:
SYSTEM.DOUBLE 类型必须映射到 SQL Server 的 float 2. 添加 DEFAULT 时请检查是否为空(非空)值与if (isNumeric && table.Columns[i].DefaultValue.ToString() != string.Empty)
关于 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,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 等)的完整列表。
【讨论】:
您需要多高的效率?我可能会编写自己的 TSQL(基于DataTable 的列)来创建表 + 列,但是要填充它,您可以选择;如果行数适中,SqlDataAdapter 应该没问题。如果您有 很多 数据,那么 SqlBulkCopy 接受 DataTable 和表名...
【讨论】:
我会根据 DataTable 构建一个 Create Table 语句并将其发送到数据库。您也可以使用 SMO(SQL Server 管理对象)。不知道什么是最快的。
这绝对是可以进入框架级类以供重用的东西。
以下链接包含有关如何执行此操作的信息(和SqlTableCreator 的代码示例):Creating a new table in SQL Server from ADO.NET DataTable。你可以找到SqlTableCreatorhere、here和here的分叉。
希望对您有所帮助。
【讨论】:
如果您指的是任意 ADO.Net DataTable,我认为您必须将其编码为 DDL“代码生成”工具,在构建“创建表...”时迭代 DataTables 的列集合DDL 语句。
然后连接到需要的数据库,执行构造好的Create Table DDL语句。
【讨论】:
这是我为在 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();
【讨论】: