3

在我的代码中,用户可以上传一个 excel 文档,希望包含它的电话联系人列表。作为开发人员,我应该阅读该 excel 文件,将其转换为 dataTable 并将其插入数据库。问题是一些客户有大量的联系人,比如说 5000 和更多的联系人,当我试图将这么多的数据插入数据库时​​,它会崩溃并给我一个超时异常。避免这种异常的最佳方法是什么,他们的任何代码是否可以减少插入语句的时间,以便用户不要等待太久?

编码

public SqlConnection connection = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString);
public void Insert(string InsertQuery)
{
    SqlDataAdapter adp = new SqlDataAdapter();
    adp.InsertCommand = new SqlCommand(InsertQuery, connection);
    if (connection.State == System.Data.ConnectionState.Closed)
    {
        connection.Open();
    }
    adp.InsertCommand.ExecuteNonQuery();
    connection.Close();
}

protected void submit_Click(object sender, EventArgs e) 
{
    string UploadFolder = "Savedfiles/";
    if (Upload.HasFile) {
        string fileName = Upload.PostedFile.FileName;
        string path=Server.MapPath(UploadFolder+fileName);
        Upload.SaveAs(path);
        Msg.Text = "successfully uploaded";
        DataTable ValuesDt = new DataTable();
        ValuesDt = ConvertExcelFileToDataTable(path);
        Session["valuesdt"] = ValuesDt;
        Excel_grd.DataSource = ValuesDt;
        Excel_grd.DataBind();


    }
}

protected void SendToServer_Click(object sender, EventArgs e)
{
    DataTable Values = Session["valuesdt"] as DataTable ;
    if(Values.Rows.Count>0)
    {
        DataTable dv = Values.DefaultView.ToTable(true, "Mobile1", "Mobile2", "Tel", "Category");
        double Mobile1,Mobile2,Tel;string Category="";
        for (int i = 0; i < Values.Rows.Count; i++)
       {
            Mobile1 =Values.Rows[i]["Mobile1"].ToString()==""?0: double.Parse(Values.Rows[i]["Mobile1"].ToString());
            Mobile2 = Values.Rows[i]["Mobile2"].ToString() == "" ? 0 : double.Parse(Values.Rows[i]["Mobile2"].ToString());
            Tel = Values.Rows[i]["Tel"].ToString() == "" ? 0 : double.Parse(Values.Rows[i]["Tel"].ToString());

           Category = Values.Rows[i]["Category"].ToString();
           Insert("INSERT INTO client(Mobile1,Mobile2,Tel,Category) VALUES(" + Mobile1 + "," + Mobile2 + "," + Tel + ",'" + Category + "')");
           Msg.Text = "Submitied successfully to the server ";
       }



    }

}
4

4 回答 4

4

您可以尝试SqlBulkCopy将数据表插入数据库表

像这样的东西,

using (SqlBulkCopy bulkCopy = new SqlBulkCopy(sqlConnection, SqlBulkCopyOptions.KeepIdentity))
{
    bulkCopy.DestinationTableName = DestTableName;
    string[] DtColumnName = YourDataTableColumns;
    foreach (string dbcol in DbColumnName)//To map Column of Datatable to that of DataBase tabele
    {
        foreach (string dtcol in DtColumnName)
        {
            if (dbcol.ToLower() == dtcol.ToLower())
            {
                SqlBulkCopyColumnMapping mapID = new SqlBulkCopyColumnMapping(dtcol, dbcol);
                bulkCopy.ColumnMappings.Add(mapID);
                break;
            }
        }
    }
    bulkCopy.WriteToServer(YourDataTableName.CreateDataReader());
    bulkCopy.Close();
}

有关更多信息,请阅读http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlbulkcopy.aspx

于 2013-06-03T06:40:31.383 回答
1

您一次插入 1 行,对于这种数据量来说,这是非常昂贵的

在这些情况下,您应该使用批量插入,因此如果您需要回滚,到 DB 的往返行程将只有一次 - 都是同一个事务

于 2013-06-03T06:38:44.223 回答
0

您可以使用工作量更大的 SqlBulkCopy,也可以使用 SqlAdpater 的批量更新功能。而不是自己创建insert语句,然后构建sqladapter,然后手动执行,创建数据集,填充,创建一个sqldataadpater,设置批量插入的数量,然后执行一次adapter。

我可以重复代码,但这篇文章确切地展示了如何做到这一点:http: //msdn.microsoft.com/en-us/library/kbbwt18a%28v=vs.80%29.aspx

protected void SendToServer_Click(object sender, EventArgs e)
{
    DataTable Values = Session["valuesdt"] as DataTable ;
    if(Values.Rows.Count>0)
    {
        DataTable dv = Values.DefaultView.ToTable(true, "Mobile1", "Mobile2", "Tel", "Category");
        //Fix up default values
        for (int i = 0; i < Values.Rows.Count; i++)
       {
            Values.Rows[i]["Mobile1"] =Values.Rows[i]["Mobile1"].ToString()==""?0: double.Parse(Values.Rows[i]["Mobile1"].ToString());
            Values.Rows[i]["Mobile2"] = Values.Rows[i]["Mobile2"].ToString() == "" ? 0 : double.Parse(Values.Rows[i]["Mobile2"].ToString());
            Values.Rows[i]["Tel"] = Values.Rows[i]["Tel"].ToString() == "" ? 0 : double.Parse(Values.Rows[i]["Tel"].ToString());

           Values.Rows[i]["Category"] = Values.Rows[i]["Category"].ToString();
       }
       BatchUpdate(dv,1000);


    }

}
public static void BatchUpdate(DataTable dataTable,Int32 batchSize)
{
    // Assumes GetConnectionString() returns a valid connection string.
    string connectionString = GetConnectionString();

    // Connect to the database.
    using (SqlConnection connection = new SqlConnection(connectionString))
    {

        // Create a SqlDataAdapter.
        SqlDataAdapter adapter = new SqlDataAdapter();

        // Set the INSERT command and parameter.
        adapter.InsertCommand = new SqlCommand(
            "INSERT INTO client(Mobile1,Mobile2,Tel,Category) VALUES(@Mobile1,@Mobile2,@Tel,@Category);", connection);
        adapter.InsertCommand.Parameters.Add("@Mobile1", 
          SqlDbType.Float);
        adapter.InsertCommand.Parameters.Add("@Mobile2", 
          SqlDbType.Float);
        adapter.InsertCommand.Parameters.Add("@Tel", 
          SqlDbType.Float);
        adapter.InsertCommand.Parameters.Add("@Category", 
          SqlDbType.NVarchar, 50);
        adapter.InsertCommand.UpdatedRowSource = UpdateRowSource.None;

        // Set the batch size.
        adapter.UpdateBatchSize = batchSize;

        // Execute the update.
        adapter.Update(dataTable);
    }
}
于 2013-06-03T06:50:02.540 回答
0

我知道这是一篇超级旧的帖子,但您不需要使用现有答案中解释的批量操作来处理 5000 次插入。您的性能受到很大影响,因为您关闭并重新打开每行插入的连接。这是我过去使用的一些代码,它保持一个连接打开并根据需要执行尽可能多的命令以将所有数据推送到数据库:

public static class DataWorker
{
    public static Func<IEnumerable<T>, Task> GetStoredProcedureWorker<T>(Func<SqlConnection> connectionSource, string storedProcedureName, Func<T, IEnumerable<(string paramName, object paramValue)>> parameterizer)
    {

        if (connectionSource is null) throw new ArgumentNullException(nameof(connectionSource));

        SqlConnection openConnection()
        {
            var conn = connectionSource() ?? throw new ArgumentNullException(nameof(connectionSource), $"Connection from {nameof(connectionSource)} cannot be null");
            var connState = conn.State;

            if (connState != ConnectionState.Open)
            {
                conn.Open();
            }

            return conn;
        }

        async Task DoStoredProcedureWork(IEnumerable<T> workData)
        {
            using (var connection = openConnection())
            using (var command = connection.CreateCommand())
            {
                command.CommandType = CommandType.StoredProcedure;
                command.CommandText = storedProcedureName;

                command.Prepare();

                foreach (var thing in workData)
                {
                    command.Parameters.Clear();

                    foreach (var (paramName, paramValue) in parameterizer(thing))
                    {
                        command.Parameters.AddWithValue(paramName, paramValue ?? DBNull.Value);
                    }

                    await command.ExecuteNonQueryAsync().ConfigureAwait(false);
                }
            }
        }

        return DoStoredProcedureWork;
    }
}

这实际上来自一个项目,我正在收集电子邮件以获取限制列表,因此有关parameterizer参数可能是什么样子以及如何使用上述代码的相关示例:

        IEnumerable<(string,object)> RestrictionToParameter(EmailRestriction emailRestriction)
        {
            yield return ("@emailAddress", emailRestriction.Email);
            yield return ("@reason", emailRestriction.Reason);
            yield return ("@restrictionType", emailRestriction.RestrictionType);
            yield return ("@dateTime", emailRestriction.Date);
        }

        var worker = DataWorker.GetStoredProcedureWorker<EmailRestriction>(ConnectionFactory, @"[emaildata].[AddRestrictedEmail]", RestrictionToParameter);


        await worker(emailRestrictions).ConfigureAwait(false);
于 2018-08-03T22:27:06.667 回答