How to create Access database from a script - database

I would like to create an Access database from a script. Ideally, I would love something similar to the way SQL scripts can be used to create a SQL database.
Is this possible at all? Is there anything that would leverage the SQL scripts I already have? I want to create the same database structure as my SQL database.
Let me know if you need me to clarify anything. Thanks.

I ended up going with my own solution. I could not get either of the first two to work very well. I created two executables: one to create the database and one to run scripts.
For the application that creates the database, I added the COM reference "Microsoft ADO Ext. 2.8 for DDL and Security". The code is actually quite simple: (Replace "test.mdb" with the proper file path for your file.)
Dim cat As ADOX.Catalog = New ADOX.Catalog()
cat.Create("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb;Jet OLEDB:Engine Type=5")
To run the scripts, I created a simple parser to read and run "Access scripts". These scripts are based on SQL scripts, in that they provide a list of commands to run. For example, a script might be defined as:
--Create the table.
CREATE TABLE [Test] ([ID] Number, [Foo] Text(255))
--Add data to the table.
INSERT INTO [Test] ([ID], [Foo]) VALUES (1, 'Bar')
Here is the code for the parser. (Replace "test.mdb" with the proper file path for your file.)
Dim textStream = File.OpenText(scriptPath)
Dim lines As List(Of String) = New List(Of String)
While textStream.Peek() <> -1
lines.Add(textStream.ReadLine())
End While
textStream.Close()
Dim connection As OleDb.OleDbConnection = New OleDb.OleDbConnection("PROVIDER=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb")
connection.Open()
For Each line As String In lines
If Not String.IsNullOrEmpty(line) Then
If Not line.StartsWith("--") Then
Dim dbCommand = New OleDb.OleDbCommand(line, connection)
dbCommand.ExecuteNonQuery()
End If
End If
Next
connection.Close()
This solution works well and was pretty simple to implement.

It is possible to create an access database by code, either with DDL instructions of by manipulating ADO or DAO objects.
I doubt that DDL T-SQL code can be of any use to create an Access database: lots of instructions will not be understood in Access, from field types to indexes and constraints.
One option would be to use ADODB connections to connect both to the original SQL database and the newly created Access database (see #HansUp proposal), and use ADOX Object Model. You'll then be able to 'read' the SQL database (ie the ADOX Catalog) and its objects and recreate objects with 'similar' properties on the Access side: tables, fields, indexes, relations, etc.
Something similar could be done with DAO Object Model, but I guess it will be easier with ADOX.
Another option would be to check if existing softwares can do the trick. EMS SQL Manager is an option.

Related

ms access connect to 2 sql server databases on the same server

I am in the process of converting an Access application to use a SQL Server backend while still using the Access front end forms. Sounds like fun I know.
This application needs data access to 2 SQL Server databases that are on the same server. There are numerous inline sql query strings that attempt to connect to both databases at the same time on a single ADODB connection. This is failing because I am expecting records but none are returned.
What is the best way to fix this? Is there any way to use these sql strings or must it all be converted to stored procedures? Thanks for any help.
Here is some code:
Dim conn As ADODB.Connection
Set conn = New ADODB.Connection
Dim rst As ADODB.Recordset
Set rst = New ADODB.Recordset
With conn
.Provider = "sqlncli11"
.ConnectionString = "Server=[MY_SERVER];Database=[MY_DATABASE];User Id=sa; Password=password;"
.Open
End With
Dim str As String
str = "SELECT TABLE_DB1.Parent_Item_No FROM TABLE_DB1 INNER JOIN [DB2].[dbo].TABLE_DB2 ON (TABLE_DB1.Comp_Item_No = " & _
"TABLE_DB2.item_no) AND (TABLE_DB1.Loc = TABLE_DB2.loc) " & _
"GROUP BY TABLE_DB1.Parent_Item_No " & _
"HAVING (((TABLE_DB1.Parent_Item_No)='" & str_Assembly & "'));"
With rst
.Open str, conn, adOpenKeyset, adLockOptimistic ' this fails to return records
If .RecordCount > 0 Then
'Do Stuff
Else
'Do Other Stuff
End If
End With
You're only checking RecordCount. Take a look at this: slxdeveloper.com/page.aspx?action=viewarticle&articleid=33 Some recordset types don't populate the RecordCount property (adOpenKeyset should though). What happens if you use While Not .EOF and .BOF instead? What is that actual value of RecordCount in your code?
Would it at all be possible to run queries from SQL which save to an access file? I have nothing but trouble pulling data directly into access. I do have success when I set up an ODBC database and go to data -> get external data -> from other sources -> From microsoft query
Another method I myself have been successful with is using the power query add-on from microsoft. https://www.microsoft.com/en-us/download/details.aspx?id=39379
Despite this, what I still mostly end up doing is using the SQL import/export tool. I don't have screenshots or specific instructions as I'm not at work right now, but this can write directly to text files, Access databases, everything. I love it too much. Getting the correct drivers was a loop for sure. If you're on 64-bit and having issues then this is the driver you need. http://www.microsoft.com/en-us/download/details.aspx?id=13255
What I do is:
Set up my source (SQL 11 native client), choose the database you're pulling from
Specify an outfile type and location (I think it has to already exist)
When prompted to specify whether to pull data from tables and views or write a query, select write a query.
Go through the rest of the importer, you can edit the sql statement later on when viewing conversion and specify whether the transfer fails or ignores errors etc.
I personally still use the import export tool for transfers of all sizes because it's just so difficult to get all the correct drivers and get SQL to like what I want. (and without admin rights I get tired of asking my boss).
I hope one of those solutions can help you!
I've outline a more proper fix and a quick fix...
The more proper fix is the Data Layer Pattern. There is a lot to this fix and it may require some application structural changes. This is discussed in depth in another question:
Data Access Layer design patterns
A very simple fix is to use Access Linked tables. A Linked Table works like a normal Access table except the data is stored and updated on the SQL Server. Its basically a built in Data Access Layer to SQL Server. Its not an elegant solution but it gets you up and running right away. More info can be found here:
https://support.office.com/en-us/article/Import-or-link-to-SQL-Server-data-a5a3b4eb-57b9-45a0-b732-77bc6089b84e#bm2
On thing to be aware of with Linked Tables are that some Access Queries and Forms retrieve all the records before filtering and can lock the table so you can end up with some performance headaches if you have lots of data and lots of users.
Consider using the SQL server SYNONYM feature to add aliases for objects in one database to the other. Then just update all your queries to use one database.
Also, you could merge the two databases with each one, or one of them, going into a new schema to keep them separate. This could be tough if you have a lot of stored procedures, views, and functions in the database. This may be a terrible answer, but it could also be true that the two databases should never have been separate in the first place.
In the INNER JOIN, you prefixed the table name with DatabaseName.Schema.:
... FROM TABLE_DB1 INNER JOIN [DB2].[dbo].TABLE_DB2 ...
But you didn't do it in the other places where TABLE_DB2 occurs.
So you either need to change this:
ON (TABLE_DB1.Comp_Item_No = TABLE_DB2.item_no) AND (TABLE_DB1.Loc = TABLE_DB2.loc)
...to this:
ON (TABLE_DB1.Comp_Item_No = [DB2].[dbo].TABLE_DB2.item_no) AND (TABLE_DB1.Loc = [DB2].[dbo].TABLE_DB2.loc)
Or (which I prefer) you can use aliases for the table names in the FROM clause:
... FROM TABLE_DB1 t1 INNER JOIN [DB2].[dbo].TABLE_DB2 t2...
...then you use the aliases everywhere else:
str = "SELECT t1.Parent_Item_No FROM TABLE_DB1 t1 INNER JOIN [DB2].[dbo].TABLE_DB2 t2 ON (t1.Comp_Item_No = " & _
"t2.item_no) AND (t1.Loc = t2.loc) " & _
"GROUP BY t1.Parent_Item_No " & _
"HAVING (((t1.Parent_Item_No)='" & str_Assembly & "'));"
Additional background information:
If you connect to an SQL Server via ADO, you're directly connecting to exactly one database - the one in the connection string:
.ConnectionString = "Server=[MY_SERVER];Database=[MY_DATABASE];User Id=sa; Password=password;"
So in your case, the database you're connecting to is named MY_DATABASE. Any SQL you're executing via ADO goes to that database.
If you need to get data from other databases on the same server, you need to prefix the names with DatabaseName.Schema. in all places where you use them.
So let's assume we have:
a table MY_TABLE in MY_DATABASE
a table OTHER_TABLE in OTHER_DATABASE on the same server
both tables have the schema dbo (the default in SQL Server)
With the connection string from above (connecting to MY_DATABASE), you can join them as follows:
select *
from MY_TABLE
inner join OTHER_DATABASE.dbo.OTHER_TABLE
on MY_TABLE.SomeColumn = OTHER_DATABASE.dbo.OTHER_TABLE.OtherColumn
where OTHER_DATABASE.dbo.OTHER_TABLE.EvenAnotherColumn = 'foo'
See? Everywhere I used OTHER_TABLE, I prefixed it with OTHER_DATABASE.dbo..
PS:
It's bad practice to use the sa user to connect to a database with an application. The sa user has the highest permissions possible.
You should either use Windows authentication or create a dedicated SQL user for your app.
Consider storing your SQL in a pass-through query instead of VBA code. You can apply your filter using a copy of the .sql property of the pass-through query's querydef object, modifying it with the criteria they enter in your form at runtime.

An example of the file structure dbf

I have a short example on how to generate dbf files like
I saw the following link:
Data File Header Structure for the dBASE Version 7 Table File
I write my program with C #
For example, I want to produce the following table( to binary ):
Field Name Type MaxLength
-------------------------------------------
DSK_ID Character 100
DSK_ADRS Numeric 2
Are you trying to create the table within Foxpro (Visual Foxpro) itself?, DBase, or with a .net/java language. Your tabs are unclear as to what you are really getting into, and just creating the table via low-level is not the way to go.
I can modify this answer more, but suggest you edit your question to provide more detail.
The basic syntax, if using Visual FoxPro would be something like.
create table SomeTableName ( DSK_ID C(100), DSK_ADRS N(2,0) )
But again, would need more on the environment you plan on working with.
By knowing you want to do via C#, I would start by Downloading Microsoft's VFP OleDb provider.
Then, you can look at the many other links for connecting, querying (always parameterize) and execute what you need. Here is a short example to get a connection and create the table you want. Then it is up to you for querying, inserting, updating as needed.
OleDbConnection oConn = new OleDbConnection("Provider=VFPOLEDB.1;Data Source=C:\\SomePath");
OleDbCommand oCmd = new OleDbCommand();
oCmd.Connection = oConn;
oCmd.Connection.Open();
oCmd.CommandText = "create table SomeTableName ( DSK_ID C(100), DSK_ADRS N(2,0) )";
oCmd.ExecuteNonQuery();
oConn.Close();
Now, note, the "Connection" string has a Data Source. This should point to a PATH location where you WANT TO CREATE and/or QUERY the tables. You can have one connection that points to a folder that has 100+ tables and you can eventually query from any of them. But again, those are going to be other questions that you can find LOTS of answer to for sampling... for example, just search on
VFP OleDB C# and you will get plenty of hits
How are you going to handle memo files? Compound index files?
Just use the ODBC or Ole DB providers via COM InterOp and issue a CREATE TABLE.

executing insert\update\deletes - Use Linq?

I was looking at using linq to sql (or EF) rather than in line\dynamic sql within my application but have hit a possible issue. My application modifies data against any number of random databases accross several sql instances, linq appears to structured towards a single database scenario. Would it be best to just continue using in line sql or is there a method of using linq without tying it down to a single database?
An example of what I'm doing at the moment:
Using cn As SqlConnection = (ConnectionString)
Using cm As SqlCommand = New SqlCommand("< Dynamic sql>;", cn)
cn.Open()
cn.ChangeDatabase(Database)
cm.ExecuteNonQuery()
End Using
End Using
And example of a query would be:
DELETE FROM settings WHERE [Sec] = 'ON' AND [Key] = 'last'; INSERT Settings([Sec], [Key], [Val]) values('ON', 'last', GETDATE());
Although I am executing stored procedures within these databases, some custom, others for adding users (sp_adduser)
All target databases have the same structure, so any query\linq would work against it.
With LINQ, you are correct in that a DataContext goes to one single database, but you can have many DataContexts, each going to a different database. You can have, for example, a SettingsDataContext going to the database with your settings and a UsersDataContext going to the database with your users, or however you have it set up.
There is the syntax of it all. Here is a link to some examples of doing inserts and selects with ADO.NET and Linq to SQL.
http://blogs.msdn.com/b/wriju/archive/2008/07/14/linq-to-sql-vs-ado-net-a-comparison.aspx
One thing I have heard often is that Linq to SQL is for those that aren't real comfortable with SQL or who just like to work in the realms of C# or VB.NET code more than having to switch thought patterns and think in the ADO.NET/inline SQL realm.

Copy data from one database to another using VB.NET

I need to copy data from one database to another using a VB.NET program.
The target database is SQL Server the source database is some proprietary ODBC compliant database.
I need to loop through a list of table to copy. Read the data from the source database table for a given modified date. Delete the corresponding date from the target database table and insert the records from the source table. The databases are of the same structure i.e. table names and field names, but the data types may differ (however they are compliant e.g. double in source, float in target). No primary keys exist.
Heres how I may do it :
Firstly execute a Delete command to the target.
I could then use a DataReader to obtain data from the source, loop through the Items and create an Insert Command for each row. Add Parameters to the Command with the appropriate values and execute. And wrap the whole thing in a Transaction.
I was just wondering if I am missing a trick here. Any Suggestions
I think you should use the right for the job and I'm guessing that that is SSIS in this case, but I could be wrong and perhaps you have already explored that path.
In that case yes a datareader would do depnding how much data you have. A datatable might even be eassier and faster to program (no need to worry about datatypes since the adapter should take care of that.
The trick would be to use set based operations and not the 'row at a time' concept which we programmers were first taught :)
Here's some pseudocode
INSERT INTO DestTable (columns, columns...)
(Select ModifiedRow from SourceTable where date = Modified)
Perhaps your requirements are more complicated and may need the row by row approach, but this is normally not the case.
I'd opt to put this code in a job step and schedule on SQL. It could also be a stored procedure run from .net.
Also, using SSIS for a db to db transfer is most likely overkill unless you are going to be using some of the special transformations in there.
Take a look at the SqlBulkCopy class. If you can get the source into a DataTable or read it with an IDataReader then it's eligible. It will also attempt to convert between compatible types. See Single Bulk Copy Operations for more details.
This would be more desirable than using INSERT statements for each row.
Dim reader As System.IO.DirectoryInfo
reader = My.Computer.FileSystem.GetDirectoryInfo("c:\program Files\Microsoft SQL Server\MSSQL.1\mssql\data")
If (reader.Attributes And System.IO.FileAttributes.ReadOnly) > 0 Then
MsgBox("File is readonly!")
Else
MsgBox("Database is not read-only protected")
End If
Check all the tables first

Update an Access Link Table when the underlying SQL Server table's columns change

At work we've got a SQL Server database that several users connect to (read only) using Access 2003.
This was fine, except now one of the tables they look at is re-created fairly often and part of this process involves a cross-tab query.
Which means that, depending on the data, the number and and names of the columns potentially change each time the table is regenerated.
However when they look at the re-created table from Access, it still shows the column headings that were there when the table was first linked to.
Is there a way I can programmatically re-link the table each time they open the Access database?
What I ended up doing was creating a VBA function that looks something like below (needs error handling!)
Public Function ReConnectToLinkTable()
Dim db As Dao.Database
Dim tdf As Dao.TableDef
Set db = CurrentDb
Set tdf = db.CreateTableDef("local_table_name")
tdf.Connect = "ODBC;DRIVER=SQL Server;SERVER=server_name;UID=user_name;" & _
"PWD=password;APP=Microsoft Data Access Conponents;" & _
"DATABASE=database_name"
tdf.Attributes = TableDefAttributeEnum.dbAttachSavePWD
tdf.SourceTableName = "server_table_name"
db.TableDefs.Delete ("local_table_name")
db.TableDefs.Append tdf
End Function
Then I created a macro called AutoExec (the name guarantees it is called when the Access file is opened) which has an Action of RunCode, which calls the ReconnectToLinkTable() function.
ODBC linked tables break when the table or view on the server is altered. Some changes can result in them just becoming read-only, others will simply not include all the columns.
I have found that updating the connect string does not successfully fix this problem. It will usually fix missing fields, but it can still be read-only. The only reliable way to do this is to recreate the linked table on the fly.
Another alternative would be to not use a linked table at all, but use a saved QueryDef that has the appropriate connect string. This will never have to be updated, but could be a performance issue as the metadata stored in the table link helps Access figure out how to retrieve the data. Without that metadata stored in the table link, it has to retrieve that information from the server each time the query is run.
Something like this snippet is usually used. Search google for 'ms access refresh link table' and you'll find various solutions all similar to this one.

Resources