Use DataTable.Load() without creating all column properties - c#

I'm loading data from a SQL server database like this:
SqlCommand cmd = new SqlCommand("select * from x", conn);
cmd.CommandType = CommandType.Text;
DataTable dt = new DataTable();
SqlDataReader rd = cmd.ExecuteReader();
dt.Load(rd, LoadOption.PreserveChanges);
Issue:
The Load() function initializes the table columns with ColumnName and DataType, but it also looks deeper into the database, and it adds some constraints like AllowDBNull, AutoIncrement, MaxLength, etc.
However, this leads to problems in my application, because I want to further process the data internally.
So, is it possible to do a Load() just with setting the most basic properties (which come directly from the select statement), without setting AllowDBNull, MaxLength, and so on? Or do I need to clean these values after the Load() ? Or is there another alternative to calling Load() ?

If you don't want that behaviour don't use DataTable.Load but DbDataAdapter.Fill(DataTable):
DataTable dt = new DataTable();
using(var da = new SqlDataAdapter("select * from x", conn))
da.Fill(dt);
The Fill operation then adds the rows to destination DataTable objects
in the DataSet, creating the DataTable objects if they do not already
exist. When creating DataTable objects, the Fill operation normally
creates only column name metadata. However, if the MissingSchemaAction
property is set to AddWithKey, appropriate primary keys and
constraints are also created.
I have tested it, it just loads the column-names and types, all other properties like AllowDbNull or MaxLength have their default values which might be right or wrong.

Related

How to save only the new rows AND modified rows in datagridview?

I'm trying to save my data from datagridview row to my sql database. My problem is with my current code, it reads and updates all the rows regardless if it has any changes or not. I know this is going to be a problem especially if my table has large amounts of data.
These are what I did with my code so far:
//retrieve data from dbase
public void loadToDGV()
{
DBConn.DBConnect();
SqlDataAdapter sqlDA = new SqlDataAdapter("SELECT * from TableName", DBConnection.conn);
sqlDA.Fill(dataTable);
gridView.DataSource = dataTable;
}
Below is what's in my Save button.
foreach (DataGridViewRow row in gridView.Rows)
{
DBConn.DBConnect();
SqlCommand comm = new SqlCommand();
comm.Connection = DBConnection.conn;
comm = new SqlCommand("SPName", DBConnection.conn);
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#ID", row.Cells["ID"].Value == DBNull.Value ? "" : row.Cells["ID"].Value);
comm.ExecuteNonQuery();
}
I have another sample code where I can only save modified rows
changeTable = dataTable.GetChanges(DataRowState.Modified);
foreach (DataRow row in changeTable.Rows)
{
DBConn.DBConnect();
SqlCommand comm = new SqlCommand();
comm.Connection = DBConnection.conn;
comm = new SqlCommand("SPName", DBConnection.conn);
comm.CommandType = CommandType.StoredProcedure;
comm.Parameters.AddWithValue("#ID", row["ID"].ToString());
comm.ExecuteNonQuery();
}
What I wanted to do is save only BOTH the "New Rows" AND "Modified Rows".
Is there any way to only get newly added/edited rows? and not include all the rows from the gridview?
My Stored Procedure only checks if the ID is existing or not.
IF NOT EXISTS (SELECT 1 FROM TABLE WHERE ID = #ID)
-- INSERT QUERY
ELSE
-- UPDATE QUERY
You should not be using a loop at all, nor even referring to the grid. You obviously have a DataTable already. If you're not already doing so, populate it with a data adapter, bind it to a BindingSource and bind that to the grid. When it's time to save, call EndEdit on the BindingSource call Update on the data adapter and pass the DataTable. That's it, that's all. There's no need to call GetChanges.
Obviously you will have to configure the data adapter appropriately, with an InsertCommand to insert new records and an UpdateCommand to save modified records. I won't go into specifics as you haven't gone into specifics but there are plenty of examples around. You can find my own here.
EDIT:
The short answer to your question is that, if you want both modified and added rows, then specify both Modified and Added rows. The DataRowState enumeration has the Flags attribute, which means that you can create compound values. That would mean that this:
changeTable = dataTable.GetChanges(DataRowState.Modified);
would become this:
changeTable = dataTable.GetChanges(DataRowState.ModifiedData Or RowState.Added);
If you were going to then loop through those rows, you wouldn't create a new command object every iteration. You'd create one command and add the parameters once, then simply set the Value of each parameter in the loop. You would also need to call AcceptChanges on the original DataTable afterwards.
You should do any of that though. As I said, you should use the same data adapter as you used to retrieve the data to save the changes. Normally you'd use different SQL for the InsertCommand and UpdateCommand but, in your case, you can use the same. Just create a single command object with the appropriate parameters and assign it to both the InsertCommand and UpdateCommand properties, then call Update.

Select all columns except the first column for any given SQL Server table

I have this code in C#, but I need it to select all columns EXCEPT the first column of the table (the identity column), so that when I insert the data into an identical table in a different database, the destination database assigns its own identity column values:
SqlCommand commandSourceData = new SqlCommand($"SELECT * FROM dbo.{tableName};", sourceConnection);
SqlDataReader reader = commandSourceData.ExecuteReader();
Is there a way to do this?
If you want a generic solution for every column in your database you can use this kind of code
public string GetColumnsWithoutIdentity(string tableName, SqlConnection con)
{
SqlDataAdapter da = new SqlDataAdapter($"SELECT * FROM dbo.{tableName} where 1=0", con);
DataTable dt = new DataTable();
da.FillSchema(dt, SchemaType.Source);
var cols = dt.Columns.Cast<DataColumn>().Where(x => !x.AutoIncrement).Select(x => x.ColumnName);
return string.Join(",", cols);
}
Now you can use the returned string to build an Sql statement without the autoincrement column.
Notice that this code is vulnerable to Sql Injection. You should be absolutely sure that the tableName parameter used to build the first query is not typed directly by your user. Let it choose from a whitelist (readonly) of predefined tables (and also this is not 100% safe)
Another drawback is the fact that you need to hit the database two times. Once to get the schema with the info about the AutoIncrement column and one to fill the datatable after that.

Giving table friendly name in SQL query using MS SQL server

I am using ADO.NET to execute store procedure. The store procedure is having multiple select statement. When I access DataSet, the tables are named as Table1, Table2 and so on. I want to give user friend name to each of these table. I do not want to use Table variable or temp tables in my SQL query. DO I have any other alternatives?
I am using following code to get the dataset
SqlCommand cmd = new SqlCommand();
SqlDataAdapter da = new SqlDataAdapter();
DataSet ds = new DataSet();
try
{
con.ConnectionString = ConfigurationManager.ConnectionStrings["connString"].ConnectionString;
con.Open();
cmd = new SqlCommand("sp_GetData", con);
cmd.Parameters.Add(new SqlParameter("#ParamOne", param));
cmd.CommandType = CommandType.StoredProcedure;
da.SelectCommand = cmd;
da.Fill(ds);
}
I dont want to do this either
da.TableMappings.Add("Table", "MyTable1");
da.TableMappings.Add("Table1", "MyTable2");
da.TableMappings.Add("Table2", "MyTable3");
or this
ds.Tables[0].TableName = "NametbA";
ds.Tables[1].TableName = "NametbB";
Preferably I want to specify the name in the SQL query. The reason I want to use this approach is because, I will pass this dataset as it to a function which will write the table name into a file.
Please provide your suggestions.
Thank you
It is unfortunately not possible to set it automatically. You will have to provide it to the code somehow.
One option would be to change the structure of your results to have twice as many result sets where the odd one is the name and the even is the data:
-- Table name
SELECT 'nameoftable' AS TableName
-- Data
SELECT * FROM ...
c# code (consider it to be psudo code):
myDataSet.Tables[1].TableName = myDataSet.Tables[0]["TableName"].ToString();
Table names in the ADO.Net dataset object are entirely .Net, C# (or vb.net) specific. They have nothing to do with the table names in the SQL query or in the database. Change them in your C# code, by simply writing
myDataSet.Tables[0].TableName 'WhateverYouWant";

Use data in datatable to call stored procedures

I have a datatable fetching values from a stored procedure written as below:
SqlConnection sqlcon = new SqlConnection(ConfigurationManager.ConnectionStrings["db"].ConnectionString);
sqlcon.Open();
DataTable dt = new DataTable("tmp");
DataSet ds = new DataSet();
SqlDataAdapter da = new SqlDataAdapter();
SqlCommand cmd = new SqlCommand("usp_abc", sqlcon);
cmd.CommandType = CommandType.StoredProcedure;
da.SelectCommand = cmd;
da.Fill(dt);
Now, I need to loop through the datatable and get the values of this datatable and pass it is as a parameter to my stored proc.
I believe such operation is better done in the DB if possible instead of going to the DB for each row in the datatable row collection.
You can use foreach to loop through the datatable, each DataRow representing a row in the returned result.
foreach(DataRow row in dt.Rows)
{
var col1 = row[0]; //access using column index/position
var firstNameCol = row["FirstName"].ToString(); //access through column name
}
You can throw more light on what you want to do if it's possible to move it to DB (using stored procedure)
DataTable.Rows
Update: Passing value to stored proc using command.Parameters
cmd.Parameters.AddWithValue("#FirstName", firstNameCol);
You can use:
foreach (DataRow row in dt.Rows)
{
foreach(object o in row.ItemArray)
{
//do something with o
}
}
To set parameters in a sproc:
SqlCommand cmd = new SqlCommand("usp_abc", sqlcon);
cmd.CommandType = CommandType.StoredProcedure;
cmd.parameters.AddWithValue("#foo", "bar");
cmd.parameters.AddWithValue("#plugh", Integer.valueOf(17));
This will set the parameter "#foo" to the value "bar" and the parameter "#plugh" to the value 17. The value can be String, Integer, Boolean, probably others.
Read the documentation on the SqlParameterCollection object for other functions to set parameters. There are others that let you explicitly set the SQL data type, instead of making it guess a SQL datatype from your C# datatype. The only time I've ever needed this was when I wanted to set an Image value.
Side note that tripped me up early on: You cannot pass in NULL. If you want a value to be null, you have to pass in System.DBNull.Value. There is no doubt some good reason for this but I find it an annoying pain.
If you only need the data to pass as a parameter to a stored procedure consider DataReader.
DataTable has overhead.
DataReader is more efficient and simpler syntax.
SqlDataReader Class
If you need the DataTable for other stuff (like bind to a DataGrid) then stay with DataTable and the answer from codingbiz +1 seems correct to me.
Servy feels strongly the answer should include how to reference a values in a DataRow.
You can do it by ordinal position or column name.
row[0];
row["colx"];
But you already have that answer from condigbiz and should give the check to codingbiz if the is what you use.
SqlDataReader reader = command.ExecuteReader();
// Call Read before accessing data.
while (reader.Read())
{
if (!reader.IsDBnull(0))
{
cmd.Parameters.AddWithValue("#param1", Reader.GetString(0));
}
}
// Call Close when done reading.
reader.Close();
OP commented Reader would lock table.
You could use with (no lock) or read into a List<>;
But you could also just stay with DataTable and that seems like the best plan to me.

C#- Updating just updated column(s)

I have a web form. There are 20 fields that correspond to the columns in a database table. Let's say there's one record that has a BIRTHDATE column and I change its value from 13-July-2000 to 12-FEB-1985. But I don't touch the rest of the columns. Is there a way in C# to run an update statement like this:
UPDATE TABLE1 SET BIRHDATE=NEWVALUE WHERE ID=1111
instead of updating all the columns of the row like this:
UPDATE TABLE1 SET COLUMN1=NEWVALUE1, COLUMN2=NEWVALUE2,......,BIRTHDATE=NEWVALU
I think it would be a waste of resource. Am I wrong? I think DataAdapters are for this purpose but I'm not sure.
You can send a direct update statement to the Oracle Engine in this way.
using (OracleConnection cnn = new OracleConnection(connString))
using (OracleCommand cmd = new OracleCommand("UPDATE TABLE1 SET BIRHDATE=:NewDate WHERE ID=:ID", cnn))
{
cmd.Parameters.AddWithValue(":NewDate", YourDateTimeValue);
cmd.Parameters.AddWithValue(":ID", 111);
cnn.Open();
cmd.ExecuteNonQuery();
}
EDIT:
If you don't know which fields are changed (and don't want to use a ORM Tool) then you need to keep the original DataSource (a datatable, dataset?) used to populate initially your fields. Then update the related row and use a OracleDataAdapter.
using(OracleConnection cnn = new OracleConnection(connString))
using (OracleCommand cmd = new OracleCommand("SELECT * FROM TABLE1 WHERE 1=0", cnn))
{
OracleAdapter adp = new OracleDataAdapter();
adp.SelectCommand = cmd;
// The OracleDataAdapter will build the required string for the update command
// and will act on the rows inside the datatable who have the
// RowState = RowState.Changed Or Inserted Or Deleted
adp.Update(yourDataTable);
}
Keep in mind that this approach is inefficient because it requires two trip to the database. The first to discover your table structure, the second to update the row/s changed. Moreover, for the OracleDataAdapter to prepare the UpdateCommand/InsertCommand/DeleteCommand required, it needs a primary key in your table.
On the contrary, this is handy if you have many rows to update.
The last alternative (and probably the fastest) is a StoredProcedure, but in this case you need to go back to my first example and adapt the OracleCommand to use a StoredProcedure, (Add all fields as parameters, change CommandType to CommandType.StoredProcedure and change the text of the command to be the name of the StoredProcedure). Then the StoredProcedure will choose which fields need to be updated.

Categories

Resources