63

I have imported data from some Excel file and I have saved it into a datatable. Now I'd like to save this information in my SQL Server database.

I saw a lot of information on the web but I cannot understand it:

  1. Someone said insert line by line another suggested bulk update... etc: what it better?
  2. Should I use OLE or SQL Server objects (like dataAdapter or connection)?

My need is to read the employee weekly hours report, from his Excel file and save it to a database table where all the reports are saved (updating the db with new records every week).

The Excel file contains reports only for the current week.

Indian
  • 529
  • 1
  • 12
  • 25
Asaf Gilad
  • 793
  • 1
  • 10
  • 15

8 Answers8

84

Create a User-Defined TableType in your database:

CREATE TYPE [dbo].[MyTableType] AS TABLE(
    [Id] int NOT NULL,
    [Name] [nvarchar](128) NULL
)

and define a parameter in your Stored Procedure:

CREATE PROCEDURE [dbo].[InsertTable]
    @myTableType MyTableType readonly
AS
BEGIN
    insert into [dbo].Records select * from @myTableType 
END

and send your DataTable directly to sql server:

using (var command = new SqlCommand("InsertTable") {CommandType = CommandType.StoredProcedure})
{
    var dt = new DataTable(); //create your own data table
    command.Parameters.Add(new SqlParameter("@myTableType", dt));
    SqlHelper.Exec(command);
}

To edit the values inside stored-procedure, you can declare a local variable with the same type and insert input table into it:

DECLARE @modifiableTableType MyTableType 
INSERT INTO @modifiableTableType SELECT * FROM @myTableType

Then, you can edit @modifiableTableType:

UPDATE @modifiableTableType SET [Name] = 'new value'
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
Navid Farhadi
  • 3,397
  • 2
  • 28
  • 34
  • It is my own class that is a helper class with a connection, in `Exec` method executes a sql command, I use it for simplify my code. – Navid Farhadi Sep 06 '12 at 14:06
  • intresting, but, if I want to re-open that data-table or edit the values in data table, how I can make ? – Alexandre Nov 11 '13 at 13:46
  • To edit the values inside stored-procedure, you can declare a local variable with the same type and insert input table into it, i.e. `declare @modifiableTableType MyTableType` and then `insert into @modifiableTableType select * from @myTableType`. Then, you can edit `@modifiableTableType`. – Navid Farhadi Nov 14 '16 at 06:56
  • 1
    best answer! simple and neat. many thanks. Is there an update (seeing the date of this post)? – Chagbert Mar 12 '20 at 04:10
  • Can you please mention where to find the sqlhelper function, it seems this does not exist in Microsoft.ApplicationBlocks.Data class – Mohsen Sichani Mar 12 '20 at 20:48
33

If it's the first time for you to save your datatable

Do this (using bulk copy). Assure there are no PK/FK constraint

SqlBulkCopy bulkcopy = new SqlBulkCopy(myConnection);
//I assume you have created the table previously
//Someone else here already showed how  
bulkcopy.DestinationTableName = table.TableName;
try                             
{                                 
    bulkcopy.WriteToServer(table);                            
}     
    catch(Exception e)
{
    messagebox.show(e.message);
} 

Now since you already have a basic record. And you just want to check new record with the existing one. You can simply do this.

This will basically take existing table from database

DataTable Table = new DataTable();

SqlConnection Connection = new SqlConnection("ConnectionString");
//I assume you know better what is your connection string

SqlDataAdapter adapter = new SqlDataAdapter("Select * from " + TableName, Connection);

adapter.Fill(Table);

Then pass this table to this function

public DataTable CompareDataTables(DataTable first, DataTable second)
{
    first.TableName = "FirstTable";
    second.TableName = "SecondTable";

    DataTable table = new DataTable("Difference");

    try
    {
        using (DataSet ds = new DataSet())
        {
            ds.Tables.AddRange(new DataTable[] { first.Copy(), second.Copy() });

            DataColumn[] firstcolumns = new DataColumn[ds.Tables[0].Columns.Count];

            for (int i = 0; i < firstcolumns.Length; i++)
            {
                firstcolumns[i] = ds.Tables[0].Columns[i];
            }

            DataColumn[] secondcolumns = new DataColumn[ds.Table[1].Columns.Count];

            for (int i = 0; i < secondcolumns.Length; i++)
            {
                secondcolumns[i] = ds.Tables[1].Columns[i];
            }

            DataRelation r = new DataRelation(string.Empty, firstcolumns, secondcolumns, false);

            ds.Relations.Add(r);

            for (int i = 0; i < first.Columns.Count; i++)
            {
                table.Columns.Add(first.Columns[i].ColumnName, first.Columns[i].DataType);
            }

            table.BeginLoadData();

            foreach (DataRow parentrow in ds.Tables[0].Rows)
            {
                DataRow[] childrows = parentrow.GetChildRows(r);
                if (childrows == null || childrows.Length == 0)
                    table.LoadDataRow(parentrow.ItemArray, true);
            }

            table.EndLoadData();

        }
    }

    catch (Exception ex)
    {
        throw ex;
    }

    return table;
}

This will return a new DataTable with the changed rows updated. Please ensure you call the function correctly. The DataTable first is supposed to be the latest.

Then repeat the bulkcopy function all over again with this fresh datatable.

techturtle
  • 2,519
  • 5
  • 28
  • 54
rofans91
  • 2,970
  • 11
  • 45
  • 60
  • This is a good solution, but you could save yourself the trouble of having to define column data if you include them in the export to begin with (dt.WriteXml(exportpath, XmlWriteMode.WriteSchema);). – DigiOz Multimedia Jul 28 '17 at 21:20
11

I am giving a very simple code, which i used in my solution (I have the same problem statement as yours)

    SqlConnection con = connection string ;
//new SqlConnection("Data Source=.;uid=sa;pwd=sa123;database=Example1");
con.Open();
string sql = "Create Table abcd (";
foreach (DataColumn column in dt.Columns)
{
    sql += "[" + column.ColumnName + "] " + "nvarchar(50)" + ",";
}
sql = sql.TrimEnd(new char[] { ',' }) + ")";
SqlCommand cmd = new SqlCommand(sql, con);
SqlDataAdapter da = new SqlDataAdapter(cmd);
cmd.ExecuteNonQuery();
using (var adapter = new SqlDataAdapter("SELECT * FROM abcd", con)) 
using(var builder = new SqlCommandBuilder(adapter))
{
adapter.InsertCommand = builder.GetInsertCommand();
adapter.Update(dt);
// adapter.Update(ds.Tables[0]); (Incase u have a data-set)
}
con.Close();

I have given a predefined table-name as "abcd" (you must take care that a table by this name doesn't exist in your database). Please vote my answer if it works for you!!!! :)

Prahalad Gaggar
  • 11,389
  • 16
  • 53
  • 71
  • this solution is not working for me, I have a lot of data in "dt" variable and nothing happens when the Update function runs, the execution in Debug mode in VS passes through lines so quickly like its doing nothing, and no exceptions either. – user734028 Sep 10 '17 at 12:23
4

I would suggest you go for bulk insert as suggested in this article : Bulk Insertion of Data Using C# DataTable and SQL server OpenXML function

Pranay Rana
  • 175,020
  • 35
  • 237
  • 263
0

I found that it was better to add to the table row by row if your table has a primary key. Inserting the entire table at once creates a conflict on the auto increment.

Here's my stored Proc

CREATE PROCEDURE dbo.usp_InsertRowsIntoTable
@Year       int,
@TeamName   nvarchar(50),
AS
INSERT INTO [dbo.TeamOverview]
(Year,TeamName)
VALUES (@Year, @TeamName);
RETURN

I put this code in a loop for every row that I need to add to my table:

insertRowbyRowIntoTable(Convert.ToInt16(ddlChooseYear.SelectedValue), name);

And here is my Data Access Layer code:

        public void insertRowbyRowIntoTable(int ddlValue, string name)
    { 
        SqlConnection cnTemp = null;
        string spName = null;
        SqlCommand sqlCmdInsert = null;

        try
        {
            cnTemp = helper.GetConnection();
            using (SqlConnection connection = cnTemp)
            {
                if (cnTemp.State != ConnectionState.Open)
                    cnTemp.Open();
                using (sqlCmdInsert = new SqlCommand(spName, cnTemp))
                {
                    spName = "dbo.usp_InsertRowsIntoOverview";
                    sqlCmdInsert = new SqlCommand(spName, cnTemp);
                    sqlCmdInsert.CommandType = CommandType.StoredProcedure;

                    sqlCmdInsert.Parameters.AddWithValue("@Year", ddlValue);
                    sqlCmdInsert.Parameters.AddWithValue("@TeamName", name);

                    sqlCmdInsert.ExecuteNonQuery();

                }
            }
        }
        catch (Exception ex)
        {
            throw ex;
        }
        finally
        {
            if (sqlCmdInsert != null)
                sqlCmdInsert.Dispose();

            if (cnTemp.State == ConnectionState.Open)
                cnTemp.Close();
        }

    }
jade290
  • 413
  • 6
  • 5
  • 2
    Welcome to SO! Can you elaborate _why_ you think it is better to do this line by line? This is a fairly old question and it has an accepted answer already. New answers should be improvements compared to the existing. – cfi Sep 27 '13 at 22:19
  • When I tried to do it, I had an issue with the primary key. It would give me an error saying something like "...cannot insert primary key unless IDENTITY_INSERT is ON. This means that it cannot auto increment and entire table. I tried SCOPE_IDENTITY but it was useless. If you don't need a primary key inserting the entire table works fine. – jade290 Sep 30 '13 at 13:18
  • 1
    There is unnecessary overhead here. Each time you insert a single row into the database you're establishing a new connection, issuing the command and disposing of the connection. Doing this in a loop is inefficient. – Red Taz Jan 15 '14 at 10:24
  • @RedTaz, that's not true. When you use the "Using" statement you only open the connection once. This is not a loop. It's used to ensure that the "Dispose" command is always excuted. http://msdn.microsoft.com/en-us/library/yh598w02.aspx – jade290 Feb 03 '14 at 13:45
  • @jade290 The `using` statement is within the `insertRowbyRowIntoTable` method, therefore calling `insertRowbyRowIntoTable` inside a loop (as you suggested) will repeatedly open the connection and dispose it for each call. You would need to move the `using` statement outside of the loop in order to gain the benefit – Red Taz Feb 03 '14 at 14:03
  • 1
    I should also add a caveat to my initial comment which is that a new connection wouldn't be established every time when [SQL Server Connection Pooling](http://msdn.microsoft.com/en-us/library/8xx3tyca%28v=vs.110%29.aspx) is enabled (which it is by default) – Red Taz Feb 03 '14 at 14:22
  • @Red Taz I ran into the same error, but this is resolved by specifying the columns you want to insert and the values you are selecting from your table. For example.... CREATE PROCEDURE [dbo].[InsertTable] \@myTableType MyTableType readonly AS BEGIN insert into [dbo].Records select nonIDparam1, nonIDparam2 select nonIDparam1, nonIDparam2 from \@myTableType END – Tristan Descartes Jan 21 '15 at 00:25
0
    //best way to deal with this is sqlbulkcopy 
    //but if you dont like it you can do it like this
    //read current sql table in an adapter
    //add rows of datatable , I have mentioned a simple way of it
    //and finally updating changes

    Dim cnn As New SqlConnection("connection string")        
    cnn.Open()
    Dim cmd As New SqlCommand("select * from  sql_server_table", cnn)
    Dim da As New SqlDataAdapter(cmd)       
    Dim ds As New DataSet()
    da.Fill(ds, "sql_server_table")
    Dim cb As New SqlCommandBuilder(da)        

    //for each datatable row
    ds.Tables("sql_server_table").Rows.Add(COl1, COl2)

    da.Update(ds, "sql_server_table")
masoud Cheragee
  • 350
  • 3
  • 9
0
public bool BulkCopy(ExcelToSqlBo objExcelToSqlBo, DataTable dt, SqlConnection conn, SqlTransaction tx)
{
    int check = 0;
    bool result = false;
    string getInsert = "";
    try
    {
        if (dt.Rows.Count > 0)
        {
            foreach (DataRow dr in dt.Rows)
            {
                if (dr != null)
                {
                    if (check == 0)
                    {
                        getInsert = "INSERT INTO [tblTemp]([firstName],[lastName],[Father],[Mother],[Category]" +
                                ",[sub_1],[sub_LG2])"+
                                " select '" + dr[0].ToString() + "','" + dr[1].ToString() + "','" + dr[2].ToString() + "','" + dr[3].ToString() + "','" + dr[4].ToString().Trim() + "','" + dr[5].ToString().Trim() + "','" + dr[6].ToString();

                        check += 1;
                    }
                    else
                    {
                        getInsert += " UNION ALL ";

                        getInsert += " select  '" + dr[0].ToString() + "','" + dr[1].ToString() + "','" + dr[2].ToString() + "','" + dr[3].ToString() + "','" + dr[4].ToString().Trim() + "','" + dr[5].ToString().Trim() + "','" + dr[6].ToString() ;

                        check++;
                    }
                }
            }
            result = common.ExecuteNonQuery(getInsert, DatabasesName, conn, tx);
        }
        else
        {
            throw new Exception("No row for insertion");
        }
        dt.Dispose();
    }
    catch (Exception ex)
    {
        dt.Dispose();
        throw new Exception("Please attach file in Proper format.");
    }
    return result;
} 
Scott Smith
  • 1,823
  • 17
  • 15
sachin
  • 9
  • 1
  • 8
    This isn't a great approach. What if one of the DataRows contained a single quote, you'd break your SQL connection and also introduce the possibility of SQL injection attacks. – Alex Aug 07 '12 at 15:10
-1

From my understanding of the question,this can use a fairly straight forward solution.Anyway below is the method i propose ,this method takes in a data table and then using SQL statements to insert into a table in the database.Please mind that my solution is using MySQLConnection and MySqlCommand replace it with SqlConnection and SqlCommand.

public void InsertTableIntoDB_CreditLimitSimple(System.Data.DataTable tblFormat)
    {
        for (int i = 0; i < tblFormat.Rows.Count; i++)
        {

            String InsertQuery = string.Empty;

            InsertQuery = "INSERT INTO customercredit " +
                          "(ACCOUNT_CODE,NAME,CURRENCY,CREDIT_LIMIT) " +
                          "VALUES ('" + tblFormat.Rows[i]["AccountCode"].ToString() + "','" + tblFormat.Rows[i]["Name"].ToString() + "','" + tblFormat.Rows[i]["Currency"].ToString() + "','" + tblFormat.Rows[i]["CreditLimit"].ToString() + "')";



            using (MySqlConnection destinationConnection = new MySqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["ConnectionString"].ToString()))
            using (var dbcm = new MySqlCommand(InsertQuery, destinationConnection))
            {
                destinationConnection.Open();
                dbcm.ExecuteNonQuery();
            }
        }
    }//CreditLimit
C.Poh
  • 442
  • 4
  • 9