VBA Access: Change bound fields through code without write conflicts? - mysql

The issue I'm having is that many of the form controls trigger events that make changes to other fields bound to the form which results in a write conflict. I have tried placing If Me.Dirty Then Me.Dirty = False to save any changes from user interaction with the form, but I still receive the write conflict regardless or where it is placed.
For example, this will result in a write conflict:
Me!pnls_rte_profiles_id = Me!pr_rte
Me.Dirty = False
I'm using MS Access 2019 with a rather large database. The forms record set comes from 12 different tables linked through an ODBC connector. Each table has a primary key and a timestamp, but the timestamps aren't pulled as a part of the query for the recordset.

SOLVED:
So I found the issue stemmed from 2 places.
I forgot to include the other tables primary keys in my SELECT query.
Updating a records value to what it already was caused an issue with the MySQL ODBC connector.
The fix to 2:
Open the ODBC manager and configure the connector.
Under the Connection tab check Allow big result sets
Under the Cursors/Results tab check Return matched rows instead of affected rows
Relink tables in MS Access
Credit: here
Hopefully this helps others avoid days of struggling like I did.

Related

Ms access database with MySQL as the backend

My current team of 20 people uses an access database front end on their desktop. The backend of the access database is on a network drive. I have been asked to create an access database front end with MYSQL as the back end.
I have installed the MySQL workbench and the ODBC connector on my computer. I have created the schema and tables and I have connected the front end of the database connected to the MYSQL table I created in workbench. My question is
How do I deploy this for the team to use. I believe I can move the front end of the access dB to the network drive and the team can copy it to their desktop. But what do I do about the backend?
Does the team need to have the ODBC connector installed on their computers?
Should I move the MySQL workbench to the network drive?
PS: I am a new developer and just learning about databases and I am only familiar with the access database please go easy on me.
Thanks.
SO is for questions about coding, so this is OT. However:
One method is described in my article:
Deploy and update a Microsoft Access application with one click.
The old backend, you can archive. It will not be used anymore.
Yes.
Probably not. It is only for you, should you need to modify the
MySQL database.
Well, first up, you STILL need and want to deploy the application part called the front end (FE) to each workstation.
So, after you migrate the data to MySQL, then of course you will use the access linked table manager, and now link the tables to MySQL. How this works is really much the same as you have now. The only difference is that you linked tables now point to the database server (MySQL). From the application point of view, it should work as before.
Like all applications, be it Outlook, Excel, accounting packages? You STILL deploy the application part to each workstation. So just because YOU are now developing and writing software with Access does not mean out of the blue that you now for some strange reason STOP deploying the FE part to each workstation. In fact, you should be deploying a compiled version of your application (a accDE).
A few more tips:
WHEN you link the FE, MAKE SURE you use a FILE dsn. The reason for this is Access converts the links to DSN-less for you. What this means is that once you link the tables, then you are free to deploy the FE to each workstation and it will retain the linked table information WITHOUT you having to setup a DSN connection on each workstation. You will also have to of course deploy the MySQL ODBC driver to each workstation as that is not part of Access nor it is part of your application.
So just because you are now developing software does not suggest nor get you off the hook of deploying that application to each workstation. So, your setup you have now with a FE on each workstation does NOT change one bit.
For the most part, after you migrate the data to MySQL, then likely setup your relationships (say with MySQL workbench) there are several other things you need to keep in mind.
All tables now need a primary key. You likely have this, but Access with a access back end did and could work on tables without a PK. However, for SQL server/MySQL etc., then all tables need that PK.
Next up:
If you have any true/false columns, you MUST set a default up for that column. If such true/false columns have a default value or allow nulls, this will confuse access - so ensure that true/false columns can't have nulls and have default (useally 0) setup on the server side.
Add a "rowversion" column. This is not to be confused with a datetime column. In SQL server this rowversion column is of timestamp data type (a poor name, since the column has zero to do with time - it is simply a column that "versions" the row. This will also eliminate many errors. I don't know what this type of column is called in MySQL, but all tables should have this column (there is zero need to see/use/look at this column on your forms - but it should be part of the table.
All of your forms, reports and code should work as before. For VBA recordset code, you need this:
dim rst DAO.Recordset
dim strSQL as string
strSQL = "SELECT * from tblHotels"
set rst = currentdb.OpenRecordSet(strSQL).
You likely in the past had lots of code as per above.
You now need:
Set rst = CurrentDb.OpenRecordset(strSQL, dbOpenDynaset, dbSeeChanges)
You can generaly do a search and replace (find each .OpenRecordSet, and add the dbOpen and dbSee to each line of code you find. (I put the dbOpenDynaset, dbSeeChanges in my paste buffer. And then do a system wide search. It takes a few minutes at most to find all .openRecordSets
At this point, 99% of your code, forms and everything should work.
The ONE got ya?
In access be it on a form or in VBA recordSet code? When you create a new row, or start typing on/in a form? Access with access back end would generate the PK at that point. There was/is no need to save the record to get the PK value. This need is rare, and in a large application I only had about 2 or 3 places where this occured.
So, if you have code like this:
dim rstRecords as RecordSet
dim lngPK as Long ' get PK value of new record
Set rstRecords = CurrentDb.OpenRecordset("tblHotels")
rstRecords.AddNew
' code here sets/ add data/ set values
rstRecords!HotelName = "Mount top Hotel"
rstRecords!City = "Jasper"
' get PK of this new record
lngPK = rstRecods!ID ' get PK
rstRecords.Update
So, in above code, I am grabbing the PK value. But with server systems, you can NOT get the PK value until AFTER the record been saved. So, you have to change above to
rstRecords.Update
rstRecords.Bookmark = rstRecords.LastModified
' get PK of this new record
lngPK = rstRecods!ID ' get PK
Note how we grab/get the PK AFTER we save.
The bookmark above simply re-sets the record pointer to the new record. This is a "quirk" of DAO, and WHEN adding new reocrds, a .Update command moves the record pointer, and thus you move it back with the above .LastModified. You ONLY need the .LastMOdifed trick for NEW reocords. For existing, you already have the PK - so it don't matter.
This type of code is quite rare, but sometimes in a form (and not VBA reocdset code), some form code we might use/need/get/grab the PK value. In a form, you can do this:
if me.dirty = true then me.dirty = false ' save reocod
lngPK = me.ID ' get PK
So, once again, in the above form code, I make sure the record is saved first, and THEN I grab the PK value as above. Of course this is ONLY a issue for NEW records. (and you don't have to use the bookmark trick - that's only for recrodsets - not bound forms.
So, just keep in mind in the few cases when you need the PK value of a NEW record, you have to do this AFTER you saved the recordset (update) or in the case of a form, after you forced a form record save. This requirement is only for new records and ALSO only when your code needs to get/grab the new PK value.
Other then the above two issues? All the rest of your existing code and forms should work as before.

MS access error with mysql

I was working on an application that uses ms access 2010 and oracle. Now I am working on using my sql 5.7 instead of oracle.
But the code in ms access contains recordset.edit and then 2 set statements followed by recordset.update.
recordset.edit
recordset.Fields(22).value=1
recordset.update
This gives run time error 3197. You and another user are attempting to change the same data at same time.
I have tried to match all data types. But nothing seems to work. Still i get same error.
Appreciate your help. Thanks in advance
This “other” user message is often misleading, and in fact another user has not updated the record.
Two things to check for:
If some sql update, or recordset code “may” update the current record you are working on, then force a disk write and thus no pending updates exist:
Eg:
If me.Dirty = True then me.Dirty = false.
YOUR code here such as above is now called/run
The 2nd common issue is null bit columns. These often confuse access, and thus you need to MAKE SURE at the SERVER level you have a default set for such columns (a value of 0 for sql server – not sure if same for MySQL). So this is a MUST check issue.
Next up:
Make sure the table in question has a PK, and you should add a row version (timestamp). This type of column is NOT to be confused with a date time, or a column to hold current time – it is a row version column. So add a timestamp column, make sure PK column exists, and make sure any bit (true/false) column in the table has a default. If existing bit columns have null values, then run update query to set them all false (0).
After you make the table changes (if required), then re-link all your access tables.
The above should cover 99% of the cases when you get that “other” user message when in fact you are sure it is not another user.

How to set a date/time in front-end access that is compatible with back-end SQL?

We use Access 2016 as a front end and SQL Server 2008 as back end.
A user creates a new record in a form. In order to generate an autonumbered ID for this new record, I use DoCmd.RunCommand acCmdSaveRecord. When I use this command, the form moves off of that record. I need to then find the ID of the record just created. I cannot search for the largest autonumber ID because we use merge replication and different users have different ID ranges.
I tried making a DateCreated column and defaulting that column to the current date and time by using GetDate() in SQL Server, but that that makes Access give lock errors and other errors because it cannot properly read SQL Server's datetime format.
Is there a .saverecord option that doesn't move off the current record in a form? Or is there a date/time field that won't produce an error when using SQL Server backend and Access frontend?
In a nutshell, I need the autonumbered ID of the last record created in a form.
Use a Pass through query to run this SQL statement after you insert your record:
SELECT SCOPE_IDENTITY() AS ID
It will return the last created identity used in the current scope.
To get the auto number field to populate in an Access form bound to a sql server table you simply need to force the form to save the record. The act of saving the record does not and should not and will not move the form off of the record.
The code to get the auto number is thus:
If me.Dirty = True then Me.Dirty = false
Debug.print "Auto number from SQL server = " & me!id
You as a general rule cannot use SELECT SCOPE_IDENTITY() since the connection used in the form will be different then the connection you use to execute the SQL pass-through query. So the responses here are wild goose chase and incorrect and the wrong way to approach this problem.
Run another command in the same session to get the id:
SELECT ##IDENTITY
For more details check the: MSDN documentation.
Anyway, if you have an API to access the database, and it doesn't expose this, it should be expended to return the scope identity, since this is the only reliable way to know what ID was generated.

Why don't date functions work as defaults in MS Access used with MySQL

I am converting a Large MS Access 2010 application to act as a front end to MySQL 5.5 database, via the v5.1 ODBC Connector, and I am experiencing a strange problem when inserting new records with bound data forms.
In a data-entry form, if the default value of a date field is set in the properties of the control as a constant (such as #04-01-2014#) the new record is created in MySQL successfully, and after saving, all fields are visible in their associated bound fields. But if the default value is defined in the Access control as a function (example: =Date()) then although the MySQL row is created successfully, ODBC fails to find the new row and Access displays all values as #DELETED. Refresh and/or requery commands are not helpful. This is nothing to do with the well known issue of -- must have a primary key and a datestamp in MySQL. All of these safeguards are in place, and as stated, it does work without FE defaults.
No defaults are being set in the MySQL backend, and it makes no difference whether or not Nulls are allowed. Data type used in the backend is DateTime. If I do set defaults in the BE and none in the FE, everything is fine. But that way, the user DOES NOT SEE THE DEFAULTS in the data entry form... an unacceptable situation.
I have also tested both ODBC v5.2, and MySQL 5.6 with exactly the same results.
A solution that seems to work (so far) is to set all defaults in code, in the form.beforeUpdate event. Something like this:
form_beforeUpdate()
if me.newrecord then
field1.value = date()
field2.value = fOtherDate()
'etc
end if
'other code
exit sub
I could insert all new records with unbound forms and passthrough queries. But with 75,000 lines of code, that is a very big job
My question is - why do I need these "workarounds"? Isn't the whole purpose of ODBC to allow fairly normal operation? What is it about simple functions that "breaks it"? What other "ordinary" Access methods will break it? If I don't understand why it didn't work I can't be sure it is properly fixed.
Has anyone else had any experience with this SPECIFIC issue? I could not find any reference to it elsewhere. Thank you for your time.

Access Continuous Form with Linked Table - How to Avoid Hitting Database Server for Every Row in Form?

I'm migrating the data from an Access database to SQL Server via the SQL Server Migration Assistant (SSMA). The Access application will continue to be used with the local tables converted to linked tables.
One continuous form hangs for 15 - 30 seconds when it's loading. It displays approximately 2000 records. When I looked in SQL Server Profiler to see what it was doing, it was making a separate call to the backend database for each record in the form. So the delay when the form opens is caused by the 2000-odd separate calls to the database.
This is amazingly inefficient. Is there any way to get Access to make a single call to the backend database and retrieve all the records at once?
I don't know if this is relevant but the Record Source for the form is a view in the SQL Server backend database, which is linked to via an Access linked table (so, hopefully, Access just sees it as a table, not a view). I needed an Instead Of trigger on the view in SQL Server, and a unique index on the linked table in Access, to allow the records to be updated via the form.
If the act of opening that continuous form really does generate ~2000 separate SQL queries (one for every row in the view) then that is unusual behaviour for Access interacting with a SQL Server linked "table". Under normal circumstances what takes place is:
Access submits a single query to return all of the Primary Key values for all rows in the table/view. This query may be filtered and/or sorted by other columns based on the Filter and Order By properties of the form. This gives Access a list of the key values for every row that might be displayed in the form, in the order in which they will appear.
Access then creates a SQL prepared statement using sp_prepexec to retrieve entire rows from the table/view ten (10) rows at a time. The first call looks something like this...
declare #p1 int
set #p1=4
exec sp_prepexec #p1 output,N'#P1 int,#P2 int,#P3 int,#P4 int,#P5 int,#P6 int,#P7 int,#P8 int,#P9 int,#P10 int',N'SELECT "ID","AgentName" FROM "dbo"."myTbl" WHERE "ID" = #P1 OR "ID" = #P2 OR "ID" = #P3 OR "ID" = #P4 OR "ID" = #P5 OR "ID" = #P6 OR "ID" = #P7 OR "ID" = #P8 OR "ID" = #P9 OR "ID" = #P10',358,359,360,361,362,363,364,365,366,367
select #p1
...and each subsequent call uses sp_execute, something like this
exec sp_execute 4,368,369,370,371,372,373,374,375,376,377
Access repeats those calls until it has retrieved enough rows to fill the current page of continuous forms. It then displays those forms immediately.
Once the forms have been displayed, Access will "pre-fetch" a couple of more batches of rows (10 rows each) in anticipation of the user hitting PgDn or starting to scroll down.
If the user clicks the "Last Record" button in the record navigator, Access again uses sp_prepexec and sp_execute to request enough 10-row batches to fill the last page of the form, and possibly pre-fetch another couple of batches in case the user decides to hit PgUp or start scrolling up.
So in your case if Access really is causing SQL Server to run individual queries for every single row in the view then there may be something particular about your SQL View that is causing it. You could test that by creating an Access linked table to a single SQL Table or a simple one-table SQL View, then use SQL Server Profiler to check if opening that linked table causes the same behaviour.
Turned out the problem was two aggregate fields. One field's Control Source was =Count(ID) and the other field's Control Source was =Sum(Total_Qty).
Clearing the control sources of those two fields allowed the form to open quickly. SQL Server Profiler shows it calling sp_execute, as Gord Thompson described, to retrieve seven batches of 10 rows at a time. Much quicker than making 2000 calls to retrieve one row at a time.
I've come across the same problem again but this time with a different cause. I'm including it here for completeness, to help anyone in a similar situation:
This time the underlying query was hanging and SQL Server Profiler showed the same behaviour as before, with Access making separate calls to the SQL Server database to bring back one record at a time, for every record in the query.
The cause turned out to be the ORDER BY clause in the query. I guess Access had to pull back all records in the linked table from SQL Server before being able to order them. Makes sense when I think of it. Although I don't know why Access doesn't just pull all records through at once, instead of getting the records one at a time.
I would try setting the Recordset Type to Snapshot (on the Data tab of the Form's property sheet and/or the property sheet of the query you are using for the form source)