SQLiteDataAdapter converts empty value to 0 - how to prevent that? - c#

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!

Related

How to save a DataTable to SQLite DB?

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?

OleDbCommandBuilder Update - at least one parameter values are missing

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.

SQL Server - Update table and return the Updated rows

I have a SQL Server database which has a lot of information inside.
I want to select top 50 rows in a single query (which I did, with no problem) but then I want to update a column from false to true, so next time I select I wont select the same, my code looks like this:
string Command = "UPDATE HubCommands SET [Alreadytaken] = 'true' FROM (SELECT TOP 50 [CommandId],[DeviceId],[Commandtext], [HashCommand],[UserId] FROM HubCommands) I WHERE [HubId] = '18353fe9-82fd-4ac2-a078-51c199d9072b'";
using (SqlConnection myConnection = new SqlConnection(SqlConnection))
{
using (SqlDataAdapter myDataAdapter = new SqlDataAdapter(Command, myConnection))
{
DataTable dtResult = new DataTable();
myDataAdapter.Fill(dtResult);
foreach (DataRow row in dtResult.Rows)
{
Guid CommandId, DeviceId, UserId;
Guid.TryParse(row["CommandId"].ToString(), out CommandId);
Guid.TryParse(row["DeviceId"].ToString(), out DeviceId);
Guid.TryParse(row["UserId"].ToString(), out UserId);
Console.WriteLine("CommandId" + CommandId);
}
}
}
This code does work, and it updates what I ask it to update, but I don't get nothing in the data table, its like it is always updating but not selecting.
If I do a normal select it does work and give information.
Does anyone have any idea how to update and get some data back, in a single query?
So your question is:
How can I update a table in SQL Server using C# and return the truly updated
rows as a DataTable ?
First You have multiple issues in your query.
You should use 1 and 0, not true or false. SQL-Server has a bit datatype and not a Boolean.
Second, this is how you should've constructed your query:
DECLARE #IDs TABLE
(
[CommandId] uniqueidentifier
);
INSERT INTO #IDs
SELECT [CommandId] FROM HubCommands
WHERE [HubId] = '18353fe9-82fd-4ac2-a078-51c199d9072b' AND [Alreadytaken] = 0;
UPDATE HubCommands
SET [Alreadytaken] = 1
WHERE CommandId IN
(
SELECT [CommandId] FROM #IDs
);
SELECT * FROM HubCommands
WHERE CommandId IN
(
SELECT [CommandId] FROM #IDs
);
Wrap all the above in a single string and use SqlDataReader. No need for an Adapter in you case (Since we're mixing commands unlike what the adapter usually does):
var sqlCommand = new SqlCommand(Command, myConnection);
SqlDataReader dataReader = sqlCommand.ExecuteReader();
DataTable dtResult = new DataTable();
dtResult.Load(dataReader);
I highly advise you to create a stored procedure accepting HubId as a parameter that does all the above work. It is neater and better for maintenance.

Passing DataSet to Stored Procedure

I have the following Stored Procedure that receives a DataSet as parameter and Inserts into table Excel.
CREATE PROCEDURE spInsertInvoice
#tblInvoice InvoiceType READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO Excel
SELECT Template, Cust_Name, Invoice_No,InvoiceDate FROM #tblInvoice
END
In my code file I am trying to read the Excel Sheet and filling the dataset. But problem is I am a bit confused as to how should I send the DataSet as Parameter to the stored Procedure.
This is what I have tried so far, but it doesn't seem to work
if (FileUpload1.HasFile)
{
string path = string.Concat((Server.MapPath("~/temp/" + FileUpload1.FileName)));
FileUpload1.PostedFile.SaveAs(path);
OleDbConnection oleCon = new OleDbConnection("Provider=Microsoft.Ace.OLEDB.12.0;Data Source=" + path + ";Extended Properties = Excel 12.0;");
OleDbCommand Olecmd = new OleDbCommand("select * from [Sheet1$]", oleCon);
OleDbDataAdapter dtap = new OleDbDataAdapter(Olecmd);
DataSet ds = new DataSet();
dtap.Fill(ds);
GridView1.DataSource = ds;
GridView1.DataBind();
if (ds.Tables[0].Rows.Count > 0)
{
string consString = ConfigurationManager.ConnectionStrings["myConnectionString"].ConnectionString;
using (SqlConnection con = new SqlConnection(consString))
{
using (SqlCommand cmd = new SqlCommand("spInsertInvoice"))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = con;
cmd.Parameters.AddWithValue("#tblInvoice", ds);
con.Open();
cmd.ExecuteNonQuery();
con.Close();
}
}
}
When I execute it, it throws ArgumentException on cmd.ExecuteNonQuery()
No mapping exists from object type System.Data.DataSet to a known
managed provider native type.
You cannot pass dataset to stored procedure but you can pass datatable to stored procedure. Follow below algorithm to execute it:
1) Create Table type in sql server for the DataTable which you want to pass.
2) Declare input variable for given table type as readonly in stored procedure.
3) Pass that data table to procedure.
This only restricts your table type parameter sequence and datatable column sequence should be same.
You can refer this link Sending a DataTable to a Stored Procedure
Or Table-Valued Parameters
You can't parameterize your table name, basically.
Parameterized SQL is just for values - not table names, column names, or any other database objects. This is one place where you do probably want to build the SQL dynamically - but with a white-listed set of options or strong validation before you put this table name in your sql query.
It that line;
cmd.Parameters.AddWithValue("#tblInvoice", ds);
You try to pass your DataSet to your table name which does not make sense.

how to pass DataTable with less columns to stored procedure when user defined table type in SQL server has default values of all columns set to null

I have a stored proc which accepts user defined table type and default values for all the columns in the user defined data type is set to null.
Now i am passing a dataTable with less columns to stored procedure from c# code expecting that the values for remaining columns will be set to null.
But i am getting this error:
Trying to pass a table-valued parameter with 21 column(s) where the corresponding user-defined table type requires 77 column(s).
This is the code
SqlConnection conn = new SqlConnection("server=****; database=***;integrated security=SSPI");
DataSet dataset=new DataSet();
conn.Open();
SqlCommand cmd = new SqlCommand("Insert");
cmd.CommandType = CommandType.StoredProcedure;
cmd.Connection = conn;
SqlParameter para = new SqlParameter();
para.ParameterName = "#TableVar";
para.SqlDbType = SqlDbType.Structured;
//SqlParameter para=cmd.Parameters.AddWithValue("#TableVar",table);
para.Value = table;
cmd.Parameters.Add(para);
cmd.ExecuteNonQuery();
You can use a SqlDataAdapter to create a DataTable matching the table type schema:
DataTable table = new DataTable();
// Declare a variable of the desired table type to produce a result set with it's schema.
SqlDataAdapter adapter = new SqlDataAdapter("DECLARE #tableType dbo.UserDefindTableType
SELECT * FROM #tableType", ConnectionString);
// Sets the DataTable schema to match dbo.UserDefindTableType .
adapter.FillSchema(table, SchemaType.Source);
You can then create DataRows with the all the default column values and just set the columns you know about:
DataRow row = table.NewRow();
// Set know columns...
row["ColumnName"] = new object();
// or check column exists, is expected type etc first
if (table.Columns.Contains("ColumnName")
&& table.Columns["ColumnName"].DataType == typeof(string)) {
row["ColumnName"] = "String";
}
table.Rows.Add(row);
I'm having the same issue and the solution I'm working on is to run an extra SQL query to get the column definition and then fill up the DataTable with the missing columns, here is the SQL statement for a column definition on your table type:
select c.name, t.name as type, c.max_length as length from sys.table_types tt
inner join sys.columns c on c.object_id = tt.type_table_object_id
inner join sys.types t on t.system_type_id = c.system_type_id
where tt.name = #tabletypename
order by c.column_id
The C# code will be a bit more messy as you have to parse the return type (ie VARCHAR, INT, etc) into a SqlDbType enum if you want the solution to work for all table valued parameter defintions..
I would have thought stuff like this could have been better solved by Microsoft inside the SQL Server engine as its the next best way to import CSV files if you do not have write access to the local filesystem, but I want a single UDTT to cater for all CSV files not having to create a new table type every time I deal with a new file format. Anyways.. rant over.

Categories