2

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`
Alex Spencer
  • 131
  • 6
  • What error you get on `ALTER TABLE...`? – gofr1 Jun 09 '16 at 13:12
  • Thats the thing. I don't get an error during this. The issue is more to do with the fact that using the SQL query running when the add or delete functions run, when at this time i only want it to display in the data grid view. I only want the columns to be added or removed from the SQL server database once i hit the "Save" button (which runs the save function, obviously) – Alex Spencer Jun 09 '16 at 13:13
  • Knew i forgot to mention something - I'm using oleDB for my connection to SQL, this command apparently isnt a member of oleDB. – Alex Spencer Jun 09 '16 at 13:20
  • It should work. Take a look to answer here https://social.technet.microsoft.com/Forums/sqlserver/en-US/664186c0-9604-4358-b8f3-59d08e824443/oledbcommand-alter-table-sql-syntax-error?forum=sqldataaccess – gofr1 Jun 09 '16 at 13:24
  • 1
    This screams of denormalized tables. You should not have a column in your table for each trainer. This is not a good design at all. – Sean Lange Jun 09 '16 at 13:24
  • @SeanLange The problem is the table I'm having to make this app around basically has dates running down the rows (i.e. 1/1/2018, 8/1/2018 etc.) This is because the app is a scheduling app and is based on an excel spreadsheet. I'm basically trying to make it easier to use, but since trainers start and leave, and time passes on, we need to be able to add and remove rows AND columns. However, we need it to do this to the DGV first (so you can preview it) and only uploading it to the server when the save button is pressed, so that you dont accidentally delete a trainer's column prematurely! – Alex Spencer Jun 09 '16 at 13:30
  • I would still advise you to normalize this to use a better design in the database. Adding and removing columns like this is problematic in that it is very difficult to code against and forces you to use dynamic sql which is highly likely to be vulnerable to sql injection. The biggest issue with denormalized data like this is that it is really hard to work with. – Sean Lange Jun 09 '16 at 13:57
  • @seanlange Yeah, I've picked that up actually '^^ But, if I took the trainers off of the columns and made them the rows instead, where would the dates go? The dates would have to become the columns instead wouldnt they? In which case the problem still stands, as new weeks would be added over time, and weeks passed would have to be removed. :/ see where I come from with this? – Alex Spencer Jun 09 '16 at 14:00
  • No you need to normalize this into more than 1 table. You would have a table for trainers and another table for trainer events. – Sean Lange Jun 09 '16 at 14:16
  • @SeanLange Ah, I get you now. There is an issue with this. Each of the trainers does a different thing each week. So the first trainer on week X is teaching course Y, while on week X the second trainer is teaching course Z. Trainer three on the other hand has the week off and has his/her cell for the week empty. Therefore the table needs to list the dates down one side, the trainers down the other with the courses each trainer is doing on a given week in the cells in the table. – Alex Spencer Jun 09 '16 at 14:20
  • Why not just use excel for that? – gofr1 Jun 09 '16 at 14:44
  • @gofr1 we already do, but its a little messy. I was tasked, along with the other software developer apprentice at the business, to try and develop a few apps for the company to try and streamline the process a bit for non-excel users. We already organized registers, marking, and results to be a bit easier, but the Trainer Schedule is proving to be much harder. For the most part, we're finished with it, but theres issues here and there like the one we have now. Would you want to see a screenshot of the app to get an idea of what we have set up with it better than i can explain it? – Alex Spencer Jun 09 '16 at 14:49
  • Screenshots will be welcomed, I think. Please, post them. – gofr1 Jun 09 '16 at 14:56
  • @gofr1 sure thing. I'll get a few sorted out and see if i can even figure out how to upload them to here. Whoo, I'm utterly clueless sometimes. But seriously, I'll go and get some sorted out for you. – Alex Spencer Jun 09 '16 at 14:57
  • This is a very common thing in the database world. You have a table for trainers. Another table for courses. And a third table for TrainerCourses. This last table contains the TrainerID, CourseID and the scheduling information. – Sean Lange Jun 09 '16 at 15:11
  • I "sorta" understand you here (would use foreign keys to make the IDs relevant to eachother in different tables) but I feel like I don't understand past that. – Alex Spencer Jun 09 '16 at 15:15
  • @AlexSpencer Hi! Thanks for screenshots! To say honestly - it looks like precisely as I imagined :) Sean Lange give you a one good answer on this question - normalized DB. – gofr1 Jun 10 '16 at 06:13
  • @gofr1 ok then. If you insist. Honestly I'm only an apprentice, the SQL stuff is a bit beyond me as I've h ad rather limited training or experience with it. The whole normalized database thing, create a few more tables and link them together with foreign keys right...? is that what he's trying to get across? I'm still trying to figure it out. Sorry if i seem a little slow but, i kinda am to be fair. It takes a while for new concepts to click with me – Alex Spencer Jun 10 '16 at 08:17
  • @AlexSpencer Yes, FK (Foreign Keys). Yes, it is relational DB this how it works here. There are situations when normalizing is necessary and you can not go away from this. I have some experience in normalizing such solutions after many many years of using this DB by some clients... And there are duplicates (tons of!), keys violations etc. I suggest you to use normalized DB from very begging. – gofr1 Jun 10 '16 at 08:30
  • @gofr1 Okay. It is a bit frustrating as I've built the program around this idea of the single table but if it is this important I can make the change. It sucks though but ehh whatever. Wish I'd known this 3 months ago. Soo, perhaps have one table with all the trainer's names and an ID next to it, then another doing the same with the courses... then use the IDs in the third table for the schedule. Cool but, how does this solve the "Adding/removing columns problem" that I have? >.< I still wouldnt be able to do that would I??? Or am i STILL misunderstanding? – Alex Spencer Jun 10 '16 at 08:34
  • @AlexSpencer For example: new trainer - user must press button "Add trainer" - enters required data and press OK. You add column to `trainers` table, refresh the grid where new column appears. When entering data on some date about trainers course, you need to reed `trainersId` and `date` and offer to user to choose or enter some course or whatever he needs. When user enters something and press OK you add a column to `TrainerCourses` table and refresh grid. – gofr1 Jun 10 '16 at 08:42

1 Answers1

1

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)
)
Sean Lange
  • 33,028
  • 3
  • 25
  • 40
  • I'm not sure I explained what I'm after very well witht his... I put up two screenshots that kinda show whats going on with it in case you wish to have a look. Thanks for trying though, but I dont get why having two extra tables would help here. And, if i really did need to do this, I could do it from within SQL using queries, Unless I misunderstood what you're trying to put across >.> – Alex Spencer Jun 09 '16 at 15:52
  • 1
    I normalized this because what you have a disaster from a data standpoint. You are creating an application that is nothing more than an excel spreadsheet trying to represent normalized data in a two dimensional table. What you are struggling with is the reason relational databases were created by Dr. Codd. – Sean Lange Jun 09 '16 at 16:20
  • @AlexSpencer you can make a PIVOT on this tables to show it in your app. When someone wants to change some field in result grid - you can get `LastName` of trainer and `[date]` of course and put them into table `TrainerCourses`. – gofr1 Jun 10 '16 at 06:25
  • 1
    @gofr1 Actually, now I think about it, All I needed was to be able to add the new column to the datagridview when the add button was pressed (or delete it when the delete button was pressed) and then only update the datasource, the SQL table, when the save button was pressed. I thought this'd be fairly simple but, apparently not. – Alex Spencer Jun 10 '16 at 08:27