I am trying to update a record in .Net. I can't figure it out how to do it. I need to specify that I have a DataGridView that displays all the records, and when I click a cell, it shows in separate TextBoxes the content of all columns. So that is I am trying to do, when I modify the TextBoxes and click the Button for Update, to update the row which contains the clicked cell.
I have tried to do this
private void button2_Click(object sender, EventArgs e)
{
DataRow[] row_update = ds.Tables["Plane"].Select("airline_id = " + aidbox.Text);
try
{
row_update["airline_id"] = int.Parse(aidbox.Text);
row_update["plane_id"] = int.Parse(pid_box.Text);
row_update["name"] = name_box.Text;
row_update["model"] = model_box.Text;
row_update["f_seats"] = int.Parse(fc_box.Text);
row_update["s_seats"] = int.Parse(sc_box.Text);
row_update["b_seats"] = int.Parse(bs_box.Text);
row_update["p_weight"] = float.Parse(weight_box.Text);
}
catch (Exception ex) { MessageBox.Show(ex.Message); }
try
{
builder = new SqlCommandBuilder(data_adapter);
data_adapter.UpdateCommand = builder.GetUpdateCommand();
data_adapter.Update(ds, "Plane");
}
catch (SqlException ex) { MessageBox.Show(ex.Message); }
}
}
but I get this error Table doesn't have a primary key. in this line DataRow row_update = ds.Tables["Plane"].Rows.Find(aidbox.Text); Here I am trying to find the row that has the id in the aidbox (because I assume that the id is unique and never changes only the other values).
Can anyone help me please with this
DataRows.Find method Gets a specified DataRow using the PrimaryKey index
If you cannot define a primary key for the datatable then could use DataTable.Select as it accepts any column in the filter expression
DataRowCollection.Find requires a primary key on the DataTable. Set the PrimaryKey property by creating an array of DataColumns. See the MSDN documentation. Or just use Select instead.
Your previous find() may work through this code,
DataColumn[] temp = new DataColumn[1];
temp[0] = ds.Tables["Plane"].Columns["airline_id"];
ds.Tables["Plane"].PrimaryKey = temp;
DataRow row_update = ds.Tables["Plane"].Rows.Find(aidbox.Text);
Related
Edit 2: Turns out it only gives a NullReferenceException the first time. After that, it says that I am not allowed to change the ConnectionString property, even though I think I have closed it in the right places.
Thanks for taking the time to read this.
I am working on a WinForms application that uses an MS Access database, and I am currently having problems with an entry deletion feature I made.
So, I have a DataGridView that switches between 3 tables on a button click, and I have a function that deletes a row on a table that is currently open by clicking a button that is at the end of the row.
When I open my first table, and try to delete a row, it works just fine. However, if I open a different table afterwards and try to delete an entry, or even go back to the first table I opened, I get a NullReferenceException in the deletion function.
Here is the code to display one of the tables in DataGridView.
public DataTable Read()
{
connection.ConnectionString = connectionString;
OpenConnection(); //connection.Open() inside an if statement
dataTable.Clear();
OleDbCommand readStudentCommand = new OleDbCommand("select * from Students", connection); //display the whole list of students
OleDbDataReader reader = readStudentCommand.ExecuteReader();
dataTable.Load(reader);
connection.Close();
return dataTable;
}
Here is the code that deletes an entry
private void MainDataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
connection.ConnectionString = connectionString;
if (ConnectionState.Closed == connection.State)
{
connection.Open();
}
var senderGrid = (DataGridView)sender;
if (e.RowIndex >= 0 && senderGrid.Columns[e.ColumnIndex] == MainDataGridView.Columns["Delete"])
{
//this function retrieves the first column value of the deleted row, which has the ID of the entry (works with any table).
DeleteEntry(MainDataGridView.Rows[e.RowIndex].Cells[0].Value.ToString()); //exception thrown here (System.Windows.Forms.DataGridViewCell.Value.get returned null)
MainDataGridView.Rows.RemoveAt(e.RowIndex);
}
and here is DeleteEntry()
private void DeleteEntry(string deletedID)
{
string tableName = null;
string idType = null;
if (studentsDisplayed)
{
tableName = "Students";
idType = "Student ID";
}
else if(booksDisplayed)
{
tableName = "Books";
idType = "BookID";
}
else if(loansDisplayed)
{
tableName = "Loans";
idType = "Loan ID";
}
string deleteCommand = String.Format("DELETE * FROM {0} WHERE [{1}] = {2}", tableName, idType, deletedID);
OleDbCommand deleteEntryCommand = new OleDbCommand(deleteCommand, connection);
deleteEntryCommand.ExecuteNonQuery();
SaveData(); //this method just calls Update() in a dataAdapter of a relevant table
connection.Close();
}
Thank you!
Edit:
As per request, here is the code that switches the table. It simply references the first function and sets the returned dataTable as DataSource.
private void StudentButton_Click(object sender, EventArgs e) //display students
{
try
{
if (!studentsDisplayed)
{
MainDataGridView.DataSource = studentDAL.Read(); //studentDAL is the class that works with the Students table of my DB.
studentsDisplayed = true; //these 3 are to avoid duplicated creation of the same table
booksDisplayed = false;
loansDisplayed = false;
ComboBoxChanger(); //don't mind this, it's for an irrelevant feature
CreateButtons(5);
}
}
catch
{
throw;
}
}
Okay, so turns out the problem was the fact that DeleteEntry(MainDataGridView.Rows[e.RowIndex].Cells[0].Value.ToString()) had a problem with the Cells[0] part. After the first time loading a table, the 0th cell just vanished. So, I rewrote the code so that instead of declaring tableName and idType in DeleteEntry(), they're declared in MainDataGridView_CellContentClick(), and then made the DeleteEntry() accept 3 idType and tableName as parameters, and changed the MainDataGridView.Rows[e.RowIndex].Cells[0].Value.ToString() argument to MainDataGridView.Rows[e.RowIndex].Cells[idType].Value.ToString(). Now it works just fine!
So I have a DataGridView which I am using as a “row selector” on a form, and a bunch of controls are bound to the bindingSource.
One of the bound controls is a ComboBox serving as a lookup which enables status choices for the rows, this is populated from a DataTable with data pulled from the DB.
The population of this box is without any issue.
When a given row is selected from the DGV the form controls display data from the given row as they should, however the “statusComboBox” is not quite playing the game.
If in the DGV, I choose a row that has a different status to one previously selected, it works as it should, however, if I choose a row with the same value to a previously selected row, instead of the box showing the DisplayMember it shows the ValueMember.
IT only seems to occur in the above scenario, where the rows selection only instigates a display response from the bound ComboBox providng a previous selection had a different “Status ID”. What have I dont wrong that would cause this behaviour?
So the form load looks like this
private void ProjectsForm_Load(object sender, EventArgs e)
{
InitBindingSource();
//// bind Selector
//ASMod$ this needs to be 'true' unless you explicitly declare columns
ProjectsDataGridView.AutoGenerateColumns = false;
ProjectsDataGridView.DataSource = ProjectsBindingSource;
GetData();
//Set GeneralStatusBox
Helpers.GeneralStatusInitLookup(statusComboBox, ProjectsBindingSource);
}
The ProjectBindingSource is initialised thus:
private void InitBindingSource()
{
ProjectsBindingSource = new BindingSource();
projectsBindingNavigator.BindingSource = ProjectsBindingSource;
ProjectsBindingSource.PositionChanged += new EventHandler(ProjectsBindingSource_PositionChanged);
}
A ProjectsAddDataBindings procedure, and the contained DataBindings.Add for the ComboBox (executed at the end of a GetData routine that additionally populated ProjectsBindingSource):
ProjectsAddDataBindings();
{
…
this.statusComboBox.DataBindings.Add("Text", ProjectsBindingSource, "GSID");
…
}
After the GetData block the GeneralStatusInitLookup populates the Lookup elements, in a helper class simply because it provides functionality to a number of different forms
public static void GeneralStatusInitLookup(System.Windows.Forms.ComboBox comboBox, BindingSource primaryBindingSource)
{
string statusFilter = "";
statusFilter = Helpers.GetStatusGroupFilter(EndeavourForm.FilterId);
if (statusFilter != "")
{
statusFilter = " WHERE " + statusFilter;
}
//// string statusFilter = ""; //// temp
string sql = "";
sql = "SELECT GSID, ShortName FROM GeneralStatus" + statusFilter + " ORDER BY Pos";
GeneralStatusDataTable = Helpers.Db.GetDataTable(sql);
comboBox.DataSource = GeneralStatusDataTable;
comboBox.DisplayMember = "ShortName";
comboBox.ValueMember = "GSID";
comboBox.DataBindings.Add(new Binding("SelectedValue", primaryBindingSource.DataSource, "GSID"));
}
And the DGV initiated row change is handled like this
private void ProjectsBindingSource_PositionChanged(object sender, EventArgs e)
{
try
{
// Update the database with the user's changes.
UpdateProjects();
statusComboBox.SelectedValue = (int)CurrentDataRowView.Row["GSID"];
}
catch (Exception)
{
}
}
private void UpdateProjects()
{
try
{
ProjectsDataAdapter.Update((DataTable)ProjectsBindingSource.DataSource);
DataHelper.CommitProposedChanges(projectsDataSet);
if (this.projectsDataSet.HasChanges() == true)
{
ProjectsBindingSource.EndEdit();
ProjectsDataAdapter.Update();
}
CurrentDataRowView = (DataRowView)ProjectsBindingSource.Current;
}
catch (InvalidOperationException)
{
throw;
}
catch (Exception)
{
throw;
}
}
Anyway I hope I haven't swamped readers with to much code, but frankly I cant see where this is going wrong. So any help would be greatly appreciated.
This was a simple solution in the end. Both the GeneralStatusInitLookup() and the ProjectsAddDataBindings() blocks made use of DataBindings.Add ... For the lookup table this was fine, but with the binding to the main table; the later, I had used "Text" as the propertyName parameter.
I am developing a program for a Computer Store using c# with VS2010 and Database Provider = OleDB.
I need a form for updating the existing Products information in the stock. So I have a DataGridView to show the products Table (Edit property = on)
My code for updating a row :
private void button1_Click(object sender, EventArgs e)
{
con.Open();
for (int i = 0; i < dataGridView1.Rows.Count; i++)
{
DataGridViewRow UpdateRow = dataGridView1.Rows[i];
if (UpdateRow.Selected == true)
{
try
{
com.CommandText = "UPDATE Products SET ProductName=#pname,Model=#model WHERE ProductID= " + UpdateRow.Cells[0].Value + "";
com.Connection = con;
com.Parameters.AddWithValue("#pname", UpdateRow.Cells[1].Value);
com.Parameters.AddWithValue("#model", UpdateRow.Cells[2].Value);
int count = com.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
con.Close();
}
That code works and it updates the access database file but it's only for one row (the selected row)
Now i want to update every edit the user have done in the DataGridView
So all i have to do is to delete the line if (UpdateRow.Selected == true) and that way , the loop will go for every row and update the info .
I debugged the program and it didn't crash but the Access database didn't update anything! I wonder why...
There are 2 methods to accomplish this:
Follow the dataset and datatable route, assign the datatable to the grid and then use the getchanges() method to retrieve the rows which have changed and then update the selected rows into the database
Use the following following function to capture the rowindex property of the changed row
private void dataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
// use e.rowindex to get the row being modified and keep that in some list and
//after update button is pressed get the rows and update accordingly
//
}
I'm having trouble with a simple table edit in a WinForms Application. I must have missed a step.
I have a DataSet containing a DataTable connected to a database with a SqlDataAdapter. There is a SqlCommandBuilder on the SqlDataAdapter. On the form, there are TextBoxes which are bound to the DataTable. The binding was done in the Designer and its machine-produced statements like this:
this.tbLast.DataBindings.Add(new System.Windows.Forms.Binding("Text", this.belkData, "belk_mem.last", true));
When I fill the row in the DataTable, the values from the database appear in the textboxes, but when I change contents of the TextBox, the changes are apparently not being going to the DataTable. When I try to save a change, both of the following return null:
DataTable dtChanges = dtMem.GetChanges();
DataSet dsChanges = belkData.GetChanges();
What did I forget?
Edit - response to mrlucmorin:
The save is under a button. Code is:
BindingContext[belkData, "belk_mem"].EndCurrentEdit();
try
{
DataSet dsChanges = belkData.GetChanges();
if (dsChanges != null)
{
int nRows = sdaMem.Update(dsChanges);
MessageBox.Show("Row(s) Updated: " + nRows.ToString());
belkData.AcceptChanges();
}
else { MessageBox.Show("Nothing to save.", "No changes"); }
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
I've tried putting in these statements without any change in behavior:
dtMem.AcceptChanges();
belkData.AcceptChanges();
Your missing the DataSourceUpdateMode.OnPropertyChanged when creating Binding instance.
*IF you are using a BindingSource as well: Just do a simple BindingSource.EndEdit() and your TextBox data will be sent over to the DataTable. Example:-
_bsHeader.EndEdit();
if (_dsHeader.HasChanges())
{
DataTable dsInsert = _dsHeader.GetChanges(DataRowState.Added).Copy();
_objDal.Insert(dsInsert);
}
Hope this helps anyone who stumbles here.
Your problem was simply missing 5 letters.... You were missing .last on the dataMember property.
BindingContext[belkData, "belk_mem"].EndCurrentEdit();
Should have been
BindingContext[belkData, "belk_mem.last"].EndCurrentEdit();
Another way to resolve is to use an intermediary BindingSource and call EndEdit() on it before calling GetChanges().
Change your binding to:
BindingSource bs = new BindingSource();
bs.DataSource = belkData;
tbLast.DataBindings.Add("Text", bs, "belk_mem.last", true);
Change your Save button code to
try
{
bs.EndEdit(); // needs to be called before getting changes.
DataSet dsChanges = belkData.GetChanges();
if (dsChanges != null)
{
int nRows = sdaMem.Update(dsChanges);
MessageBox.Show("Row(s) Updated: " + nRows.ToString());
belkData.AcceptChanges();
}
else { MessageBox.Show("Nothing to save.", "No changes"); }
}
catch (Exception ex)
{
MessageBox.Show("Error: " + ex.Message);
}
You could also just call EndEdit() on the individual rows if you wanted to avoid using the intermediary BindingSource.
belkData.Rows[0].EndEdit();
or if you're dealing with more than one row
foreach( DataRow row in belkData.Rows )
row.EndEdit();
For those struggling to understand why EndEdit() or EndCurrentEdit() needs to be called in the first place. Take a look at the documentation on Row States and Row Versions and BindingSource.EndEdit Method. Until you call EndEdit(), the binding remains in an editing state, and changes are only visible in the Proposed version of the DataTable. Calling EndEdit() moves the changes to the Current version. Only then does the DataTable "have changes". DbDataAdapter's do not look at the Proposed version.
I have a dropdown in my webpage, which always returns the value 0 as the selected index no matter whichever item the user selects. I have populated the dropdown using a DB query. And I am populating in on Page_Load method in my page. The code shown below does the specified work: int danceid;
protected void Page_Load(Object sender, EventArgs e)
{
if (!IsPostBack)
{
PopulateDanceDropDown();
}
}
private void PopulateDanceDropDown()
{
DataTable dt = new DataTable();DataRow row = null;
dt.Columns.Add("Did", Type.GetType("System.Int32"));
dt.Columns.Add("DName", Type.GetType("System.String"));
var dancer_dance = (from dd in context.DANCER_AND_DANCE
where dd.UserId == dancerId
select new
{
Value = dd.DanceId,
Text = dd.DanceName
}).ToList();
foreach (var dndd in dancer_dance)
{
row = dt.NewRow();
row["Did"] = dndd.Value;
row["DName"] = dndd.Text;
dt.Rows.Add(row); dances.DataSource = dt;
dances.DataTextField = dt.Columns[1].ToString();
if (!IsPostBack)
{
dances.DataBind();
}
}
protected void changeIndex(object o, EventArgs e)
{
danceid = dances.SelectedIndex;
}
protected void dropthedance(object o, EventArgs e)
{
int danceIDFromDropDown = danceid;
var dancer_dance = from dd in context.DANCER_AND_DANCE
where dd.DanceId == danceIDFromDropDown
select dd;
foreach (var dndd in dancer_dance)
{
context.DANCER_AND_DANCE.DeleteOnSubmit(dndd);
}
try
{
context.SubmitChanges();
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
The line int danceIDFromDropDown = danceid; in the method dropthedance always has the value 0. Pleaseeeeeeeeeeeeee help someone
Are you sure that you want to be using the index as the ID? Typically, you're going to want to set the actual ID from your database as the DataValueField and then you can grab the value that way.
But I also noticed that you grab the index and place it into a variable on the indexchanged event and then you try to use that value in a different method. I'm assuming that danceid is an attribute somewhere not shown here. At any rate, the value isn't persisting through postbacks. Instead of trying to store it in a variable like you would on a desktop application, try adding EnableViewState="True" to your dropdown control. Then get that index on your submit handler directly. Or if you really want to store it in a variable, then try persisting the value of that variable by storing it in the session or caching it, then pull from that cache/session variable when it comes time to actually use the value.
But again, it might be better practice to place the danceid in the listitem object itself. Just the though of basing IDs on item indexes makes me shudder, especially when you populating the list from a database, because what happens when you add a new item to that list in the library and then try to sort them by name... then your indices become useless.
Replace
int danceIDFromDropDown = danceid;
with
int danceIDFromDropDown = dances.SelectedIndex;
It may work.