I seem to be having troubles with my stored procedure call here. I'm trying to get all my information from a stored procedure, which returns a single column into a datatable. I've followed along for most solutions here and can't seem to figure out why it is not working. My data table has a primary key constraint on it, due to having multiple values in it. But, from what I've read, this isn't necessary as the Fill on the SqlDataAdapter will just merge those changes if there is any?
Here is my code so far:
Dim dtValues As New DataTable()
Dim dtCol As New DataColumn()
dtCol.ColumnName = "ReferenceID"
dtCol.DataType = GetType(SqlInt32)
dtCol.AllowDBNull = False
dtValues.Columns.Add(dtCol)
dtValues.PrimaryKey = {dtCol}
Public Shared Sub ExecStoredProc(ByVal ProcName As String, ByRef connection As SqlConnection, ByRef dtValues As DataTable, ByVal ParamArray args() As Object)
Dim sqlCommand As New SqlCommand()
For i = 0 To args.Length - 1 Step 2
sqlCommand.Parameters.AddWithValue(CStr(args(i)), args(i + 1))
Next
sqlCommand.CommandText = ProcName
sqlCommand.CommandType = CommandType.StoredProcedure
sqlCommand.Connection = connection
Dim sqlAdp As New SqlDataAdapter(sqlCommand)
' Fill the table to mess around with.
sqlAdp.Fill(dtValues)
End Sub
However, the error that I get is:
Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.
My stored procedure I'm calling is a simple
Select ID
From TableName
Where Reference = #Reference
with no conditions really, and I've tested and it returns about 19 unique records.
Any help would be appreciated, thanks!
You are defining a not null primary key data column in your table called "ReferenceID":
Dim dtCol As New DataColumn()
dtCol.ColumnName = "ReferenceID"
dtCol.DataType = GetType(SqlInt32)
dtCol.AllowDBNull = False
However, your sql query is not selecting a 1ReferenceID1 column, it is selecting a column named ID:
Select ID
From TableName
Where Reference = #Reference
Therefore, when you go to fill your table you get the error:
Failed to enable constraints. One or more rows contain values violating non-null, unique, or foreign-key constraints.
Because the not-null, unique constraint you set on "ReferenceID" is being violated.
Change your query to:
Select ID as [ReferenceId]
From TableName
Where Reference = #Reference
And you should be set (assuming that that ID is unique and not null)
Related
SETUP:
I am using a SQL Server database linked to a MS Access file so I can use Access forms.
I am using unbound forms with custom queries since I am dealing with several tables (If there is a more efficient way I am all for it).
In SQL Server I have setup a database role with permissions for the table, I have double checked that I have allowed the role to update the table.
ISSUE:
Whenever I use my update query in Access using a QueryDef (shown below) it runs successfully, but does not actually update the table.
DETAILS:
I was having an issue with my insert query as well, however that was only because I had not updated the query with new columns I had added in the database. I also made sure that I did the same for the update query.
Additionally I know that the update query works since I am able to update the entry directly from SSMS.
Since this seemed similar to another access/sql-server question I had, Found Here. I made sure to try the solution for that one, refreshing the table link. However, it made no difference.
CODE:
Query:
UPDATE con_people
SET people_first_name = #firstName,
people_last_name = #lastName,
people_title = #title,
people_group = #group,
people_email = #email,
people_shift = #shift,
people_hiredate = #hireDate,
people_location = #location,
people_reportsTo = #reportsTo,
people_versionCount = people_versionCount + 1,
people_datelastupdated = #dateUpdated,
people_isActive = #isActive
WHERE people_employeeID = #empID;
QueryDef:
Public Function UpdatePeople(firstName As String, _
lastName As String, _
title As Integer, _
group As Integer, _
Email As Variant, _
isActive As Boolean, _
Shift As Integer, _
Location As Integer, _
HireDate As Variant, _
ReportsTo As Variant, _
employeeID As Integer)
OtherFunctions.Initialize
Dim QDF As DAO.QueryDef
If FindQuery("UpdatePeople") = True Then OtherFunctions.dbs.QueryDefs.Delete "UpdatePeople"
Set QDF = OtherFunctions.dbs.CreateQueryDef("UpdatePeople", SQLUpdatePeople)
QDF.Parameters("#firstName").Value = firstName
QDF.Parameters("#lastName").Value = lastName
QDF.Parameters("#title").Value = title
QDF.Parameters("#group").Value = group
QDF.Parameters("#email").Value = Email
QDF.Parameters("#isActive").Value = isActive
QDF.Parameters("#empID").Value = employeeID
QDF.Parameters("#shift").Value = Shift
QDF.Parameters("#hireDate").Value = HireDate
QDF.Parameters("#location").Value = Location
QDF.Parameters("#reportsTo").Value = ReportsTo
QDF.Parameters("#dateUpdated").Value = ConvertTimeUnix.ConvertDateToUnix(Now())
QDF.Execute
If FindQuery("UpdatePeople") = True Then OtherFunctions.dbs.QueryDefs.Delete "UpdatePeople"
End Function
Any help is appreciated,
Thanks.
Thanks to a comment by #Andre I was able to find the source of the issue.
I was using the wrong data type when I was updating the entry. The SQL-Server was expecting an INT (for a foreign key) when I was providing it a boolean (SQL-Server BIT).
Details About the solution:
Links provided by Andre: Error Object - Data Access Object and Determine real cause of ODBC failure (error 3146) with ms-access?.
For more information on the DAO.Error object please reference those.
Here is an example of how it is used:
Dim myerror As DAO.Error
For Each myerror In DBEngine.Errors
With myerror
If .Number <> 3146 Then 'prevents the system from returning the basic error.
MsgBox "Error #:" & .Number & ", Description: " & .Description & ", Source: " & .Source
End If
End With
Next
Once I ran it, this was returned, allowing me to find the root cause:
Error #:547,
Description: [Microsoft][ODBC SQL Server Driver][SQL Server]The UPDATE statement conflicted with the FOREIGN KEY constraint "FK_people_isActive". The conflict occurred in database "continuousimprovement", table "dbo.con_isactive", column 'isActive_id'.,
Source: ODBC.QueryDef
Again, Thank you Andre.
I am attempting to make a small application allowing users to read the content of a table describing the inventory of a warehouse, search depending on 2 rows indicating which warehouse the item resides in and by it's assigned barcode which i already managed to get to work by using a binding source, and a datagrid view, updating the view trough a query taking the barcode and location as strings from two boxes.
The second part i would need for this application to suit my basic objective would be to have a way to add new lines and store them into the original table on the database so users could add the new items independently from the warehouses directly.
So far i have encountered 2 issues: i need a primary key that would represent a sequential ID but i do not know how to produce a sequentially incrementing ID, i manage to get the first addition ID by using a top 1 order by desc query combination but the data does not get updated after adding the new line, producing an error since it tries to add another line with the same value for the primary key.
The second issue i am encountering is: the gridview gets altered accordingly to the data i input in the textboxes i set up to gather the various values for the table but the table on the database itself is not showing any change, keeping only the test data i inputted at it's creation.
Public Class AddItems
Private Sub AddItems_Load(sender As Object, e As EventArgs) Handles MyBase.Load
'TODO: This line of code loads data into the 'MagazzinoDataSet.LastUsedID' table. You can move, or remove it, as needed.
Me.LastUsedIDTableAdapter.LastUsedID(Me.MagazzinoDataSet.LastUsedID)
'TODO: This line of code loads data into the 'MagazzinoDataSet.Stock' table. You can move, or remove it, as needed.
Me.StockTableAdapter.Fill(Me.MagazzinoDataSet.Stock)
'TODO: This line of code loads data into the 'MagazzinoDataSet.AddWarehouseList' table. You can move, or remove it, as needed.
Me.AddWarehouseListTableAdapter.AddWarehouseList(Me.MagazzinoDataSet.AddWarehouseList)
'TODO: This line of code loads data into the 'MagazzinoDataSet.WarehouseList' table. You can move, or remove it, as needed.
Me.WarehouseListTableAdapter.Fill(Me.MagazzinoDataSet.WarehouseList)
'TODO: This line of code loads data into the 'MagazzinoDataSet.Stock' table. You can move, or remove it, as needed.
Me.StockTableAdapter.Fill(Me.MagazzinoDataSet.Stock)
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim R As DataRow = MagazzinoDataSet.Tables("Stock").NewRow()
R("Supplier") = Supplier.Text
R("Producer_code") = ProducerCode.Text
R("Barcode") = Barcode.Text
R("Comp_name") = ComponentName.Text
R("Warehouse") = Warehouse.Text
R("Internal_Code") = InternalCode.Text
R("Description_IT") = ITDescr.Text
R("Description_EN") = ENDescr.Text
'R("ID") = NextID.SelectedValue <- this would be an hidden uneditable multibox containing the product of the query finding the next value to be inserted in the table (basically last ID + 1, nothing fancy)"ID" would be the primary key of this table
R("Quantity") = "0"
MagazzinoDataSet.Tables("Stock").Rows.Add(R)
DataGridView1.DataSource = MagazzinoDataSet.Stock
End Sub
End Class
To sum it up:
How would i go about updating the database table to include the new line?
Is there a smart way to find the last value, incrementing it by 1 to have the next value and updating it when inserting a new line, so as to not end up with 2 lines with the same value for primary key, generating an error?
To set an incremental ID in the Db, assuming you have access to SQL Server Management Studio, in Design of the table, for the ID column, in Column Properties, scroll down to Identity Specification and set (is Identity) to Yes.
To add a new row, I use this code:
Using NotesDS As New DataSet
Using NotesDA As New SqlDataAdapter With {.SelectCommand = New SqlCommand With {.Connection = SQLDBConnection, .CommandText = "SELECT * FROM Notes WHERE ID = " & ID}}
NotesDA.Fill(NotesDS, "Notes")
Using NotesDV As New DataView(NotesDS.Tables("Notes"))
Using NoteBuilder As New SqlCommandBuilder(NotesDA) With {.QuotePrefix = "[", .QuoteSuffix = "]"}
If NotesDV.Count = 0 Then
Dim NoteDRV As DataRowView = NotesDV.AddNew
NoteDRV.Item("UserName") = UserName
NoteDRV.Item("Note") = Note
NoteDRV.Item("NoteDate") = NoteDate
NoteDRV.Item("CompanyCode") = CompanyCode
NoteDRV.EndEdit()
NotesDA.UpdateCommand = NoteBuilder.GetUpdateCommand
NotesDA.Update(NotesDS, "Notes")
End If
End Using
End Using
End Using
End Using
Obviously, amend to make appropriate for your table and column names.
If you need to retrieve the ID for display, you can add a handler to the Update like:
Public Sub GenericOnRowUpdated(sender As Object, e As System.Data.SqlClient.SqlRowUpdatedEventArgs)
Dim newID As Integer = 0
Dim idCMD As SqlClient.SqlCommand = New SqlClient.SqlCommand("SELECT ##IDENTITY", SQLDBConnection)
If e.StatementType = StatementType.Insert Then
newID = CInt(idCMD.ExecuteScalar())
e.Row("ID") = newID
End If
End Sub
and use like:
AddHandler NotesDA.RowUpdated, New SqlRowUpdatedEventHandler(AddressOf GenericOnRowUpdated)
NotesDA.Update(NotesDS, "Notes")
NewID = NoteDRV.Item("ID")
EDIT
First Example amended and explained below:
'Declare you connection to the SQL dB. Connection String looks like "Data Source=192.168.71.10\dBName; Initial Catalog=dBName; User ID=USER; Password='PASSWORD!';MultipleActiveResultSets=true" - You may well already have an open connection, and can use that instead. Not sure what your
StockBindingSource is...
Dim oConn As New SqlConnection("CONNECTION STRING")
'Open the connection
oConn.Open()
'Declare Your DataAdapter and initialise using your connection
Dim DA As New SqlDataAdapter With {.SelectCommand = New SqlCommand With {.Connection = oConn, .CommandText = "SELECT * FROM Stock WHERE ID=0"}}
'Declare you DataSet
Dim DS As New DataSet
'Fill Your DataSet with the Stock table from your DataAdapter
DA.Fill(DS, "Stock")
'Declare a DataView for easy use (really the same as using DS.Tables("Stock").DefaultView)
Dim DV As New DataView(DS.Tables("Stock"))
'Declare a CommandBuilder and initialise with your DataAdapter. This will now watch for changes made to your data and build the appropriate SQL UPDATE/INSERT/DELETE command. the "[" and "]" are in case any column names use reserved words
Dim Builder As New SqlCommandBuilder(DA) With {.QuotePrefix = "[", .QuoteSuffix = "]"}
'Decalre a DataRowView for data population, based on your DataView table structure
Dim R As DataRowView = DV.AddNew()
'Populate the fileds with your Form data
R("Supplier") = Supplier.Text
R("Producer_code") = ProducerCode.Text
R("Barcode") = Barcode.Text
R("Comp_name") = ComponentName.Text
R("Warehouse") = Warehouse.Text
R("Internal_Code") = InternalCode.Text
R("Description_IT") = ITDescr.Text
R("Description_EN") = ENDescr.Text
R("Quantity") = "0"
'Notify that the edit has finished
R.EndEdit()
'Get the SQL command from the CommandBuilder
DA.UpdateCommand = Builder.GetUpdateCommand()
'Execute the update (in this case it will be an INSERT)
DA.Update(DS, "Stock")
please advise; what is wrong in this code ?
giving below error ... Column 'xxxx' does not belong to underlying table ''.
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Not IsPostBack Then
Dim connStr As String = ConfigurationManager.ConnectionStrings("FBB-DSL-DBConnectionString").ConnectionString
Dim tablex As New DataTable()
Using conn As New SqlConnection(connStr)
Using cmd As New SqlCommand("sp_columns", conn)
cmd.CommandType = CommandType.StoredProcedure
cmd.Parameters.AddWithValue("#table_name", "tbl_EmpRecords")
' get the adapter object and attach the command object to it
Using ad As New SqlDataAdapter(cmd)
' fire Fill method to fetch the data and fill into DataTable
ad.Fill(tablex)
End Using
End Using
End Using
'Creating DataView
Dim view As New DataView(tablex)
Dim dt As DataTable = view.ToTable(False, "COLUMN_NAME" )
CheckBoxList1.DataSource = dt
CheckBoxList1.DataBind()
End If
End Sub
The system defined stored procedure sp_columns produces a datatable where each row contains the schema details of the columns in the tbl_EmpRecords.
This means that the columns present in this tablex are the following:
TABLE_QUALIFIER,
TABLE_OWNER,
TABLE_NAME,
COLUMN_NAME,
DATA_TYPE,
TYPE_NAME,
PRECISION,
LENGTH,
SCALE,
RADIX,
NULLABLE,
SQL_DATA_TYPE,
SQL_DATETIME_SUB,
CHAR_OCTET_LENGTH,
ORDINAL_POSITION,
IS_NULLABLE,
SS_DATA_TYPE
See the MSDN docs about sp_columns for the meaning of each column.
So, there is no column named XXXXXX and this is exactly what the error tells you. If you really want to create a new datatable with only one column then your XXXXX field should be one of the above names.
' This will return a new table with only one column named COLUMN_NAME'
' The table will contain all the column names of the table tbl_EmpRecords'
Dim dt As DataTable = view.ToTable(False, "COLUMN_NAME")
Instead, if you are trying to sort the tbl_EmpRecords using someone of the fields defined in that table (IE, empName, HireDate etc....) then you have to use a different approach for your query.
....
Using conn As New SqlConnection(connStr)
Using cmd As New SqlCommand("select * from tbl_EmpRecords", conn)
Using ad As New SqlDataAdapter(cmd)
ad.Fill(tablex)
End Using
End Using
End Using
....
then you can proceed with the DataView code.
However, it is always better to query the database to retrieve only the data you are interested in.
So, if you need only the field XXXXX then just use the query
select XXXXX from tbl_EmpRecords
and probably you can ask the database to sort that record for you with
select XXXXX from tbl_EmpRecords ORDER BY XXXXX
removing all the DataView code.
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)
)
So I have the below VB that creates an access file in the default workspace, creates a table, create some fields in that table...just need to know the syntax for setting the first data type/field to autonumber...GUID, Counter, etc will not work as in Access SQL
' error handling usually goes here
dim ws as workspace
dim dbExample as database
dim tblMain as TableDef
dim fldMain as Field
dim idxMain as Index
set ws = workspace(0)
set dbExample = ws.CreateDatabase('string file path')
set tblMain = dbExample.CreateTableDef("tblMain")
set fldMain = tblMain.CreateField("ID", 'right here I do not know what to substitute for dbInteger to get the autonumber type to work )
tblMain.Fields.Append fldMain
etc to create other fields and indexes
so in this line:
set fldMain = tblMain.CreateField("ID", dbInteger)
i need to replace the dbInteger with something that VB reconizes as the autonumber property.
i have tried GUID, Counter, Autonumber, AutoIncrement....unfortunately none of these work
anyone know the syntax I am missing here?
Thanks,
Justin
See Creating an AutoNumber field from code at The access Web.
Here are the key lines from that linked page:
Set db = Application.CurrentDb
Set tdf = db.TableDefs(strTableName)
' First create a field with datatype = Long Integer '
Set fld = tdf.CreateField(strFieldName, dbLong)
With fld
' Appending dbAutoIncrField to Attributes '
' tells Jet that its an Autonumber field '
.Attributes = .Attributes Or dbAutoIncrField
End With
With tdf.Fields
.Append fld
.Refresh
End With
BTW, what you're doing is not DDL.