Okay guys,
After many readings and finding solutions down here I just need to ask this.
I already searched for it but it didn't help me as good as it should to sort this out.
Also, excuse me for my grammatical mistakes if any...
What's the problem?
I load up a WPF form with a combobox in. This combobox gets all the names from all the tables in my database. I choose a table name and press a button to fill up a DataGrid (WPF) with the chosen table. This all works perfectly. But, then when I changed a cell or added/deleted a row or column I have to update this to the database.
This is where I'm stuck. I got it working trough a not so optimal way. So that's why I ask if there is a better solution.
//fill the datagrid with data from chosen table in combobox!
selectedTable = cboxTables.SelectedItem.ToString();
dataset = db.getDataFromTable(selectedTable);
datatable = dataset.Tables[selectedTable];
datagridAdmin.ItemsSource = datatable.DefaultView;
And when the selection in the DataGrid has changed, I set the 'Submit' button as active wich calls this code:
db.updateTable(selectedTable, datatable);
Note that 'db' is a instance off my databaseclass. The method is as following:
public bool updateTable(String tableName, DataTable datatable)
{
using (SqlBulkCopy bulkcopy = new SqlBulkCopy(thisConnection.ConnectionString, SqlBulkCopyOptions.CheckConstraints))
{
//Set destination table name
//to table previously created.
bulkcopy.DestinationTableName = tableName;
try
{
thisCommand = new SqlCommand("DELETE FROM " + tableName, thisConnection);
thisCommand.ExecuteNonQuery();
bulkcopy.WriteToServer(datatable);
}
catch (Exception ex)
{
logger.WriteLine(applicationName, ex.Message);
}
}
}
But the problem is, the first column is a auto increment ID wich keeps raising every time I submit the changed DataGrid.
Isn't there a better way to do this?
Thanks!
PS: I'm coding in Visual Studio 2010 with C# in WPF.
In short for the method you have given you would be better off using the Truncate table method before calling your bulk copy, this will reset the Identity column back to 0:
thisCommand = new SqlCommand("TRUNCATE TABLE " + tableName, thisConnection);
However either method is going to cause problems with foreign keys in your database. I am not sure how you are retrieving your data in your database class, but I would look at using the SqlCommandBuilder to update your database rather than deleting and re-inserting all the data with each update.
EDIT
To further explain my suggestion of the SqlCommandBuilder.
A ViewModel such as the following should allow use of the SqlCommandBuilder (Note: this is hugely cut down and in real terms this requires better validation, exception and event handling, but the rough idea is there):
public class YourViewModel
{
private SqlDataAdapter adapter;
private DataTable table;
private SqlCommandBuilder commandBuilder;
public DataTable Table { get { return table; } set { table = value; } }
public void LoadTable(string connectionString, string tableName, int[] primaryKeyColumns)
{
adapter = new SqlDataAdapter(string.Format("SELECT * FROM {0}", tableName), connectionString);
table = new DataTable();
adapter.Fill(table);
List<DataColumn> primaryKeys = new List<DataColumn>();
for (int i = 0; i < primaryKeyColumns.Length; i++)
{
primaryKeys.Add(table.Columns[primaryKeyColumns[i]]);
}
table.PrimaryKey = primaryKeys.ToArray();
commandBuilder = new SqlCommandBuilder(adapter);
commandBuilder.GetUpdateCommand();
}
public int Update()
{
if (table == null || table.Rows.Count == 0 || adapter == null)
{
return 0;
}
if (table.PrimaryKey.Length == 0)
{
throw new Exception("Primary Keys must be defined before an update of this nature can be performed");
}
else
{
return adapter.Update(table);
}
}
}
Bind the Table property to the grid view, then call the Update method when required. You can even bind the update to the table's rowchanged events etc to automatically update the db. The Code Project has a fairly good article on WPF, Data grids and database integration.
Related
I'm working on a vet app, using C# and SQL. When I have access to the client history, it fills my pacient datagridview. I would like to know how to fill the clinic history datagridview from the pacient datagridview while I have selected the rows of the pacient. Already made the ClearSelection() from load to deselect any pacient, but I tried to make the SelectedRow event, and nothing happens on the clinic history datagridview.
If needed, I can put the code or pictures later.
PS: clinic history table has foreign key linked to pacient table.
EDIT: Here is the code I wrote. GetData gets the pacient's table and GetData2 the clinichistory's table.
private void GetData(string selectCommand)
{
try
{
dataAdapter = new SqlDataAdapter(selectCommand, connString);
table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
dataAdapter.Fill(table);
bindingSource17.DataSource = table;
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
}
private void GetData2(string selectCommand)
{
dataGridView3.DataSource = null;
try
{
dataAdapter = new SqlDataAdapter(selectCommand, connString);
table = new DataTable();
table.Locale = System.Globalization.CultureInfo.InvariantCulture;
dataAdapter.Fill(table);
bindingSource18.DataSource = table;
}
catch (SqlException ex)
{
MessageBox.Show(ex.Message);
}
}
//Here is the part that confuses me, since the pacient table works perfectly, but not so the clinichistory table. The following works with a client search, which returns the pacient owned by the client
private void button12_Click_1(object sender, EventArgs e)
{
Form7 Buscarcli = new Form7();
Buscarcli.TransfEvent += frm_TransfEvent;
Buscarcli.ShowDialog();
dataGridView2.DataSource = bindingSource17;
if (lblID.Text != null)
{
GetData("Select * from Pacientes where id_pacientes like '%" + lblID.Text + "%'");
}
}
//After this, Idk how to continue to make it work. Bindingsource17 is the datagridview for pacients, and Bindingsource18 the same but for clinichistory.
Thank you very much.
PS: I have a few weeks coding experiencie, so sorry if it looks like a complete mess. I do what I can.
Nothing like what you've been doing, I'm pretty sure. The easy way is:
Add a new dataset to your project
Open it, right click the surface, choose "Add TableAdapter", configure the connectionstring
Add a query of something like SELECT * FROM Patient WHERE ID = #id
Finish the Wizard, calling your query FillById/GetDataById
Add another query; rigt click the tableadapter, add query.. SELECT * FROM Patient WHERE lastName LIKE #lastName - or whatever you will search patients by
Call it FillByLastName (or whatever)
Add another tableadapter - SELECT * FROM ClinicHistory WHERE ID = #Id, FillById etc, finish
Add another wuery to this one; SELECT * FROM ClinicHistory WHERE PatientID = #PatientID, FillByPatientId etc, finish etc
Save the data set
Switch to the form
SHow the datasources window (View menu, other windows)
Drag the Patients node onto the form
Expand the Patients node
Drag the ClinicHistory, that is a child of the Patients table node NOT the one that is a peer of it, onto the form
Switch to code, find the line that fills the Patients table from the toolstriptextbox, add these lines under it:
clinicHistoryTableAdapter.ClearBeforeFill = false;
foreach(PatientsRow ro in yourDataSetName.Patients)
clinicHistoryTableAdapter.FillByPatientId(yourDataSetName.ClinicHistory, ro.Id);
And change the line itself so it reads more like:
patientsTableAdapter.FillByLastName(yourDataSetName.Patients, idToolStripTextBox.Text); //todo: rename that textbox
Adjust names for your context (I guessed)
Run the app; put a patient name into the top navigator (text box) and click Fill.. Many patients (hopefuily) will load.. And when you click on any one of them the ClinicHistory table auto updates to show the related data
I'm trying to fill a data grid view in my windows form application but nothing is being returned from the database when I execute the select query. I've looked at other questions about this topic on this site but cannot find anything that addresses my problem.
The name of the data view table is qbcMemDataView and the data source is a sqlite dataset called sqlite_dbDataSet1
Here is the code I have in place:
public Form1()
{
InitializeComponent();
dbConnection = new SQLiteConnection("Data Source=sqlite_db.sqlite;Version=3");
dbConnection.Open();
string[] restrictions = new string[4];
restrictions[2] = "test_table_mom";
using (DataTable dTbl = dbConnection.GetSchema("Tables", restrictions))
{
for (int i = 0; i < dTbl.Rows.Count; i++)
{
tblChooser.Items.Add(dTbl.Rows[i].ItemArray[dTbl.Columns.IndexOf("TABLE_NAME")].ToString());
}
if (tblChooser.Items.Count > 0)
{
tblChooser.SelectedIndex = 0;
}
}
}
private void btnSelect_tbl_Click(object sender, EventArgs e)
{
string sql = "SELECT id, name FROM test_table_mom";
using (SQLiteDataAdapter dbAdapter = new SQLiteDataAdapter(sql, dbConnection))
{
DataTable dataTbl = new DataTable();
dbAdapter.Fill(dataTbl);
qbcMemDataView.DataSource = dataTbl;
}
}
Also, here is a screenshot of the program running that might help better explain the issue I am having: http://imgur.com/j9ffeVi
I know there is data inside the table, I just don't know why it is not appearing in the data grid when the btnSelect_tbl_Click method is executed.
Any help would be appreciated.
Thanks!
Per the tutorial How to: Bind Data to the Windows Forms DataGridView Control, you are missing a BindingSource component that binds the data from the datasource to your table to the DataGrid.
Initialize the BindingSource at the top of your class like so:
private BindingSource bindingSource1 = new BindingSource();
Then near the top of your button click method before the sql add the line:
qbcMemDataView.DataSource = bindingSource1;
and finally change the last line of code
qbcMemDataView.DataSource = dataTbl;
to
bindingSource1.DataSource = dataTbl;
try that and see if it works for you.
Note: I'm not sure if this applies to c# but maybe it's universal fix.
Android builtin adapters and such use _id as the name of the id field. The other problem is _id and id well it's not well documented in android.
About "_id" field in Android SQLite
You can use this technique renaming in the select but it gets messy and you may not catch all occurrences.
string sql = "SELECT id _id, name FROM test_table_mom";
My Opinion: Go back and refactor your db id to _id.
I am currently working on a database system. In the system a user can search for a specific member using their ID. Searching for them filters all DataGridView results to just that specific member.
private void button3_Click(object sender, EventArgs e)
{
dataGridView1.ReadOnly = false;
using (SqlConnection con = new SqlConnection(constring))
{
int id = Convert.ToInt32(textBox1.Text);
con.Open();
DataTable FindAaron = new DataTable();
SqlDataAdapter adapt = new SqlDataAdapter("SELECT * FROM MembersTable WHERE MemberID =" + id, con);
adapt.Fill(FindAaron);
dataGridView1.DataSource = FindAaron;
con.Close();
}
}
This code filters the DataGridView results down to one row from the table 'MembersTable'. The user can now physically click on the table cell and edit the data as much as they want. Once they are finished they hit a 'Save Changes' button which I want to save the changes they made, update the source table and refill the DataGridView with all the members, now with updated info. This is the code I have behind the 'Save Changes' button at the moment.
try
{
//MemberClass.UpdateMember();
this.membersTableTableAdapter.Update(mainDatabaseDataSet.MembersTable);
dataGridView1.Refresh();
MessageBox.Show("Details updated");
}
catch
{
MessageBox.Show("An error has occured");
}
This unfortunately does not update the DataGridView in the form to display all the updated data or save the data that has been edited back to the Sql table. Have puzzled over this for a few days and can't figure out what I'm doing wrong. Any and all help is much appreciated.
Actually there is no connection seen between membersTableTableAdapter and adapt or mainDatabaseDataSet.MembersTable and FindAaron.
Try as following;
//Get the changed data
DataTable changes = FindAaron.GetChanges();
if (changes != null)
{
//Update data
adapt.Update(changes);
}
I'd like to know if it's a bad practice. I have a page of datagrid of 50 rows with about 10 text boxes in each row. Page function is very simple with just a button to update and save in ms sql. When user clicks the button, it takes long time to save. I am using update in a sql loop with a hidden id field id. What is the best way to simply update table?
for (int i=0; i<options_bind.Items.Count; i++)
{
if (((CheckBox)options_bind.Items[i].FindControl("check_Save")).Checked )
{
call sql update one by one here
}
}
Use SqlCommanBuilder to bind data to datagridview
sqlDataAdapter = new SqlDataAdapter(selectQueryString, sqlConnection);
sqlCommandBuilder = new SqlCommandBuilder(sqlDataAdapter);
dataTable = new DataTable();
sqlDataAdapter.Fill(dataTable);
bindingSource = new BindingSource();
bindingSource.DataSource = dataTable;
yourDataGridView.DataSource = bindingSource;
And then on update event
try
{
sqlDataAdapter.Update(dataTable);
}
catch (Exception exceptionObj)
{
MessageBox.Show(exceptionObj.Message.ToString());
}
I am using Xceed's Datagrid, bound to a dataset. I want to update the database when the datagrid's RowEditEnd is called. The problem is that in order to avoid concurrency violations when updating, I have to clear the dataset after update and refill it, like this:
public void UpdateDatabaseFromDataSet()
{
adapter.Update(exampleDataSet);
exampleDataSet.Clear();
adapter.Fill(exampleDataSet);
}
I think I have to do this because my dataset's primary autoincrement column "ID" does not match the values generated by the database. When I clear the dataset, it clears the datagrid and its selected cell. This is very annoying since, if you edit a cell and hit enter or a directional key, the cell and database will update fine, but your selection gets reset instead of navigating to the next row/cell. This makes entering data by hand very cumbersome.
Here is the method that creates the dataset:
public void InitDataSet(int tableid)
{
cmd = new SQLiteCommand("SELECT * FROM table_" + tableid, con);
adapter = new SQLiteDataAdapter(cmd);
cb = new SQLiteCommandBuilder(adapter);
try
{
exampleDataSet = new DataSet();
adapter.Fill(exampleDataSet);
prodDataSet.Tables[0].Columns[0].AutoIncrementSeed = -1;
prodDataSet.Tables[0].Columns[0].AutoIncrementStep = -1;
currenttableID = tableid;
}
catch (ApplicationException ex)
{
MessageBox.Show("Encountered an error.", "Error: " + ex.ToString());
}
}
Note that I have tried setting the tables autoincrementseed and autoincrementsteps to -1 but I still get concurrency violations if I don't refill the dataset.
I would really like my datagrid to work the way it does if I do not clear and refill the dataset. Is there anyway to avoid the concurrency violations I'm running into? Am I going about this the wrong way?
Thanks for your help.
-Steven
I don't exactly understand the complete problem but I might be able to give you something
When you don't clear the dataset and call "fill" it'll try to "merge" the records from the DB with the existing records. The clear forces your grid to refresh I guess.. losing the selected row.
With
Adapter.FillLoadOption = LoadOption.PreserveChanges (or other);
You can influence how records from the db that are changed are merged with the dataset.
Now there is one problem left and that is to determine the new autoinc value when an insert has taken place. You can catch the Adapter.RowUpdated
void Adapter_RowUpdated(object sender, SqlRowUpdatedEventArgs e)
{
if (e.StatementType==StatementType.Insert)
{
Select last autoinc value with ##Identity
e.Row["ID"] = last autoinc value
}
}
So :
Don't clear de datatable
Catch rowupdated and get the latest autoinc value in the db in put it in the new row
Evt. call fill to see if other users have made changes
Here is the code that ended up working for me, putting it here for other's benefits.
Here's my data providers update method (note that I was able to remove the extra fill call):
public void UpdateDatabaseFromDataSet()
{
adapter.Update(exampleDataSet);
}
And here is the RowUpdated event I'm using:
void adapter_RowUpdated(object sender, System.Data.Common.RowUpdatedEventArgs e)
{
if (e.StatementType == StatementType.Insert)
{
SQLiteCommand cmdNewID = new SQLiteCommand("SELECT last_insert_rowid()", con);
e.Row["RowID"] = cmdNewID.ExecuteScalar();
}
}