0

I have a DataGridView with 5 columns all setup in the Form Designer. I also have a Stored Procedure that will SELECT the 5 columns and data required based on parameters. I want this data to be updateable; the user can add rows, delete rows and update existing rows.

I have so far managed to populate the DataGridView with data from a Stored Procedure in SQL Server, but unfortunately this is read-only and any changes made to the datatable post-load are only in the front end application.

I have done this many times in MS Access and I believe the DataSet needs to be 'bound' to the DataGridView rather than fed as a DataSource in order to make the DataGridView updateable directly to the data table in SQL Server but I am not sure on how to achieve this. I have looked online at very many sources and cannot seem to work out what it is I need to do in order to get this working.

Existing code:

    Public Sub loadDataGridView(taskID As Integer)
        Dim db As New SqlConnection(getCredentials)
        Dim sp As SqlCommand = db.CreateCommand
        Dim sa As SqlDataAdapter = New SqlDataAdapter(sp)
        Dim ds As DataSet = New DataSet()
        Dim x As Integer = 0

        db.Open()
        With sp
            .CommandText = "StoredProcedureName"
            .CommandType = CommandType.StoredProcedure
            .Parameters.Add(New SqlParameter("@taskID", taskID))
        End With

        sa.Fill(ds)

        DataGridView1.DataSource = ds.Tables(0)
        db.Close()
    End Sub
RazorKillBen
  • 561
  • 2
  • 20
  • Firstly, don't create a `DataSet` at all. It's pointless. If all you want is a single `DataTable` then just create a `DataTable`. You can pass a `DataTable` to `Fill` as well. – jmcilhinney Feb 05 '20 at 23:58
  • As for the issue, there is no issue. You save changes from a `DataTable` back to your database using the `Update` method of a suitably configured data adapter, same as always. It's up to you to create the `InsertCommand`, `UpdateCommand` and `DeleteCommand` as required. While I've never tried it, I would assume that you can include multiple SQL statements in each command if you need to affect multiple tables, separating them with semicolons. If you've already done this with Access then what's the problem? Maybe you used a command builder rather than creating your own commands. – jmcilhinney Feb 06 '20 at 00:02
  • [CRUD Operations using DataGridView, DataTable and TableAdapter](https://stackoverflow.com/a/36274706/3110834) – Reza Aghaei Feb 06 '20 at 06:08
  • *"I believe the DataSet needs to be 'bound' to the DataGridView rather than fed as a DataSource"*. That's exactly what binding is. – jmcilhinney Feb 06 '20 at 07:54
  • @RezaAghaei, for some reason I thought that the query was pulling from multiple tables but, rereading the question, there seems to be no evidence that that is the case. As such, a command builder may be a possibility, which I first thought was not the case. That said, I'm not sure whether one can be used with a stored procedure. Do you know for sure that it can? By the way, there are no table adapters in your answer. Table adapters and data adapters are different things, although related. – jmcilhinney Feb 06 '20 at 07:56
  • @jmcilhinney (CommandBuilder & Stored Procedure) → N/A, (CommandBuilder & Complex Query from multiple table) → N/A, In the linked post the focus is on `DataAdapter`, `TableAdapter` was a typo. Thanks for pointing that. – Reza Aghaei Feb 06 '20 at 08:08
  • @RezaAghaei, *"(CommandBuilder & Stored Procedure) → N/A"*. That's what I suspected. In that case, the manual creation of action commands is required, which I don't think is covered in your other answer. – jmcilhinney Feb 06 '20 at 08:16
  • @jmcilhinney No it doesn't apply and it's not a direct answer to question. But I advise to use a simple select query if possible. I'm not going to extend the comments to compare the solutions. But i'd like to put the comment here because it will makes the life much easier. I also guess the OP is not aware of designer features [like this](https://stackoverflow.com/a/37824444/3110834). – Reza Aghaei Feb 06 '20 at 08:22
  • Thanks @jmcilhinney - in Access, I used ADO to set the `RecordSource` of the form to a Stored Procedures `Recordset`. You then specify which table is the database table that will be updated and it automatically handles any DELETE, INSERT, UPDATE etc. Am I right in thinking that the use of `SqlCommandBuilder` works in the same way as you've alluded below? Bind the data source and the use of `SqlCommandBuilder` automatically handles any SQL transactions? – RazorKillBen Feb 06 '20 at 09:21
  • I am aware of this @RezaAghaei thank you - it just unfortunately wouldn't work in this scenario (multiple user and "environment" settings that will need to be paramaterised in runtime etc) – RazorKillBen Feb 06 '20 at 09:22
  • No problem, @RazorKillBen Parametrized → It will not stop you from using the mentioned approaches. Fully support parametrized queries. – Reza Aghaei Feb 06 '20 at 09:24
  • Sorry @RezaAghaei - just to expand, I meant to say that the Database login details are collected at Runtime - the user supplies the password and username for the SQL Server login. So I can't set the DataSource in the designer, as I'd have to supply either my details or an admin login etc. – RazorKillBen Feb 06 '20 at 09:27
  • And also it's not a stopper as well :) – Reza Aghaei Feb 06 '20 at 09:28
  • I'll take a look at that later on and post back an answer with any findings! Thanks again :) @RezaAghaei – RazorKillBen Feb 06 '20 at 10:27
  • This scenario is massively different to databinding in Access; the DGV is bound to a local cache of data (the dataset/table) and the local cache is not automatically directly connected to the database. You have to push/pull it yourself, either by crafting a dataadapter with the relevatn select/insert/update/delete commands, or (easier) by adding a typed dataset, adding a tableadapter, telling it you want to "use existing stored procedures" and then picking the S/I/U/D procedures. This latter way would be my preference, like using the Forms designer is preferable to coding a UI by hand – Caius Jard Feb 06 '20 at 18:55
  • The fact that the user provides login details at run time is no impediment to using a typed `DataSet` and setting up design-time binding. There are vast numbers of applications - most, in fact - that use different connection details in development and in production. You simply use a different connection string at different times and there's nothing to stop you editing an existing connection string or building a new one at run time. – jmcilhinney Feb 07 '20 at 01:42
  • That said, you don't have to create a typed `DataSet` if you don't want to. It would likely benefit you to learn a bit more about ADO.NET in general and in specific scenarios. I have posted some examples [here](http://www.vbforums.com/showthread.php?469872), including one using a data adapter with a command builder and one without. Note that the action commands usually match the query very closely but they don't have to, i.e. the changes don't have to be saved to the same columns, tables or even database as the data is retrieved from. – jmcilhinney Feb 07 '20 at 01:45
  • Thanks @jmcilhinney - there appears to be so many different ways to achieve this and I'm afraid I'm getting a little overwhelmed with all of the different links and scenarios. What specific resource could you point me towards or code snippet example or rework of my code above would help the most in achieving what I'm after? Essentially an event will fire the code that will supply the parameters and retrieve the data, and I want that data to be able to be edited/added/deleted in the control itself in "realtime". I'm familiar with ADO but ADO Access and .NET are wildly different. – RazorKillBen Feb 07 '20 at 15:35
  • There's really only one way to do it. Create a `DataTable`, create a data adapter, call `Fill` on the data adapter to populate the `DataTable`, bind the `DataTable` to the `DataGridView`, edit as required, call `Update` on the data adapter to save the changes from the `DataTable` to the database. There are variations at various stages but you can ignore them. For instance, you could create a `DataSet` to contain the `DataTable` but that would be pointless. You also can't use a command builder in this specific case so ignore that. Have you looked at my relevant example? – jmcilhinney Feb 07 '20 at 16:47

1 Answers1

1

For anyone in future looking for how to get this done, I've attached the code I used to achieve this. This is based primarily on jmcilhinney examples posted here.

Module DataGridViewRetrieve
    Public db As New SqlConnection(lgnGetCredentials)
    Public sp As SqlCommand = db.CreateCommand
    Public sa As SqlDataAdapter = New SqlDataAdapter(sp)
    Public dt As DataTable = New DataTable()

    Public Sub initialiseDataAdapter()
        Dim dRecords As SqlCommand = db.CreateCommand
        Dim uRecords As SqlCommand = db.CreateCommand
        Dim iRecords As SqlCommand = db.CreateCommand

        ' # Initialise delete records Stored Procedure
        With dRecords
            .CommandText = "<NAME OF STORED PROCEDURE TO DELETE>"
            .CommandType = CommandType.StoredProcedure
            .Parameters.Clear()
            .Parameters.Add("@ID", SqlDbType.BigInt, 10, "<NAME OF ID COLUMN IN DataGridView>")
        End With

        ' # Initialise update records Stored Procedure
        ' Note - you'll need to add your parameters for each column in the below example
        With uRecords
            .CommandText = "<NAME OF STORED PROCEDURE TO UPDATE"
            .CommandType = CommandType.StoredProcedure
            .Parameters.Clear()
            .Parameters.Add("@dgID", SqlDbType.BigInt, 10, "<NAME OF ID COLUMN IN DataGridView>")
            .Parameters.Add("@Title", SqlDbType.Bit, 1, "<NAME OF COLUMN IN DataGridView>")
        End With

        ' # Initialise insert records Stored Procedure
        With iRecords
            .CommandText = "pl.x53_loadLifeListGoals_iRecords"
            .CommandType = CommandType.StoredProcedure
            .Parameters.Clear()
            .Parameters.Add("@dgID", SqlDbType.BigInt, 10, "<NAME OF ID COLUMN IN DataGridView>")
            .Parameters.Add("@Title", SqlDbType.Bit, 1, "<NAME OF COLUMN IN DataGridView>")
        End With

        ' # Set data adapter commands
        sa.DeleteCommand = dRecords
        sa.UpdateCommand = uRecords
        sa.InsertCommand = iRecords

        ' # Set missing schema action key
        sa.MissingSchemaAction = MissingSchemaAction.AddWithKey
    End Sub

    Public Sub loadData(TitleName As String)
        ' # Clears data
        FormName.DataGridViewName.DataSource = Null

        ' # Calls the initialise routine
        Call initialiseDataAdapter()

        ' # Initialise the SELECT Stored Procedure
        With sp
            .CommandText = "<NAME OF STORED PROCEDURE TO SELECT>"
            .CommandType = CommandType.StoredProcedure
            .Parameters.Clear()
            .Parameters.Add(New SqlParameter("@TitleName", TitleName))
        End With

        ' # Load data
        sa.Fill(dt)
        FormName.BindingSourceName.DataSource = dt
        FormName.DataGridViewName.DataSource = FormName.BindingSourceName
    End Sub

    Public Sub saveData()
        sa.Update(FormName.BindingSourceName.DataSource)
    End Sub
End Module

'// ATTACH YOUR LOAD CALL TO YOUR EVENT HANDLER - IN THIS INSTANCE, FORM LOAD
Private Sub Form1_Load(sender as object, e as EventArgs) Handles Me.Load
    Call loadData(Me.txtTitle.Text)
End Sub

Private Sub ButtonSave_Click(sender as object, e as EventArgs) Handles Me.ButtonSave
    Call saveData()
End Sub
RazorKillBen
  • 561
  • 2
  • 20