Local Data in MS Access - ms-access

I want to have a "Login" form where a user would enter in a number that uniquely identifies them. After this number is entered I want all of the other forms to use that number on this local instance of the application. IE the user is "Logged In".
One solution would be to have a column in the list of users mark whether that user is the "Active" user, and have every form look up that field. This stops working when you want to have multiple people using the front end to access the same back end, as there would now be more than one "Active" user. Is there a way to make a global local variable that I can use to keep track of who is currently using this particular instance of the front end?

For reliable multi-user access each user must have their own local copy of the front-end regardless, so you could just save their ID number in a global variable or a local table in the front-end.

You can have a public function in a module that returns the ID (stored in the module) and call it in queries and form modules.
Note that variables are always stored per process in the RAM and are therefore always per-user.

Related

MS Access - record level security

I have an MS Access database where all the users will be in the same group, except a few. I don't need to restrict certain objects from users. Instead, I need to give the current user write capabilities to his record and its related records only and read-only capabilities to all other records. Is this possible?
Unfortunately, be it Access, SQL server or Oracle, the restriction of rows to a single user is not built in. This means you have to build some type of interface in which you grab/use the users Logged on ID (their network pc id, or say prompt for a user + password). Then you when a form loads you have to restrict the records to that one given user. There are many ways to do this and they are all standard approaches to filtering or restricting data. However "YOU" have to build and write such code into the forms. You could perhaps base the form on a query with a expression (VBA or macro TempVars) that limits the records returned to the given user.
So you have to “cook” and “code” this ability for most any database. You thus also need to code to “save” the user name who created the record. This coding requirement as noted is required for most systems when looking to restrict data to single rows and such features are not generally built into the database system. You also likely need to restrict and prevent users from opening the database and seeing the table view.

How to autofill a field from a variable in Access 2007

I have a form for entering information, each user must "login" by entering their name when they start up my database. I would like to auto populate each records "User" field with this name they have logged in with.
I already have the whole login setup except I cant for the life of me work out how to insert the UserName string contents into a field when the user hits save.
If you do not wish to use Windows log-in details, the safest and easiest thing is a table. I generally keep a one row table to hold various details and current user name would work well in such a table. It can be deleted from the table in the log-in script and then updated when the user logs in. You can use DLookUp throughout your application to get the user name.
I would recommend saving the login Username to a global variable. Then in your save method, grab that global variable username and insert it into the field. Look up using global variables and you'll see its pretty easy to use.

Access 2000 form is slow to load over a network

I have an Access 2000 database that sits on a peer to peer network. (All computers are connected via ethernet 100 to a simple 4 port switch)
The database is designed with a back-end and a front-end.
There are 2 users that connect to the back-end via the network. One user connects to the back-end directly as the back-end sits on her computer.
All the computers are fairly low spec, with either 500 meg or 1 gig of ram
I have one form, frmInvoice, which is based on the table "tblInvoice" There are about 60,000 records in the table. There is also the table "tblInvoiceProducts" which is linked to "tblInvoice". There are about 150,000 records in the table "tblInvoiceProducts".
When I open form "frmInvoice" on the computer which hosts the back-end, the form opens very quickly. However if I open the form on one of the other computers it is very slow to open. However, once opened, record navigation is fast.
I think the problem is that when the form is opened, Access opens the entire table and all the records are accessible. Am I correct in this assumption?
I need to rectify this problem and welcome all suggestions.
I am going to install a D-Link DNS-323 2-Bay Network Storage Enclosure and store the back-end database on this as this will improve security / data protection.
Thanks in advance
"I think the problem is that when the form is opened, Access opens the entire table and all the records are accessible. Am I correct in this assumption?"
My bet is you are absolutely correct.
If you open the property sheet for the form, choose the Data tab, and look at the value for Record Source, do you see the name of one of your tables?
Or a query without a WHERE clause, perhaps like "SELECT * FROM tblInvoice;"?
If your answer is yes to either of those questions, Access will have to pull every single record from the table across the wire.
So, don't do that! :-) The key to decent performance is to limit the form's Record Source to a reasonable subset of rows.
Choose some criterion which makes sense in your situation, perhaps invoice date, and build a query which incorporates that criterion into its WHERE clause. Base that criterion on an indexed field --- add an index if your table doesn't already have one on that field. You can experiment by creating a new query in the query designer ... maybe the SQL View would look like this:
SELECT Invoice_ID, Customer_ID, invoice_date
FROM tblInvoice
WHERE invoice_date = Date();
That would retrieve only rows where invoice_date matches today's date.
(If you've already loaded the form, pulling down all the rows into local cache, you won't get a true indication of the speed of that query. It would be better to test the query from a new Access session without first loading the form.)
So then give the user a method to select a different invoice_date. For example, a text box control named txtSelectDate. And in the After Update event of that control, you can write an updated SELECT statement which you apply as the form's Record Source.
Private Sub txtSelectDate_AfterUpdate()
Dim strSql As String
If Len(Me.txtSelectDate & "") > 0 Then
strSql = "SELECT Invoice_ID, Customer_ID, invoice_date" & vbCrLf & _
"FROM tblInvoice" & vbCrLf & _
"WHERE invoice_date = " & Format(Me.txtSelectDate, "\#yyyy-m-d\#")
Debug.Print strSql
Me.RecordSource = strSql
End If
End Sub
The Debug.Print line will print the SELECT statement in the Immediate Window so if anything goes wrong you can view the completed statement, and copy & paste it into SQL View of a new query for testing. Changing the Record Source property automatically causes Access to requery the data source. And you can validate the user's entry in the text box's Before Update event to make sure it's a valid date.
Another possibility is to have the form first load with a single non-editable dummy record(1). Then display records only after the user chooses an invoice_date. To do that, you could use something like this as the form's Record Source:
SELECT TOP 1 Null AS Invoice_ID, Null AS Customer_ID, Null AS invoice_date
FROM tblInvoice;
In addition to the excellent suggestions made by HansUp, I would add the following:
Detailed forms should never take more than a couple of seconds to load. If they do, you're either doing something wrong or you need to optimize your binding/code.
Datasheet and reports can take longer depending on their complexity and how often people need them. Basically, if it's something they need a lot, it need to be fast. On the other hand, if it's a monthly report, waiting 30s is acceptable.
You don't say if your front end is split from the back-end or if you're using the same .mdb for your data and UI. If you do, then stop and split your database, even for the person on the same computer as the data.
Your back-end mdb file should only contain your data tables, and nothing else. The front-end mdb must be installed on the user's individual machines.
Make sure that your tables have the right indexes for your filter and sorting criteria. If you're binding your form to your tblInvoice and set the property to order by invoice reference and your table doesn't have an index for this, Jet will need to load all invoice references from the table to check their order.
If all your indexes are OK, Access will not load all the data to display a single record in a detailed form. It will only load lots of data if you're using a datasheet or a continuous form.
So the issue is probably with something else: maybe you're binding to a complex query from within your form, like a combo box or a datasheet, that it's that particular thing that's dragging your down.
Try to bind/unbind controls one by one to see which one has the biggest impact, then concentrate on optimizing that particular area.
You can also bind the controls as needed from code. For instance you can keep your form or the expensive controls unbound and wait for the user to select the record they need before you bind it to the form/control.
If you have lots of information on the form that loads related data from other queries and tables, you could re-design the form to group data in a Tab Control, then have a sub-form in each tab-control to display the data and load this only the user selects the tab.
Make sure that if you display lots of data in continuous forms/datasheets, that they are bound as snapshot to make them read-only. If they are read/write and someone else is trying to load a detailed form, Access has to do extensive lock check to make sure that the data is not being edited in multiple places. The more you make your data read-only, the better.
A final tip for for better performance: create a dummy table with a DUMMY field in your backend, link to it from the front-end. Bind this table to a form that has a single control for the DUMMY field. Make this form invisible and open it automatically when you start your application.
This form will keep the dummy table open and will ensure that your front-end doesn't have to open/close the connection to the backend every time it needs to query it.
Locking the database is time-expensive, especially over the network. If this is done very often, it can really decrease performance.
Maybe it won't change anything for your particular case, but I've found it to be good practice as the impact can be tremendous.
I have found the reasons for forms being very slow to load.
They include:
1. Having active filters on the form
2. Having combo boxes which allow users to navigate / select records
If I remove these I don't experience and speed issues.
To allow users to navigate / select records, I have a cmd button on the form. When clicked it opens a pop up form allowing the user to select the record of choice. I store the record id as a variable, close the pop up form and filter the recordset of the form e.g
select * from tblInvoice where InvoiceId = tmpInvoiceId
works for me!
yes Access (Jet) sucks bad across a network.. to make things more complex, there aren't real 'indexing tools' in Access like there are in SQL Server..
If I were you, I'd go get ONE solid PC, install SQL Server 2008 R2 on it, and upgrade to Access Data Projects.

MS Access 2007 - Identify users and based on that limit access to data

Considering the set up:
Ms Access Application split into Front End and Back End = both native MS Access
Front End consists of forms only - it will be the only way to access data
Front End copy distributed to each user machine (thanks for answers to this question)
I need to implement the following scenario:
Ms Access application with < 20 users,
each user is assigned to 1 to 10 projects,
when user opens the MS Access he should only be presented with the data related to the project(s) he is assigned to
So for example we have users:
John
Owen
John is assigned to projects A,B,D.
Owen to B,D
When John logs in he can see only data
related to projects A,B,D. When Owen
logs in he can see only B,D
John and Owen can access the application at the same time
Related tables in the Back End
user
project
userProject - links user(s) to project(s) in many to many relationship.Each user can be assigned to one or more projects, one or more users can work on a project.
I came across this solution on databasedev.co.uk which basically uses a hidden form to store the current users details and then using this to filter the data on other forms.
So here is my Question:
Would that be the recommended solution? Are there any better options? I was thinking that I could use a table on the Front End instead of the hidden form for example.
Edit Re Comment
I see no reason why you should not maintain a table of users in the back-end with a join table of user, project that can be used to filter the projects.
The current user can be obtained with code if you are using network name (http://www.mvps.org/access/api/api0008.htm), it can be stored to a hidden field on the form, which would be useful for setting the form to relevant projects, or you could store the name to a custom database property (http://wiki.lessthandot.com/index.php/Custom_Database_Properties_Creation_and_Use)
The code below applies to finding number of logged-in users.
You could use provider specific ADO Schemas. You need to pass a valid connection, for example:
ADOUserList Currentproject.Connection
Public Sub ADOUserList(oConn As ADODB.Connection)
Dim rs As ADODB.Recordset
Set rs = oConn.OpenSchema(adSchemaProviderSpecific, , "{947bb102-5d43-11d1-bdbf-00c04fb92675}")
Debug.Print rs.GetString
rs.Close
End Sub
More information: http://msdn.microsoft.com/en-us/library/aa155436.aspx
I have a similar, if slightly more complex, situation. In my case, Users are assigned to User Groups, which have varying permissions over Access objects (forms, reports etc). They also have Projects to which they are assigned, and Preferences. Tables:
user
user_group
user_pref
project
user_project
I still, however, use a hidden form (session) which holds session information about the user that's currently logged in. eg: user_id, user_name, subform of assigned projects, preferences (such as 'Current Project').
Finally, a module basSession contains all the functions I need to get or set any of the session information in the hidden form, eg gfSession_GetUserID().
HTH
Be aware that in your current setup there is no way a method to 'Identify users and based on that limit access to data'. If all data resides in a shared backend Access file, your users can just open the back-end database and browse through all data. The only way to actually limit your users' access to the data is by using a database server.
If you want to go to Access I would suggest that you create queries for all (important) tables and use these queries in the forms. Include a WHERE statement in the query that limits the output to what the user may view. You can do this by either changing the complete SQL on opening of the database or include a global variable in the WHERE part of the query and set that variable to the current UserID.

How to communicate between two instances of open MDB with same form?

In VBA (Access 2000) is there anyway to send information to a form between two open instances of the database?
For example :
User 1 has an instance open of DB.MDB on his PC and has FormOne open. User 2 has another instance of DB.MDB open on her PC and has FormOne open.
Can User 1 maniuplate the contents of a textbox on User 2's FormOne instance (ie. sending a message similar to a chat client)?
You could store data to a table and update the form or subform on a timer.
Like Remou, I think the table method is as you are going to get. You can optimize the querying by maintaining a one-record table that has the value of the last update. Then have your timer form check to see if the value has changed since the the timer last triggered, this will tell the timer to check the chat table.
In the alternative you can have records deleted as soon as they are read to keep the table small.
You will find that all the record creation/deletion will bloat your database though, so be sure to compact it regularly.
Lastly if all users have access to a shared drive you could just store the messages in a text file instead of a table.
Another issue is of course eavesdropping (with tables or files). You could minimize this by:
Obfuscating/encrypting the text before it is written and deobfuscating it when it is read. Deleting the record as soon as it is read by it's target.
Hiding the file/table. For files use: SetAttr myFile, vbSystem or vbHidden
For Tables prefix the table name with USys_ and make the table hidden.
All that said, it's still going to be a sorry substitute for a chat client. It will slow down the database and possibly slow down the shared drive. I would think long and hard about why I need this, and if it's really the best approach.