DataGridView bound to MS Access table with Autonumber Primary key causes Concurrency errors - vb.net

A have a volunteer timesheet data entry system which allows the volunteers to enter the times they have spent on various activities. I used the VB.net Designer to create the system (OK, I know now that that was not a good move!) so please don't ask me to show my code, most of it is generated by the Designer. My problem is this:
Each new record is assigned a negative number as a primary key when it is entered which is the way a dgv works with Access Automumber keys. I am executing the following statements in the RowValidating event when the row is valid.
a_dgv.EndEdit()
a_dgv.CommitEdit(DataGridViewDataErrorContexts.Commit)
Me.TimeSheets2BindingSource.EndEdit()
Me.TableAdapterManager.UpdateAll(Me.MembershipDataSet)
This code does not update the primary key value on the dgv although it does so in the Access table. If a user then attempts to delete or alter a record he has earlier added in the same session the update fails with a concurrency error. The only answer if have found to this problem is to refill the whole table. This is obviously not a desirable solution. Does anyone have a proven tested one?
I should probably mention that my table has two databound comboboxes

I was under the impression that a datagrid that is the result of a dataset from say Access does not show the PK values as -1, -2, -3.
If you created the disconnected dataset (or datatable) in code from a fill (pull data from Access), then each row normally does not show the PK.
However, regardless of the above, assuming you entered 5 rows, and now need to see the PK values?
You will during data entry in the grid should see this:
In above, I have added two rows. Your save code is somewhat like this:
tblHotels = DataGridView1.DataSource
rstDataReader.Update(tblHotels)
tblHotels.AcceptChanges()
That will send the data back to SQL server (or Access), and the autonumber PK 'ids are then generated. However, such changes are NOT pulled back into the dataset/datatable. In other words, the PK id's are generated in the database, but UNLESS you re-pull the data, you are not going to see the PK values.
You WILL have to re-pull the data. However, you can keep the current position of the grid, and re-fill the data like this:
rstDataReader.Update(tblHotels)
tblHotels.AcceptChanges()
Dim MyTop As Integer = DataGridView1.FirstDisplayedScrollingRowIndex
tblHotels.Clear()
rstDataReader.Fill(tblHotels)
DataGridView1.FirstDisplayedScrollingRowIndex = MyTop
And then you should see this:
The other way would be to send + update each row as you edit data, and then pull the PK, but obvious then you not be able to update the all your grid changes with a SAVE button, and thus of course no un-do ability.
I find the above that re-positions the top of the grid does not flicker. On the other hand, I suppose this could/would depend on how large the data set is (but then again, loading up a grid with too many rows is less then ideal).
So, as far as I can tell, you have to re-pull the dataset/datatable to get the new generated PK id's, or you have to save + pull for each row you edit. For a gridview with even several 100 rows, I don't see any flicker with the above code.

Related

vb.net dataview grid won't add record and doesn't update after data is modified independently

I have a dataview grid bound to a datasource at run time. The datasource is filled from an access database via a DataAdapter. The data fills and displays correctly, and updates to existing rows seem to work OK but I have two problems:
When I type something in a new row and then press return or switch to a different row, I want the DataAdapter to add that row then and there to the database so I can retrieve the Autonumber index of the new record from Access and use that to add an associated record in a different table (Entries, a many to many linking table). This isn't happening. In the RowLeave event I have adapter.Update(dsSentences) and then I check for the new row, but the RowCount doesn't reflect its presence even though the newly added data is visible in the grid, and the adapter.Update doesn't seem to have triggered the Insert query that I specified in the DataAdapter. So nothing is added.
(edit: OK, so the new row has not yet been added when this event is fired. Which event should I then use to commit the data and retrieve the Autonumber primary key for my new record? I've tried UserAddedRow but that one fires before you've entered any data into the new row.)
THe second problem is that I need to update the data independently and then have the grid reflect those changes. How do I do that? Is there some call that will force the grid to get the updated data from the DataAdapter via the Dataset? Any help would be much appreciated. I'm almost ready to dtop the whole idea of binding data and do it all through code, Data binfing is supposed to save time but I'm finding it labyrinthine and unpredictable.
FWIW here's the query I'm using to fill the grid:
PARAMETERS nIdCollection Long;
SELECT tblSentences.IdSentence, tblSentences.SentenceText, tblSentences.SentenceParsed, Not IsNull([tblSentences]![SentenceParsed]) AS HasParsed, Entries.IdEntry
FROM tblSentences INNER JOIN Entries ON tblSentences.IdSentence = Entries.IdSentence
WHERE (((Entries.IdCollection)=[nIdCollection]))
ORDER BY Entries.SortValue;
As you can see, it requires a record in Entries. After I've entered a new record in tblSentences, before there are any entries the IdEntry will be null assuming it shows up at all. That's why I need to intercept directly after the Insert, add the record to Entries and requery to keep everything in order. You could do it all in an SQL stored procedure but I have to use Access.
Edit: After a lot of googling I've come to the conclusion that what I'm trying to do = add a record to a table through an additional INSERT query apart from the one handled by the DataAdapter, every time a new row is added - simply can't be done if you are using data binding. I am going to have to delete all my code and start from scratch populating the grid through code (unbound). I think it's the only way to do what I want. I will leave this here as a warning to anyone else not to make my mistake of trying to use Data binding when your data is coming from more than one table. Bad mistake.

How do I add an auto-populated form record (created from another record) to a table in access?

I am populating a form with information from two other forms to create a new client record. From form 1 I get the CaseID which will tie the new client record back to the Case table. Form 2 lets me select a client from a list and pull information for some fields into the new record. These data populate Form 3 which appears to show a new record (then number at the bottom of the form is 1 more than currently exists in the table) But the ClientID field is blank - this is the unique key for the client records table. I cannot seem to get this field to increment and thus can't get the record to save to the table.
Based on various searches I've tried forcing the record to save but nothing happens... no errors nor new records. I've tried
If me.dirty then
me.dirty = false
end if
and
DoCmd.RunCommand acCmdSaveRecord
I assume I'm simply not truly initiating a new record even though the form suggests it is being created. What is the appropriate way to add the record, including which control I need to use to initiate the action (e.g., attach to the On_click() event of a button). Note that, sometimes the record will be perfectly fine as populated, and sometimes the user may need to edit one or more fields before saving the record. It is also plausible that occasionally a user will decide the record should not be added and thus will need to close without saving.
It turns out that the ClientID field in the test database I was provided is NOT an autonumber field as it will be in the live dB version. So I simply had to look up the max value in the table and add one. I will have to test the autoincrement when I get the true Dev environment set up with the actual database structure duplicated instead of the mockup I have been working on to create the work flow.

Access Query not updating fast enough

Im building a key inventory mgmt system. I've created a query that show's me keys currently not in use by identifying which keys have been returned, aren't lost or have never been rented. I copied this query into the look up field for key_id in my keyActivity table (used to record key sign outs). The issue is that the query does not update to provide available keys until the table keyActivity is closed and opened again
Example: I open keyActivity, indicate that key_id = 5 is lost. When I go to a new record and select the key to sign out, key_id = 5 is presented as being available. It is not until I close the table, open it again, that key = 5 is removed from the list.
Here you can see key 5 is indicated as lost in id 5 but in id 7 when selecting a key, 5 is available when it shouldn't be.
Is there anyway to fix this or set this up to work as intended. I plan on using forms to present all the information. Is there a form solution perhaps?
The suggestion you would be better off with a Form to change table data. It can be easily requery-ed to update the table according to the changes you make and to display the udpated data accordingly. Please also read on the given references for further info.
In terms of data updating and locks in a multi user environment this article could be helpful.
"Access is NOT a database server. It's a desktop database. It has been pushed to the limit to support mutli-user environments, but only in the sense that you can share the "back end" database across a network."
...
...
"Even the record locking is performed by the Front End. All of the front end
database applications share the "lock file" (a file with the same name as
the database file, but with the extension LDB); but that file is simply a
mechanism that the front ends use to determine which front end can make
changes to the database."
....
Here is a difference between requery and refresh:
Me.Requery forces the entire recordset (underlying data) for the form to reload. This means ALL of the records in your current form will reload. Your current position will be lost, so if you're sitting on record 10 of 100, you'll find yourself back on the first record. Me.Requery is essentially the same as closing and reopening the form. Any new records added by other concurrent users will be available. Likewise any records that have been deleted will disappear. Requery essentially "re-runs the query" that pulled the data into the form in the first place. You can also use requery to update the data in a list box or combo box.
Me.Refresh saves the current record that you're working on. It will also retrieve any changes (but not additions or deletions) to any records shown in the current form. Any calculations on the form (unbound fields) are recalculated. Refresh does NOT reload the recordset. You do not lose your position in the form (you stay on the current record). Any new records added by other users will not be shown.
Reference
MS Access - Write to Table Immediately After Changing Value in Form

Auto Inserting data into table row behind subform?

I've got a form that's got a subform and THAT subform/child has a subform/grandchild.
When I add a new client to the main form, the subform/child contains data like shipping address, etc etc. The subform/grandchild that one contains data like what we're shipping them.
When I make my initial new entry in the top form, there's data that seems to auto populate into the subform/child (and subsequently the table), however there are things that are always "default" items to ship (third subform/grandchild) that do not do that.
I've got some table constraints for the grandchild table like (ShipPackingSlip type bit) is set to 1, so that the checkbox should always be true. However, this does not occur on the 3rd layer of the form. Even setting the default option value to -1 will "autocheck" the box, but the data behind the forum does not reflect that.
Am I doing something wrong here?
Does that even make sense?
As I understand it (and I could be wrong), you can't use a subform in a subform on a main form without problems. It's better to organize your data and forms so that if you need to view more detailed information on data presented in a subform - it's better to call a new form from a button on the subform, passing a value to it so it pulls the correct record to view.
This would, of course, require you to store this subform of the subform data in a related table - then just relate it back to the main database with a unique identifier. This allows you a bit better means to organize your data, indexing isn't a pain, and you don't repeat information entry as much.

Linking one form to multiple tables, and saving data on click

So I have a form that I want the user to use to update multiple tables with. I do not want the form to be bound to the tables, as I would rather do this with VBA (I think, I don't know).
So here is a little table example
UserTable
UserID
First
Middle
Last
Division
Title
EquipTable
EquipID
AssignedLaptop
Model
Barcode
SoftTable
SoftID
SoftwareName
License#
Custom (running sum to calculate how many licenses are left....another question)
ExchangeTable
UserID
SoftID
EquipID
So that is how I set the tables up. All these text/cmb boxes exist on one table, and I would like to be able to save this data from this one form to all relative tables with a button click.
So can anyone help me with the following:
If I have this unbound form populate after a Create New button click, how then can I tell hidden text boxes (that ideally are to contain the " "ID numbers), to populate new ID numbers (auto-numbers), for each of the tables, in them, so that I can assign an UPDATE SQL statement to a SAVE button click, in the VB to save the data?
Could I set it up so that I just the "exchange table" (no idea why i named it that) populates the ID numbers for the other tables, instead of visa versa?
Am I going about this the wrong way
EDIT:
I was just trying to give you an example to see if what I was trying to do is possible with multiple tables (ultimately multiple Keys) on one form, and if so how does it differ from doing it with one form/table: I use the unbound approach alot because of the forms I have to build, and the need to constantly pas parameters from one form to the next in the VBA. I think it is faster to code it myself? So for this concept I always have a hidden text box on the form that usually has the IDnumber of the relative table. So to click save would just require a simple SQL = * WHERE Tbl_ID = frmID kinda idea.
All I was wondering were these questions?
When you run an INSERT, does the ID number need to be present in the STATEMENT or will access just assign the next relative one for you when you execute?
If not
Considering the method above, how can i determine this number (ID I need to use) myself, with code?
Another question? How do you defeat the mouse wheel scroll through records function on a bound form?
I see what you are saying, wording was strange on this but I do see where you are goign with this.
This is what you have:
You have an unbound form that is not linked to any table
This unbound form that is in no way linked to any table will be designed by you by adding some text boxes and also combo boxes and buttons
Within these textboxes you have some textboxes that are going to store the ID (The key value) of the row. So that when you do a save the ID number is set back to the text box
Once the ID number is set to this hidden field you can then issue updates to all tables that need this ID number
Does that sound right? If so it is very simple. All you have to do is once you click the "Create Button" perform the insert, something to this effect:
' Command to Insert Records.
Dim cmdInsert As New OleDbCommand()
cmdInsert.CommandText = "INSERT INTO AutoIncrementTest (Description) VALUES (?)"
cmdInsert.Connection = cnJetDB
cmdInsert.Parameters.Add(New OleDbParameter("Description", OleDbType.VarChar, 40, "Description"))
oleDa.InsertCommand = cmdInsert
Then you issue another command to grab the IDENTITY back, namely your ID:
' Create another command to get IDENTITY value.
cmdGetIdentity = New OleDbCommand()
cmdGetIdentity.CommandText = "SELECT ##IDENTITY"
cmdGetIdentity.Connection = cnJetDB
You can always get an identity with SELECT ##IDENTITY
So in your code behind you set the textbox to the value of SELECT ##IDENTITY.
I haven't done access in over 5 years but I remember doing something like this. Once you have this identity you place it in your hidden text box. This will help you perform your UPDATES that use this identity.
Here is a good place to start: http://support.microsoft.com/kb/815629
Here is another great article that may help you: http://support.microsoft.com/kb/815629