Data Manipulation from SQL Server source through controls and LINQ
Introduction
In this article we'll see how to make a SQL Server resident table available to our application. We'll use Wizards to connect to the instance, create DataSet and TableAdapter classes. We'll see how SQL Server resident data could be exposed through controls, with some examples, and how they can be manipulated code-side, to realize updates towards the underlying database. Last, we'll see some LINQ references, to be applied to the present context.
Few steps to Dataset
A maybe simplistic subtitle, but not too far from truth: we'll see here how to connect to our database, using DataSource Configuration Wizard, which will create the objects we'll use in our examples. This will be a very simple operation. Let's suppose we have a SQL Server instance, on which resides the TECHNET database. It contains a table named People, composed by an autoincremental Id, and two Varchar fields, Name and City.
From the Visual Studio menĂ¹, click on Project, then Add New Data Source
In the window that will appear, we must select the type of object from which our data will be read. In our case, Database.
We'll choose now the model type to make read data available through our application. We'll work with a DataSet.
Now we will be asked for connection parameters. Let's click on New Connection, feeding the Wizard with our SQL Server instance parameters. The connection string will be saved in App.config file, for its later modification in case of migration to a different operative context, or instance changing, or the like.
Click Next. The Wizard will show the objects contained in our database (TECHNET, in our case). Select People table, and click Next.
The Wizard will ask for a DataSet name. At the end of the procedure, we'll see our DataSet among the files belonging to the solution.
Double-clicking our DataSet will open the designer, through which we could see how the Wizard had created a DataTable-type object, named People, with the same fields read from the source table, and a TableAdapter-type object, with some methods exposed, such as populating, updating, and deleting functions, to be executed through T-SQL, which has been automatically generated by the table schema. Here we can rename wizard-created objects, columns, column property changes, and modify queries.
Binding to DataGridView: data presentation and modification
Let's suppose we are in Windows Forms environment: we have a Form, on the top of which we'll create a DataGridView. In that control we want to present our table's contents, being able to modify it if some changes occurs by the user.
The following code will realize those functionalities. We'll start by declaring two new references, the first at the DataSet viewed above, and the second one to its TableAdapter, as in the DataSet schema.
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
DataGridView1.DataSource = myDS.People
End Sub
Private Sub DataGridView1_CellValidated(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValidated
myTB.Update(myDS.People)
End Sub
End Class
On the Form's Load Event, we want to populate our DataSet. To this end, we'll use the TableAdapter Fill method, passing to it the People DataTable as argument. The DataSource will then be set as the DataGridView DataSource. This will generate in the grid the columns which reference the table's fields, showing their contents. We can now modify the presented data.
For the data to be saved, though, this will not suffice: we must tell the TableAdapter to execute an update on the underlying data. We'll use the method Update for this. In the snippet above, the update procedure will take place after the cell contents are validated, i.e. when the data the cell contains has been flagged has correct. If we want to execute this operation when the entire row will be validated, we can use the RowValidated event.
Running our example, and doing some tests inserting data, modifying them, and deleting rows, we'll see that every modification will automatically take place in the original table also.
Binding to ComboBox on single column
Some controls, while possessing the same properties which allows data binding, aren't made for show the entire range of results. A ComboBox, for example, cannot show all the columns from a table, but only one of them. On a ComboBox, we can set the DataSource in the same way we've done for the DataGridView, but specifying, in the DisplayMember property, the data member which will be exposed.
An example may be as follows:
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
ComboBox1.DataSource = myDS.People
ComboBox1.DisplayMember = myDS.People.NameColumn.ColumnName
End Sub
End Class
We'll executed the same initialization on DataSet and TableAdapter, proceeding in populating our DataSet as in the previous example. The Datasource property of ComboBox control is set the same way we've done for the DataGridView, and then we set the DisplayMember property using the string which defines the column name (ColumnName), belonging to the column which exposed the Name field (NameColumn) from the People table, resident in our DataSet. This way, running our program, we'll see that the ComboBox elements will be represented by the column Name.
LINQ references
The major characterics of Language-Integrated Queries are the univocity of their instructions set, independent from the referenced data source, and the powerful filter functions, to select data in a very concise and efficient way. After initializing the references to our DataSet, LINQ syntax could be used with profit in cases as those we saw.
Stepping back to the DataGridView example, and supposing we want to extract and view only those records in which the Name fields starts for "John", we could write a snippet like this:
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
DataGridView1.DataSource = (From pr As TECHNETDataSet.PeopleRow
In myDS.People
Where pr.Name Like "John*").ToList
End Sub
Private Sub DataGridView1_CellValidated(sender As Object, e As DataGridViewCellEventArgs) Handles DataGridView1.CellValidated
myTB.Update(myDS.People)
End Sub
End Class
Please note that DataGridView DataSource isn't directly set to a DataTable, but to an extraction of PeopleRow elements, read on the base of a Where clause which search for the string "John*" (being the asterisk the wildcard character) in the Name field. Working on a filtered list don't allow us, in this case, to add new items. We can though modify those shown, and with the call at the Update method, our changes will be saved.
In the same way, speaking about our ComboBox and wishing to recreate the same conditions as above, an example could be:
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
ComboBox1.DataSource = (From pr As TECHNETDataSet.PeopleRow
In myDS.People
Select pr.Name).ToList
End Sub
End Class
In this case, knowing we can bind a simple list of strings, we could bypass the matters inherents to DisplayMember adding a further selection in our query. In the last example, we extract all the rows from the table People, further extracting from them the element Name. Obviously, to continue using DisplayMember property, we could simply write:
ComboBox1.DataSource = (From pr As TECHNETDataSet.PeopleRow
In myDS.People).ToList
ComboBox1.DisplayMember = "Name"
Or, avoiding the extractions of subset of the first selection, demanding to DisplayMember property the task of showing a specific column.
Data Modifications through LINQ
Let's suppose we wish to perform an update without any user interaction. Think about a field which, with predetermined conditions satisfied, must be automatically modified. In our example, we want to change in "Turin" every city for any record in our table. Through LINQ, and the methods we saw until now, we can do it without worrying about the connection layer:
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
Dim results As IEnumerable(Of TECHNETDataSet.PeopleRow) = From p As TECHNETDataSet.PeopleRow In myDS.People
For Each result In results
result.City = "Turin"
Next
myTB.Update(myDS.People)
End Sub
End Class
We have populated out DataSet, extracting from our table an enumeration of PeopleRows. Then, with a For/Each loop, we have changed the field value, and - calling on TableAdapter's Update - we have consolidated our data. Running the example, and checking the corresponding property on SQL Server side, we can notice the data were successfully modified.
A more LINQ-like method to write the above example could be the following, in which we modify the cities in "Milan":
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
Dim results As IEnumerable(Of TECHNETDataSet.PeopleRow) = (From p As TECHNETDataSet.PeopleRow In myDS.People)
results.ToList.ForEach(Sub(x As TECHNETDataSet.PeopleRow) x.City = "Milan")
myTB.Update(myDS.People)
End Sub
End Class
Last, let's suppose we desire to modify the City field for a particular record. We want, for example, extract the single record in which Name contains the string "John" to modify its City in "New York".
Public Class Form1
Dim myDS As New TECHNETDataSet
Dim myTB As New TECHNETDataSetTableAdapters.PeopleTableAdapter
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
myTB.Fill(myDS.People)
Dim p As TECHNETDataSet.PeopleRow = myDS.People.Single(Function(x As TECHNETDataSet.PeopleRow) x.Name.Contains("John") <> 0)
p.City = "New York"
myTB.Update(myDS.People)
End Sub
End Class
In this case, using the Single function, we've extracted a record referred to the condition imposed in the internal function, namely the string "John" to be present in the column Name. After that, referencing the result variable, it will be sufficient to modify the desired property, calling the Table Adapter's Update function.
Bibliography
Other Languages
The present article is available in the following localizations: