I'm trying to perform a simple task, however, all the examples I'm finding do not work.
I have a very simple case:
I have a dataTable that belongs to a dataSet. It is populated from some text file that I parsed (not CSV).
I just want to save that dataTable into SQLite DB, where I have an empty table with the same schema (same number of columns and format of columns).
I have this method:
public static void UpdateTable(string dbpath, DataSet dataSet, string tableName)
{
using (SQLiteConnection db = new SQLiteConnection($"URI=file:{dbpath}"))
{
db.Open();
dataSet.AcceptChanges();
SQLiteDataAdapter DataAdapter = new SQLiteDataAdapter("select * from " + tableName, db);
DataAdapter.AcceptChangesDuringUpdate = true;
SQLiteCommandBuilder commandBuilder = new SQLiteCommandBuilder(DataAdapter);
DataAdapter.UpdateCommand = commandBuilder.GetUpdateCommand();
DataAdapter.Update(dataSet, tableName);
}
}
This is the last way I tried. All I'm getting is the same empty table in SQLite DB.
All I want to do is just save a table into a DB. I guess I can do that row by row, but I know there is a way to do that in one command.
Could you please help me write a functioning method?
Related
I have data coming from an Excel spreadsheet, I cannot change how this comes in. Is there a solution for adding IList<IList<Object>> values to a SQL Server database instead of looping as I am reaching limit with currently 5k rows.
I was also informed that I shouldn't use injection, so any alternatives are welcomed.
public static void Load_Table()
{
// This function on_click populates the datagrid named JumpTable
// this.JumpTable.ItemsSource = null; // Clears the current datagrid before getting new data
// Pulls in the 2d table from the client sheet
IList<IList<Object>> client_sheet = Get(SetCredentials(), "$placeholder", "Client!A2:AY");
DBFunctions db = new DBFunctions();
db.postDBTable("DELETE FROM Client");
foreach (var row in client_sheet)
{
string exe = "INSERT INTO Client ([Tracker ID],[Request ID]) VALUES('" + row[0].ToString() + "','" + row[1].ToString() + "')";
db.postDBTable(exe);
}
}
Database functions
public SqlConnection getDBConnection()
{
// --------------< Function: Opens the connection to our database >-------------- \\
string connectionString = Properties.Settings.Default.connection_string; // Gets the connection source from properties
SqlConnection dbConnection = new SqlConnection(connectionString); // Creates the connection based off our source
if (dbConnection.State != ConnectionState.Open) dbConnection.Open(); // If it's not already open then open the connection
return dbConnection;
}
public DataTable getDBTable(string sqlText)
{
// --------------< Function: Gets the table from our database >-------------- \\
SqlConnection dbConnection = getDBConnection();
DataTable table = new DataTable();
SqlDataAdapter adapter = new SqlDataAdapter(sqlText, dbConnection);adapter.Fill(table);
return table;
}
public void postDBTable(string sqlText)
{
// --------------< Function: Post data to our database >-------------- \\
SqlConnection dbConnection = getDBConnection();
SqlCommand cmd = new SqlCommand(sqlText, dbConnection);cmd.ExecuteNonQuery();
}
I have worked with lots of bulk data loads in the past. There are two main ways to avoid individual inserts with SQL Server, a list of values inserted at one time or using bulk insert.
the first option to use a list of values is like this:
INSERT INTO Foo (Bar,Baz)
VALUES ('bar','baz'),
('anotherbar','anotherbaz')
In c# you would loop through your list and build the values content, however doing this with out a sql injection vulnerability is difficult.
The second option is to use bulk insert with SQL Bulk copy and a datatable. Before getting to the code below you would build a DataTable that holds all your data then use SqlBulkCopy to insert rows using Sql functionality that is optimized for inserting large amounts of data.
using (var bulk = new SqlBulkCopy(con)
{
//bulk mapping is a SqlBulkCopyColumnMapping[] and is not necessaraly needed if the DataTable matches your destination table exactly
foreach (var i in bulkMapping)
{
bulk.ColumnMappings.Add(i);
}
bulk.DestinationTableName = "MyTable";
bulk.BulkCopyTimeout = 600;
bulk.BatchSize = 5000;
bulk.WriteToServer(someDataTable);
}
These are the two framework included methods. There are other libraries that can help. Dapper is one but I am not sure how it handles inserts on the back end. Entity framework is another but it does single inserts so it is just moving the problem from your code to some one else's.
What I did:
I am using OleDbAdapter to read from the database, getting a fresh DataTable filled. This went good. Then I want to add a column into that DataTable, which also went good.
I added a OleDbCommandBuilder, to update the database with the DataTable having one more column. And I tried it with the 'automatical way' of the OleDbCommandBuilder, as I thought what I want is simple. But so far this did not work.
What I expect
is that the OleDbCommandBuilder is writing a fresh SQL command for me, having 'UPDATE' or 'INSERT' contained. I further expect, that I can't read all Commands within the OleDbAdapter, except the SELECT command, because OleDbAdapter takes the commands from the builder right before using them.
I have read in the internet, that adapter.Fill(...) is not necessary if I let call adapter.Update(...). But without adapter.Fill(...) I don't get content from the database.
Finally a problem has got a name:
Now, after searching for the problem, I got the following message: System.Data.OleDbException: For at least one parameter no value has been given.
My questions:
1) Do I expect something wrong?
2) Which parameter hasn't got a value? Solved This helped me to understand:
https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.parameters?redirectedfrom=MSDN&view=netframework-4.7.2#System_Data_SqlClient_SqlCommand_Parameters
3) Are the adapter, builder ... placed in the right order?
4) Have I got something additional to do, like calling a function to update the SQL command withing the adapter?
5) How can I improve the way I solve that problem? E.g.: Is there any event which will help me to understand more what is going on? How to catch such an event?
Many thanks in advance!
This is the my code - originally it is divided into two functions. But I put it all in one for you:
public virtual bool AddColumnOfString_ToDataTable(string tableName, string newColumnName, string defaultCellValue)
{
/// Approach: Accessing database at minimum time.
/// returns true if column name could not be found and column could be added
DataTable table = new DataTable();
string strSQL = "SELECT " + tableName;
OleDbDataAdapter adapter = new OleDbDataAdapter(strSQL, strConnection);
adapter.Fill(table);
OleDbCommandBuilder builder = new OleDbCommandBuilder(adapter);
bool result = false;
if (false == HasColumn(newColumnName))
{
DataColumn newColumn = new DataColumn(newColumnName, typeof(System.String));
newColumn.DefaultValue = defaultCellValue;
table.Columns.Add(newColumn);
result = true;
}
adapter.Update(table);
return result;
}
You modified the structure of the DataTable by adding newcolumn to the datatable and this is not reflected in the generated update/insert/delete sql commands.
Have a look to this example: OleDbCommandBuilder Class
so simply:
adapter.Update(table);
Only update the data in the base table in the server (if changed)
1) Do I expect something wrong?
No, it's working but no change in the structure of base table in MS access
2) Which parameter hasn't got a value?
you don't pass parameters in the SQL command
3) Are the adapter, builder ... placed in the right order?
yes, but remove the part that modify the datatable. It has no effect
4) Have I got something additional to do, like calling a function to update the SQL command withing the adapter?
rview my code with the comments.
5) How can I improve the way I solve that problem? E.g.: Is there any event which will help me to understand more what is going on? How to catch such an event?
You can't modify the structure of the datatable by adding new columns
Update
I test your code , modified it with comments:
public bool AddColumnOfString_ToDataTable(string tableName, string newColumnName, string defaultCellValue)
{
// Approach: Accessing database at minimum time.
// returns true if column name could not be found and column could be added
DataTable table = new DataTable();
//string strSQL = "SELECT " + tableName; // not valid syntax
string strSQL = "SELECT * from " + tableName;
OleDbDataAdapter adapter = new OleDbDataAdapter(strSQL, myConnectionString);
adapter.Fill(table);
OleDbCommandBuilder builder = new OleDbCommandBuilder(adapter);
bool result = false;
// remove this code, it has no effect on the underlying base table in MS Access databas
//any change in the structure of datatable has no effect on the database
/*
if (false == table.HasColumn(newColumnName))
{
DataColumn newColumn = new DataColumn(newColumnName, typeof(System.String));
newColumn.DefaultValue = defaultCellValue;
table.Columns.Add(newColumn);
result = true;
}
*/
// code to modify data in DataTable here
//Without the OleDbCommandBuilder this line would fail
adapter.Update(table);
//just to review the generated code
Console.WriteLine(builder.GetUpdateCommand().CommandText);
Console.WriteLine(builder.GetInsertCommand().CommandText);
return result;
}
Update2:
If you are interested for adding new column to MS Access Database, you can run the following code:
public bool AddColumn(OleDbConnection con,
string tableName,string colName,string colType, object defaultValue)
{
string query = $"ALTER TABLE {tableName} ADD COLUMN {colName} {colType} DEFAULT {defaultValue} ";
var cmd = new OleDbCommand(query, con);
try
{
con.Open();
cmd.ExecuteNonQuery();
Console.WriteLine("Sql Executed Successfully");
return true;
}
catch (OleDbException e)
{
Console.WriteLine("Error Details: " + e);
}
finally
{
Console.WriteLine("closing conn");
con.Close();
}
return false;
}
public void AddColumnTest()
{
OleDbConnection con = new OleDbConnection(myConnectionString);
string tableName="table1";
string colName="country";
string colType="text (30)";
object defaultValue = "USA";
AddColumn(con, tableName, colName, colType, defaultValue);
}
I test the code with MS Access and it's working fine.
Below is a snippet of the code. As you can see, that method returns a table from SQLite database, and adds that table to a DataSet if it doesn't exist yet.
SQLiteConnection connection;
DataSet Set = new DataSet();
DataTable GetTable(string tableName, string command)
{
if (!Set.Tables.Contains(tableName))
{
var adapter = new SQLiteDataAdapter(command, connection);
SQLiteCommandBuilder builder = new SQLiteCommandBuilder(adapter);
adapter.FillSchema(Set, SchemaType.Source, tableName);
adapter.Fill(Set, tableName);
adapter.Dispose();
}
return Set.Tables[tableName];
}
To call it, for example
DataTable myTable = GetTable("MyTable", "select * from MyTable);
To access a field:
object emptyValue = myTable.Rows[0]["Some_Column"];
There are some cells in the SQLite file that are of type INT, and their values are empty (not null). However when I'm trying to populate myTable, they are conveniently converted to 0's which I DO NOT WANT. How do I go about fixing that? I would like to keep empty values (and null values) as null's when importing to C#.
You can retrieve the row I was talking about above by executing the following SQL statement:
select * from MyTable where some_column = ''
The SQLite file that I use is SQLite3. Just in case it helps.
Thanks in advance!
I'm filling a Data Grid in C# (WinForms) via a System.Data.DataTable. DataTable is filled from a DB table via ODP.
I have a data navigator in Data Grid for updating, deleting and inserting rows.
I want to use DataTable to commit all changes made in Data Grid to the database.
I have to use OracleDataAdapter but I couldn't figure out how to achieve this.
What kind of a CommandText should I use to achieve all three commands (update, delete, insert)?
The code below didn't work (maybe because CommandText I inserted is not appropriate)
public void ExecuteNonQuery(string commandText, OracleCommand oracleCommand, CommandType commandType, DataTable dataTable)
{
oracleCommand.CommandText = commandText;
oracleCommand.CommandType = commandType;
try
{
oracleCommand.Connection = m_Connection;
OracleDataAdapter oracleDataAdapter = new OracleDataAdapter(oracleCommand);
oracleDataAdapter.Update(dataTable);
}
catch (Exception)
{
LoggerTrace.Instance.Write(TraceEventType.Error, LoggerTrace.LoggerTraceSource.DatabaseManagerError, "Query could not be executed!");
throw;
}
}
for insert create a new row in data table and insert into data table ,for update update the value and finally make save change of data set or data table
OracleCommandBuilder producess the appropriate insert, update and delete queries after the select query is inserted.
string selectCommand = "select * from Table";
oracleDataAdapter.SelectCommand = new OracleCommand(selectCommand, m_Connection);
OracleCommandBuilder cmdBuilder = new OracleCommandBuilder(oracleDataAdapter);
DataTable dataTable = new DataTable();
oracleDataAdapter.Fill(dataTable);
After OracleCommandBuilder build the command this way you can execute any updates in the DataTable like this:
oracleDataAdapter.Update(dataTable);
Sequences, virtual columns etc. are not allowed.
I have a four column table in a SQL Server database. The info for the first three columns is supplied by another source. Column 4 is set to null by default.
I then have a win form with a datatable that populates with the information from the SQL Server database using the following code:
public DataTable populateFormList()
{
SqlConnection con = new SqlConnection(Properties.Settings.Default.sqlConnectionString);
SqlCommand cmd = new SqlCommand("SELECT * FROM of_formlist_raw", con);
con.Open();
SqlDataReader reader = cmd.ExecuteReader();
DataTable dt = new DataTable();
dt.Load(reader);
return dt;
}
datagridview2.DataSource = populateFormList();
datagridview2.Refresh();
Now that works fine in obtaining my data.
The user can then make changes to the null values in column 4.
How can I easily write these changes from the datatable back into the SQL Server table?
In other words, once the on screen datatable has additional values, how can I then store the updated information back in the SQL Server database from which it was originally obtained from?
Thanks.
Try something like this and just pass (DataTable)datagridview2.DataSource as the data table:
private static void BulkInsertToSQL(DataTable dt, string tableName)
{
using (SqlConnection con = new SqlConnection(_DB))
{
SqlBulkCopy sbc = new SqlBulkCopy(con);
sbc.DestinationTableName = tableName;
//if your DB col names don’t match your data table column names 100%
//then relate the source data table column names with the destination DB cols
sbc.ColumnMappings.Add("DBAttributeName1", "DTColumnName1");
sbc.ColumnMappings.Add("DBAttributeName2", "DTColumnName2");
sbc.ColumnMappings.Add("DBAttributeName3", "DTColumnName3");
sbc.ColumnMappings.Add("DBAttributeName4", "DTColumnName4");
con.Open();
sbc.WriteToServer(dt);
con.Close();
}
}
2 options, with or without TableAdapter.
I would recommend to read this in MSDN for TableAdapter
They're using BindingSources too, which are excellent components, easy-to-use.
Without TableAdapter, read this, the "Update Records Using Command Objects" part.