I'm sorry if the title is a little vague but I wasn't sure how to put it in a short space.
For context, I have a save button, which is when changes made are updated in the SQL server database. This works fine when adding rows, or changing values, even deleting rows. Nothing wrong there.
However, when I try to add or remove columns the app becomes a bit more problematic and simply does not update added/removed columns in the database and doesnt throw an error.
The only way I can get adding or removing columns to work is to use a sql query on the add/delete buttons, but this saves directly to the server - which i do not want.
What I need is for the changes to appear in the table, and then only update the database when the save button is clicked.
My code is here --- (Note, this is done over three forms, I have the main form with the table, plus two more that are used for inputting the name of the "trainer column" that is to be added or removed)
Private Function save() ''''' Main form
Try
ds.Tables(0).AcceptChanges()
da.Update(ds)
DataTableColours()
MessageBox.Show("Data updated successfully.")
Catch
MessageBox.Show("Data failed to update properly. Please ensure you are connected to the Baltic network and try again. If the problem persists, seek IT support.")
End Try
End Function
Public Function AddTrainerFunc() ''''' Main form
'Dim SqlAddCol As String = "ALTER TABLE MasterTrainerSchedule ADD [" & TrainerName.Trim() & "] nvarchar(255)"
'Using con As New OleDbConnection(cs)
' Using cmd As New OleDbCommand(SqlAddCol, con)
' con.Open()
' cmd.ExecuteNonQuery()
' End Using
'End Using
ds.Tables(0).Columns.Add(TrainerName.Trim()).DefaultValue = " "
RefreshBtn()
End Function
Public Function delTrainerFunc() ''''' Main form
Dim SqlDelCol As String = "ALTER TABLE MasterTrainerSchedule DROP COLUMN [" & TrainerName.Trim() & "]"
Using con As New OleDbConnection(cs)
Using cmd As New OleDbCommand(SqlDelCol, con)
con.Open()
cmd.ExecuteNonQuery()
End Using
End Using
ds.Tables(0).Columns.Remove(TrainerName)
DelTrainer.Close()
RefreshBtn()
MessageBox.Show("Trainer '" & TrainerName.Trim() & "' has been deleted from the table.")
End Function
Private Sub btnAdd_Click(sender As Object, e As EventArgs) Handles btnAdd.Click '''''Add Column Form
If Not txtTrainerName.Text = "Trainer Name Here" Or txtTrainerName.Text = "" Then
MTS.TrainerName = txtTrainerName.Text
MTS.Enabled = True
Me.Close()
MTS.AddTrainerFunc()
Else
MessageBox.Show("Please input a name for the trainer in the text box above.")
End If
End Sub
Private Sub btnDel_Click(sender As Object, e As EventArgs) Handles btnDel.Click ''''' Delete Column form
Dim delYN As Integer = MessageBox.Show("Are you sure you want to delete '" & cmbTrainers.Text & "' from the MTS table? The action will be permanent!", "Delete Trainer?", MessageBoxButtons.YesNo)
If delYN = DialogResult.Yes Then
MTS.Enabled = True
MTS.delTrainerFunc()
End If
End Sub
Sorry if this was a bit long winded but... I can't seem to find a way to add columns to the Database quite how I wanted too, neither through googling the answer, nor through simple experimentation, so I came here in the hopes that one of you may be able to help. Thanks in advance for any help you can provide.
EDIT --- I am using oleDB as the connection to sql, if this helps.
EDIT 2 --- Here's a few screenshots in case you wish to have a look at the visual side of the app.
The add form being used. (with the main form in the background. Sorry I couldnt get that on its own - only allowed two links with 6 rep!)
And the delete trainer form. The dropdown lists everyone in the table for you, then prompts you when you click "delete"
EDIT 3 --- Okay, I know the normalizing tables thing that Sean was on about could have worked, but it might have required quite a big change to the server used and to the program as well. I managed to find a simpler way to get this working that calls the sql queries to add or remove columns to the table on the save, only after the changes have been made to the data grid.
Heres some code in case anyone was interested. It's a little messy and can probably be optimized a bit, but this works for me regardless.
` Private Function save()
Try
da.Update(ds)
DataTableColours()
MessageBox.Show("Data updated successfully.")
Catch
MessageBox.Show("Data failed to update properly. Please ensure you are connected to the Baltic network and try again. If the problem persists, seek IT support.")
End Try
'This section reads the SQL server for column names, and adds any that are listed in the DGV, but not the database. I know its a little messy but itll do.
Dim columnnum As Integer = -1
Dim columname As String
For Each column In ds.Tables(0).Columns
columnnum = columnnum + 1
columname = dgvSchedule.Columns(columnnum).HeaderText
If Not ds2.Tables(0).Columns.Contains(columname) Then
MessageBox.Show("Table does not include " & columname)
Dim SqlAddCol As String = "ALTER TABLE MasterTrainerSchedule ADD [" & columname.Trim() & "] nvarchar(255)"
Using con As New OleDbConnection(cs)
Using cmd As New OleDbCommand(SqlAddCol, con)
con.Open()
cmd.ExecuteNonQuery()
End Using
End Using
End If
Next
columnnum = -1
For Each column In ds2.Tables(0).Columns
columnnum = columnnum + 1
columname = ds2.Tables(0).Columns(columnnum).ColumnName
If Not ds.Tables(0).Columns.Contains(columname) Then
MessageBox.Show("Will Delete " & columname)
Dim SqlDelCol As String = "ALTER TABLE MasterTrainerSchedule DROP COLUMN [" & columname.Trim() & "]"
Using con As New OleDbConnection(cs)
Using cmd As New OleDbCommand(SqlDelCol, con)
con.Open()
cmd.ExecuteNonQuery()
End Using
End Using
End If
Next
ds2.Tables.Clear()
da2 = New OleDbDataAdapter(sql, cs)
da2.Fill(ds2)
End Function`
I don't know very many details of what you are doing here but here is an example of a more normalized approach to this.
create table Trainers
(
TrainerID int identity
, FirstName varchar(25)
, LastName varchar(25)
, CONSTRAINT PK_Trainers PRIMARY KEY CLUSTERED (TrainerID)
)
create table Courses
(
CourseID int identity
, CourseName varchar(50)
, CONSTRAINT PK_Courses PRIMARY KEY CLUSTERED (CourseID)
)
create table TrainerCourses
(
TrainerID int not null
, CourseID int not null
, StartDate date not null
, EndDate date not null
, DailyStartTime time not null
, CONSTRAINT PK_TrainerCourses PRIMARY KEY CLUSTERED (TrainerID, CourseID, StartDate, DailyStartTime)
, CONSTRAINT FK_TrainerCourses_Trainers FOREIGN KEY (TrainerID) REFERENCES Trainers(TrainerID)
, CONSTRAINT FK_TrainerCourses_Courses FOREIGN KEY (CourseID) REFERENCES Courses(CourseID)
)
Related
I'm trying to use Access's upsizing wizard to move the data from Access to SQL Server. I'm currently on this error but can't figure out where the property is coming from;
Property 'Attributes' already exists for 'table'.
The SQL its trying to run is;
EXEC sp_addextendedproperty N'Attributes', N'2', N'user', N'dbo', N'table', N'table', N'column', N'ID'
But the table in Access doesn't include an ID column and I can't see anything in the properties on the table to indicate why it's trying to add the property for SQL Server.
In the wizard guide I chose not to import any extras like indexes, triggers etc.
Any ideas why the wizard is doing this and how to stop it trying to create the properties?
Alternatively, are there any other tools which would moved the data from Access to MSSQL while keeping Access front-end objects in place and working?
The upsizing wizard had its deficiencies from the start, and has been removed from recent versions of Access. I recommend not using it.
Personally, I have a form that handles upsizing for me. It's a form with on it two text boxes named ConStr and adoString (the first containing the connection string for Access to use including the ODBC; prefix, the second containing either an ODBC or OLEDB string for ADO to use), a button named ToSQL, and a listbox named lstTables. It contains the following code:
To populate the local tables on load:
Private Sub Form_Load()
lstTables.RowSourceType = "Value List"
Dim iterator As Variant
For Each iterator In CurrentDb.TableDefs
If Not iterator.NAME Like "MSys*" And Not iterator.NAME Like "~*" Then
lstTables.AddItem iterator.NAME
End If
Next iterator
End Sub
To move the tables over to SQL server:
Private Sub ToSQL_Click()
Dim i1 As Variant
Dim td As DAO.TableDef
Dim NewTd As DAO.TableDef
Dim db As DAO.Database
Set db = CurrentDb
'Iterate through all selected tables
With lstTables
For Each i1 In .ItemsSelected
Set td = db.TableDefs(.ItemData(i1))
'Add a primary key if none exist
'AddPK td 'Not providing this one as it's not part of normal upscaling
'Move the table to SQL server
DoCmd.TransferDatabase acExport, "ODBC Database", _
conStr _
, acTable, .ItemData(i1), .ItemData(i1)
'Rename the local table to name_local
td.NAME = .ItemData(i1) & "_local"
'Change the remote table to the schema specified
'ADOChangeSchema GetDefaultSchema(), "mySchema", .ItemData(i1) 'Not providing this one as it's not part of normal upscaling
'Set the primary key in SQL server
ADOAddPrimaryKey GetDefaultSchema(), .ItemData(i1), GetPKName(td)
'Create a new linked table, linking to the remote table
Set NewTd = db.CreateTableDef(.ItemData(i1), 0, GetDefaultSchema() & .ItemData(i1), conStr)
db.TableDefs.Append NewTd
Next i1
End With
End Sub
And some helper functions:
Public Sub ADOAddPrimaryKey(SchemaName As String, tableName As String, FieldName As String)
On Error GoTo SetNotNull
Dim conn As Object
Set conn = CreateObject("ADODB.Connection")
Dim cmd As Object
Set cmd = CreateObject("ADODB.Command")
conn.Open adoString
cmd.ActiveConnection = conn
cmd.CommandText = "ALTER TABLE " & SchemaName & ".[" & tableName & "] ADD CONSTRAINT [" & tableName & "_PK] PRIMARY KEY CLUSTERED([" & FieldName & "]);"
cmd.Execute
Exit Sub
SetNotNull:
If Err.Number = -2147217900 Then
cmd.CommandText = "ALTER TABLE " & SchemaName & ".[" & tableName & "] ALTER COLUMN [" & FieldName & "] INTEGER NOT NULL"
cmd.Execute
cmd.CommandText = "ALTER TABLE " & SchemaName & ".[" & tableName & "] ADD CONSTRAINT [" & tableName & "_PK] PRIMARY KEY CLUSTERED([" & FieldName & "]);"
cmd.Execute
Else
Err.Raise Err.Number
End If
End Sub
Public Function GetDefaultSchema() As String
Dim conn As Object
Set conn = CreateObject("ADODB.Connection")
Dim rs As Object
conn.Open adoString
Set rs = conn.Execute("SELECT SCHEMA_NAME()")
GetDefaultSchema = rs.Fields(0)
End Function
Public Function GetPKName(td As DAO.TableDef) As String
'Returns the name of the first field included in the primary key (WARNING! Doesn't return all fields for composite primary keys!)
Dim idx As DAO.Index
For Each idx In td.Indexes
If idx.Primary Then
GetPKName = idx.Fields(0).NAME
Exit Function
End If
Next idx
End Function
This form only preserves data and the primary key, and makes several assumptions (too lazy to avoid them), such as: table names don't contain square brackets, there are no composite primary keys, the table schema is safe for use in SQL statements, there are no attachment fields or multivalued fields, and there are no relationships (had a version that preserved relationships, but... I honestly don't know where it is now).
It also leaves a renamed copy of the local table. The original version then tests 1K random rows to check if the content is identical, but I've omitted that for brevity.
You can use this as a starting point, since it might need tuning to suit your specific needs.
I am using sql server with my VB.NET application where in multiple instance of the application is run from different server (CITRIX). I am sorting and picking up one individual Row for processing and immediately marking that row as picked in a column so that other instance doesn't pick up the same row and waste time. The issue is, in between picking up the row and updating as picked, another instance of the application is picking up the row. I have been suggested for using with DB Lock but the concept is not that much clear to me like whether it will solve my problem, whether I need admin right to use it (I do not have admin right in client DB) etc. Below is the code snippet I have used.
Dim MyConnection As SqlConnection
Try
MyConnection = New SqlConnection(connString)
MyConnection.Open()
Dim tableName As String = myTableName
Dim sqlQuery As String = "Select Top 1 * from " + tableName + " where "<some condition>
Dim MyCommand As SqlDataAdapter = New SqlDataAdapter(sqlQuery, MyConnection)
Dim DS as DataSet = New DataSet
MyCommand.Fill(DS, tableName)
If DS.Tables(0).Rows.Count >= 1 Then
sqlQuery = "UPDATE " + tableName + " SET Fld = #fld where Cond1= '" + DS.Tables(0).Rows(0).Item("Cond1").ToString + "'"
Dim cmd As New Data.SqlClient.SqlCommand(sqlQuery)
cmd.CommandType = CommandType.Text
cmd.Parameters.Add("#fld", Data.SqlDbType.VarChar).Value = "Picked"
Try
cmd.Connection = MyConnection
cmd.ExecuteNonQuery()
Catch ex As Exception
MsgBox(ex.ToString)
End Try
End If
Catch ex As Exception
MsgBox(ex.ToString)
Finally
MyConnection.Close()
End Try
I want it to make in such way that if an instance picks up a row, until it finishes updating the row, the row will not be visible to other instance with same query on the table, but other instance will continue to work with the other rows at the same time.
Two options I see:
Change your SELECT and UPDATE queries to a single UPDATE query. I didn't see where your SELECT was buying you anything.
If the SELECT is truly needed, then use a stored procedure on the database to handle the SELECT and the UPDATE on the database server side. You can lock the row during the transaction. See: Transaction Locking and Row Versioning Guide
Note that in general you should try to move your database queries to stored procedures. Not only does this reduce the amount of network traffic moving datasets back and forth, it increases the reliability, separates your database code from the UI, allows updates to the procedures without having to push new versions of the client application out and also avoids SQL injection.
JUST A HEADS UP ----- This no longer applies for me. I fixed it by adding the following two lines to my form.load.
cb.QuotePrefix = "["
cb.QuoteSuffix = "]"
It has fixed the issue for me and I hope it sorts it out for you too.
Okay, so, I have an application in VB.net that serves as a scheduler for the trainers at the company I work at. In the edit mode (so updates can be made to the database the information is stored on) there are a number of buttons, adding and removing trainers, or adding weeks to the table.
The button causing me problems is the save/update button. It has three sets of commands within it, one for updating added columns, one for removed columns, and then a third one which simply updates other modified data. I know it probably could be more efficient but oh well, I'll get to that later.
The problem is, the last chunk of code includes "da.update(ds)" which is the data adapter updating the datasource. While this command works perfectly fine in our other app that connects to our SQL server, its causing problems here.
Any column where the first cell's value is null causes an exception saying
"Incorrect syntax near 'the column header's first two characters'."
Now, I thought this issue stemmed from - due to the exception - me using an incorrect set of names for the columns, which were the dates of the mondays of each week (so for example 01/02/2016) so it'd show Incorrect syntax near '01'. in this instance.
However, changing the naming convention did not fix this like how the exception would suggest, and it only occurs on columns where the FIRST value is a null - implying that the first trainer has nothing planned for this week right now.
Anyone have any ideas as to a way around this that doesnt involve basically filling in every null on this rather large table? I know that would work but It'd be pretty time consuming, and I am willing to do this if no other solution rears its head.
I've looked around on the internet and haven't found a solution that appeared relevant to our exact issue, so help would be very appreciated.
In case it is important - here is the function causing the issue.
Dim da As OleDbDataAdapter 'The datasets and adapters variables.
Dim da2 As OleDbDataAdapter
Public ds As DataSet = New DataSet
Public ds2 As DataSet = New DataSet
'Connection String. Connects to the server and finds the database and table listed.
cs = "Provider=SQLOLEDB;"
cs &= "Server=SOFWAREDEVSQLSE\SQLEXPRESS;"
cs &= "Database=MTS2;"
cs &= "User Id=;" 'You don't need to see that to be fair.
cs &= "Password=;" 'You don't need to see that to be fair.
sql = "Select * FROM MTS2;"
'Runs the string. Flares up a timed out error if connection could not be established.
Try
da = New OleDbDataAdapter(sql, cs)
da.Fill(ds)
da2 = New OleDbDataAdapter(sql, cs)
da2.Fill(ds2)
Catch ex As Exception
MsgBox("Connection failed. Please ensure you have a suitable connection to the Training network. Otherwise, refer to helpdesk support for further assistance.")
Me.Close()
End Try
dgvSchedule.DataSource = ds.Tables(0)
Private Function save()
'Try
''This section reads the SQL server for column names, and adds any that are listed in the DGV, but not the database. I know its a little messy but itll do.
Dim columnnum As Integer = -1
Dim columname As String
For Each column In ds.Tables(0).Columns
columnnum = columnnum + 1
columname = dgvSchedule.Columns(columnnum).HeaderText
If Not ds2.Tables(0).Columns.Contains(columname) Then
Dim SqlAddCol As String = "ALTER TABLE MTS2 ADD [" & columname.Trim() & "] nvarchar(255)"
Using con As New OleDbConnection(cs)
Using cmd As New OleDbCommand(SqlAddCol, con)
con.Open()
cmd.ExecuteNonQuery()
End Using
End Using
End If
Next
columnnum = -1
For Each column In ds2.Tables(0).Columns
columnnum = columnnum + 1
columname = ds2.Tables(0).Columns(columnnum).ColumnName
If Not ds.Tables(0).Columns.Contains(columname) Then
Dim SqlDelCol As String = "ALTER TABLE MTS2 DROP COLUMN [" & columname.Trim() & "]"
Using con As New OleDbConnection(cs)
Using cmd As New OleDbCommand(SqlDelCol, con)
con.Open()
cmd.ExecuteNonQuery()
End Using
End Using
End If
Next
ds2.Tables.Clear()
da2 = New OleDbDataAdapter(sql, cs)
da2.Fill(ds2)
da.Update(ds) ''''' The exception is thrown here. " Incorrect syntax near '01'."
DataTableColours()
MessageBox.Show("Data saved successfully. New weeks and trainers added and deleted. Changed values updated.")
'Catch
' MessageBox.Show("Data failed to update properly. Please ensure you are connected to the Baltic network and try again. If the problem persists, seek IT support.")
'End Try
End Function
The function saves the values in the data grid view (DGVSchedule) to the server by taking the current columns in the DS with their original columns (which are in DS2) A Sql query is then ran to add or remove any column mismatches. DS2 is then updated to use the same values as DS. Finally, DA.update(DS) is called, which updates all other modified values into the SQL server - theoretically. It is instead causing our peculiar exception.
Any help would be greatly appreciated, thanks.
I have fixed the problem I was encountering. I fixed it by adding the following two lines to my form.load.
cb.QuotePrefix = "["
cb.QuoteSuffix = "]"
Basically, I feel like a dumbass now but this solved the problem.
Unable to delete data from database in vb.net using SQL Server.
Here is my code for deletion, can you tell me where I have gone wrong?
Name of the table is OneToOne:
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
cn.Open()
With cmd
.Connection = cn
.CommandText = " DELETE * FROM OneToOne WHERE SrNo = " & DataGridView1.SelectedRows(0).Cells(0).Value
i = cmd.ExecuteNonQuery
End With
If (i > 0) Then
MsgBox("Conversation Deleted")
End If
cn.Close()
showdata()
End Sub
To delete all rows at once, you can iterate through the selected rows and build a comma-separated string of the Cell(0) values.
Then instead of using WHERE srNo = , use WHERE srNo IN ({your string})
At least, this is the way to do it following your current approach. A better way would be to pass a table-valued parameter to a stored procedure, but that's a broad subject that you would need to research.
And, as with most programming questions, there are multiple other ways this can be done.
on_button_click
s = "DELETE FROM OneToOne WHERE SrNo IN(val1,val2.....valn)"
cn.Open()
cmd = New SqlCommand(s, cn)
i = cmd.ExecuteNonQuery()
If i > 0 Then
MsgBox("yup")
End If
cn.Close()
val1,val2.. are your SrNo values. It's on you how you retrieve/provide SrNo values
I am doing some maintenance work on a linked-table application in Microsoft Access 2010 and experiencing this little gem of a problem. The application is linked to a SQL Server 2008 database. The application has a main form that allows a user to choose a combination of park code and resource and pops up an edit form for the details of that particular combination. If that combo doesn't exist in the database, the application inserts a new record in, but the issue is that 2 records get inserted.
Here's the seat of the problem code, it gets called when I need to insert a new record in a details popup form:
Private Sub New_Rec(unit_code As String, resource As String, sql As String)
DoCmd.RunSQL ("INSERT INTO PARK_RESOURCES (unit_code, resource, sensitivity) VALUES " _
& "('" & unit_code & "','" & resource & "','public')")
'Force an explicit save
'http://www.pcreview.co.uk/forums/update-cancelupdate-without-addnew-edit-t1150554.html
If Me.Dirty Then
Me.Dirty = False
End If
Me.RecordSource = sql
End Sub
Creating a "new" record results in 2 records getting inserted into the Recordset. It doesn't seem to matter if I move the explicit save code before or after setting the RecordSource. In either order (and stopping after either) produces 2 new records inserted in the database (verified by querying in SSMS).
When I set the RecordSource property and step through the code, the event chain looks like: Me.RecordSource = sql --> Form_BeforeUpdate() --> Form_AfterUpdate() --> Form_After_Insert() --> Form_Current(). The duplicate is not present at the close of BeforeUpdate, but by the time I get to AfterUpdate, the duplicate has already been inserted. What happens between BeforeUpdate and AfterUpdate that causes this to happen?
According to MSDN, the order is: BeforeInsert → BeforeUpdate → AfterUpdate → AfterInsert. They also state that setting the value of a control through Visual Basic doesn't trigger these events. But when I update the RecordSource in code, the last 3 events certainly fire; BeforeInsert is the only one that a step-through doesn't stop on.
As per Daniel Cook's request, here is the calling code.
Private Sub Form_Load()
On Error GoTo Err_Form_Load
Me.KeyPreview = True
If Not IsNull(Me.OpenArgs) Then
ProcessOpenArgs (Me.OpenArgs)
Me.lblHeader.Caption = Me.unit_code & ": Resource - " & Me.resource
Else
Me.lblHeader.Caption = "Information Needs"
End If
... (error trapping)
End Sub
And the ProcessOpenArgs sub (OpenArgs get set as "park;resource"):
Private Sub ProcessOpenArgs(open_args As String)
On Error GoTo Err_ProcessOpenArgs
Dim Args() As String
Args = Split(open_args, ";")
Me.unit_code = Args(0)
Me.resource = Args(1)
'Check to see if there are records in the database for current unit/resource combo'
Dim rs As DAO.Recordset
Dim sql As String
sql = "SELECT * FROM PARK_RESOURCES " & _
"WHERE resource='" & Me.resource & "' AND unit_code='" & Me.unit_code & "'"
Set rs = CurrentDb.OpenRecordset(sql, dbOpenDynaset, dbSeeChanges)
'if there aren''t, create a new record'
If (rs.RecordCount = 0) Then
New_Rec Me.unit_code, Me.resource, sql
Else 'go to the current listing'
Me.RecordSource = sql
End If
Exit_ProcessOpenArgs:
Exit Sub
Err_ProcessOpenArgs:
MsgBox Err.Number & Err.description
Resume Exit_ProcessOpenArgs
End Sub
I will continue to comb through the event documentation and as a last resort I may go totally nuts and just stick every possible event in my VBA code and step through them, but does anyone know what could be happening to cause the duplicates?
In your ProcessOpenArgs, the If (rs.RecordCount = 0) Then line could be a problem unless you first use rs.MoveLast - see here
When I'm setting Me.unit_code and Me.unit here:
Private Sub ProcessOpenArgs(open_args As String)
On Error GoTo Err_ProcessOpenArgs
Dim Args() As String
Args = Split(open_args, ";")
Me.unit_code = Args(0)
Me.resource = Args(1)
the code is creating 1 record and then New_Rec inserts a second record in the DB. When the Form automatically Requeries after Me.RecordSource = sql, it sticks the first record (created by the Me.xxx = yyyy statements in ProcessOpenArgs into the DB too and then pulls both back out to the Form Recordset. That's where the double insert is coming from.
In order to correct it, I changed Me.unit_code and Me.resource to local subroutine variables l_unit_code and l_resource and used those instead in ProcessOpenArgs. That took care of this problem as well as a second problem that I had with records form one resource type bleeding into other resource types.
Thanks all for the assist!