ADO.NET: DataView

DataTables are a great solution for caching data in memory.  In fact, out of the box, there is no .NET class that is more suited for the task than a DataTable.

 

But, a DataTable, in and of itself, is not flexible enough to support sorting, searching and filtering of data.  The only 2 methods that a DataTable has that can be used for searching are:  the Find method, which lets you locate a row based on its primary key value; and the other is the Select method, which returns an array of rows based on filter criteria.  When you want more flexibility for performing sorting, searching, and filtering, you’ll want to use a DataView.

 

A DataView was essentially designed to be a view of the data that is stored in a DataTable.  It is not a separate cache of data, so it does not maintain its own copy of data.  But, it provides a more flexible way of working with the data that is stored in a DataTable, and in many times is actually used in place of a DataTable.

 

Every DataTable has a Default DataView.  If you didn’t know that, consider this:

 

'Create a new DataTable
Dim table As New DataTable()

'Get a reference to the DataTable's
'  Default DataView
Dim view As DataView = table.DefaultView

 

 

Setting Things Up…

First, let’s create a new Visual Basic Windows Forms Application project called “WorkingWithDataViews”.  Add 2 DataGridViews to the form and set the AutoSizeColumnMode property for each to “DisplayedCells”, then add 2 labels, and a button.  Mine looks like this:

 

form

 

 

Next, before we get started working with DataView’s, let’s create a DataTable and populate it with some data so that we have something to work with.

 

If you are new to DataTables, then you may want to take a look at 2 other articles that I’ve written about them:

 

ADO.NET:  DataTables, An Introduction

 

ADO.NET:  DataTables, In-depth

 

 

Create an Employee DataTable, then fill it with some employees.

 

You can just double-click on Button1, and insert this code into the Button1_Click event handler, if you want.

 

'Create a new DataTable
Dim table As New DataTable("Employee")

'Add an auto ID column
table.Columns.Add("ID", GetType(Integer)) _
    .AutoIncrement = True

'Add additional columns
table.Columns.Add("FirstName")
table.Columns.Add("LastName")
table.Columns.Add("HireDate", GetType(Date))

'Set the PrimaryKey for the table
table.PrimaryKey = _
        New DataColumn() {table.Columns("ID")}

'Add data to it.
With table.Rows
    .Add(Nothing, "Gary", "Lima", #1/1/2000#)
    .Add(Nothing, "Paola", "Rael", #12/25/2008#)
    .Add(Nothing, "Anthony", "Rael", #2/1/2009#)
    .Add(Nothing, "Andrew", "Roberts", #2/1/2009#)
    .Add(Nothing, "Daniel", "Parks", #6/15/1990#)
    .Add(Nothing, "Jay", "Adams", #6/15/1990#)
    .Add(Nothing, "Amy", "Alberts", #6/15/1990#)
    .Add(Nothing, "Sean", "Allen", #6/15/1990#)
    .Add(Nothing, "John", "Black", #6/15/1990#)
    .Add(Nothing, "Jordan", "Perry", #6/15/1990#)
End With


'Accept changes, changing the RowState from
' "Added" to "UnModified"

table.AcceptChanges()

 

The Employees table above will be used for all of the DataView examples below, except for the Sorting dates example.

 

 

How DataViews Work…

As mentioned above, a DataView is simply a view of the data that is stored in a DataTable.  It is not a separate copy of the data.  Therefore, viewing the contents of a DataView is just like viewing the contents of a DataTable.

 

To demonstrate this, let’s take a look at this code added to the Button1_Click event handler:

 

'Set the DataSource of DataGridView1 to the
'  DataTable itself.
Me.DataGridView1.DataSource = table


'Set the DataSource of DataGridView2 to the
'  DataTable's Default DataView
Dim view As DataView = table.DefaultView
Me.DataGridView2.DataSource = view

 

Notice that DataGridView1 is displaying data directly from the DataTable, and DataGridView2 is displaying data directly from the table’s default DataView

 

Now, run the project (F5), and click Button1. 

 

You’ll immediately notice that both DataGridViews look exactly the same.  They are both displaying the same data.

 

form1

 

 

DataViews are only a view of the data

To prove that the DataView is simply a view of the data stored in the DataTable, and not a separate copy:  in DataGridView1 (top DataGridView), change the LastName for Paola from “Rael” to “Lima”.  Make sure to move to a different cell after changing the name.

 

Notice that in DataGridView2 (bottom DataGirdView), Paola’s name immediately changes, so that both DataGridViews are displaying the same data.

 

Here’s how it looks:

 

form2

 

 

Another thing to notice is that the DataGridViews are not in synch:  Their row positions are separate from each other.  So, if in DataGridView1, you click on the cell containing “Anthony”, DataGridView2 does not immediately move to that row.  Furthermore, if you click on a cell of any row in DataGridView2, DataGridView1 does not automatically move to that row.

 

 

DataView References

Now, let’s go back and change the code so that both DataGridViews are using DataViews, just like I’ve done here:

 

'Set the DataSource of DataGridView1 to the
'  DataTable itself.
Dim view1 As DataView = table.DefaultView
Me.DataGridView1.DataSource = view1


'Set the DataSource of DataGridView2 to the
'  DataTable's Default DataView
Dim view2 As DataView = table.DefaultView
Me.DataGridView2.DataSource = view2

 

Now run the program again, and notice that both DataGridViews are displaying the exact same data.

 

In DataGridView1, try clicking on cells that are in different rows.  Notice that DataGridView2 automatically moves to the same row that DataGridView1 is on (although not the same cell).  Try clicking on different cells in DataGridView2, and notice that DataGridView1 automatically moves to the same row.

 

Important notice

There is something that I would really like you to notice here.  Even though my code example above declared two different variables (view1 and view2), both variables are actually pointing to the same DataView in memory, which in this case is the table’s DefaultView.

 

This is very important to understand.  Because, any sorting or filtering applied to one DataView, automatically affects the other.

 

Let’s test this out by sorting view1 by the FirstName column:

 

'Set the DataSource of DataGridView1 to the
'  DataTable itself.
Dim view1 As DataView = table.DefaultView
view1.Sort = "FirstName"

Me.DataGridView1.DataSource = view1


'Set the DataSource of DataGridView2 to the
'  DataTable's Default DataView
Dim view2 As DataView = table.DefaultView

Me.DataGridView2.DataSource = view2

 

Now, run the program (F5) and look at the results.  The data in both DataGridViews are sorted by the FirstName column, as you can see:

 

form3

 

 

DataViews have a constructor

If you were to try to use multiple DataViews for displaying data from a DataTable in different controls on the same form, like we are above, you may find yourself frustrated when you realize they are all in synch, especially if you are trying to apply different sorting and filtering for each DataView.

 

This is a mistake I made when I first started programming with DataViews.

 

Fortunately, the DataView has a constructor that can be used to create a completely separate DataView object in memory.  This means that any sorting and filtering applied to that view does not affect any other view that references the same DataTable.  Here’s an example of what I mean:

 

'Create a NEW DataView,
'  passing the DataTable as an a reference
Dim view1 As New DataView(table)
view1.Sort = "FirstName"

Me.DataGridView1.DataSource = view1


'Create a NEW DataView,
'  passing the DataTable as an a reference
Dim view2 As New DataView(table)

Me.DataGridView2.DataSource = view2

 

Now run the program again (F5), and notice that DataGridView1 is sorted, but DataGridView2 is not sorted.

 

form4

 

 

Sorting…

The examples above have already shown you how to sort a column, using the “FirstName” column.  When sorting was applied, the FirstName column was automatically sorted in Ascending order.

 

There are 2 directions that sorting can be applied:  In Ascending (ASC) order, and in Descending (DESC) order.  Ascending is the default.

 

To specify which direction to apply when sorting, simply add the “ASC” or “DESC” keywords after the column you wish to sort, as such:

 

'Sort the view by FirstName column
'  in Ascending Order

view1.Sort = "FirstName ASC"


'Sort the view by FirstName column
'  in Descending Order

view1.Sort = "FirstName DESC"

 

 

Sorting by multiple columns

Sorting can not only be applied to one column, but it can also be applied to multiple columns.  All you have to do is list the columns that you want to sort by, separated by a comma. 

 

For example, to sort by LastName, and then FirstName, use the following syntax:

 

'Sort the view by LastName column, then
'  by FirstName column in Ascending Order
'  (which is the default)

view1.Sort = "LastName, FirstName"

 

To specify the sort direction for each individual column, simply add the ASC or DESC keywords after the column names:

 

'Sort the view by LastName column in Descending
'  order, then by FirstName column in Descending
'  order

view1.Sort = "LastName DESC, FirstName DESC"

 

 

Data Types affect sorting

Another very important note is that the Data Type of a column affects the way the data is sorted.  Because Strings sort one way, Integers sort another way, Dates sort another way, etc…  So if your sorting does not work the way you expect, go back and check the Data Type of the column you are trying to sort, and make sure it is correct for the type of data that is being stored in it.

 

Let me give you an example:

 

'Create a new table with a Date Colum
'  The default Data Type is String
Dim table As New DataTable()
table.Columns.Add("Date")

'Add 24 months of data
For i As Integer = -24 To -1
    table.Rows.Add(Today.AddMonths(i)) : Next

'Get the DataView for the table
Dim view As DataView = table.DefaultView

'Sort by Date Ascending
'  NOTE:  The data type for the Date
'  column is a String

view.Sort = "Date ASC"

Me.DataGridView1.DataSource = view

 

Take a look at the result here:  It’s not sorted correctly. 

 

form6

 

 

Notice that the first date is January 2008, then January 2009, then October 2007, then October 2008, etc.

 

That’s why it’s very important to make sure your columns have the correct data type. 

 

 

Now, let’s fix this problem by setting the Date column’s data type to Date, and then try sorting again:

 

'Create a new table with a Date Column
'  The default Data Type is String
Dim table As New DataTable()

'Set the data type to Date
table.Columns.Add("Date", GetType(Date))

'Add 24 months of data
For i As Integer = -24 To -1
    table.Rows.Add(Today.AddMonths(i)) : Next

'Get the DataView for the table
Dim view As DataView = table.DefaultView

'Sort by Date Ascending
'  NOTE:  The data type for the Date
'  column is a String

view.Sort = "Date ASC"

Me.DataGridView1.DataSource = view

 

And the result:

 

form7

 

 

Searching…

Not only can DataViews be used for sorting, but they can be used for Searching as well.  The Find and FindRows methods are the two methods that the DataView uses to perform searches.

 

Set the Sort property First

Before searching can be performed using the Find or FindRows methods, the Sort property must be set.  It can be set to 1 column (as “FirstName”) or to multiple columns (as "LastName, FirstName”).  Setting the Sort property on a DataView causes the corresponding DataTable to create an index on each column referenced.

 

Using the Find method

When a search is performed using the Find method, you pass the value you want to search for (eg.:  “Gary”), or an array of values you want to search for (eg.:  “Paola”, “Rael”), that correspond to the columns applied to the Sort property.  When a match is found, an integer is returned that indicates the index of the matching row within the DataView.  If no matching row is found, then a –1 is returned.

 

Here’s an example of using the Find method to search for “Gary”:

 

'Get a reference to the table's
'  default DataView
Dim view As DataView = table.DefaultView

'Set the Sort to the name of the column
'  that we want to search

view.Sort = "FirstName"

'Set the view as the source for DataGridView 1
Me.DataGridView1.DataSource = view

'Create a variable to store the index of
'  the row, if found
Dim index As Integer

'Search for "Gary"
index = view.Find("Gary")

'If it was found Gary, then the value of
'  index will be greater than -1
If index > -1 Then

    'A match was found, so use the row index
    '  to set the current cell of DataGridView 1
    Me.DataGridView1.CurrentCell = _
            DataGridView1.Rows(index).Cells(1)

Else
    MsgBox("Match not found")
End If

 

 

Here’s another example of using the Find method to search for multiple values (a FirstName and a LastName) matching “Paola" “Rael”:

 

'Get a reference to the table's
'  default DataView
Dim view As DataView = table.DefaultView

'Set the Sort to the name of the columns
'  that we want to search:  The FirstName
'  column and the LastName column

view.Sort = "FirstName, LastName"

'Set the view as the source for DataGridView 1
Me.DataGridView1.DataSource = view

'Create a variable to store the index of
'  the row, if found
Dim index As Integer

'Search for a row containing "Paola" in the
'  FirstName column, and "Rael" in the LastName
'  column.

index = view.Find(New Object() {"Paola", "Rael"})

'If it was found, then the value of index will
'  be greater than -1
If index > -1 Then

    'A match was found, so use the row index
    '  to set the current cell of DataGridView 1
    Me.DataGridView1.CurrentCell = _
            DataGridView1.Rows(index).Cells(1)

Else
    MsgBox("Match not found")
End If

 

 

Using the FindRows method

The FindRows method can be used to return an array of rows that match the value(s), you are searching for.  This is useful when there may be multiple rows containing the same value(s). 

 

If you’ve been looking at the data we’ve been using for demonstration, you’ll notice that there are two persons with the last name of “Rael”, they are “Paola” and “Anthony”.

 

Let’s use the FindRows method to display rows that have a LastName of “Rael":

 

'Get a reference to the table's
'  default DataView
Dim view As DataView = table.DefaultView

'Set the Sort to the name of the columns
'  that we want to search:  The LastName
'  column

view.Sort = "LastName"

'Set the view as the source for DataGridView1
Me.DataGridView1.DataSource = view

'Create an array to store the rows found
Dim rows As DataRowView()

'Search for all rows containing the
'  LastName of "Rael"

rows = view.FindRows("Rael")

'Loop through each row in DataGridView1
For i As Integer = 0 To _
    Me.DataGridView1.Rows.Count - 1

    'See if the row in the DataView that is
    '  bound to row in the grid is in the
    '  array of rows that were found.
    If rows.Contains( _
        DataGridView1.Rows(i).DataBoundItem) Then

        'If it was, select it.
        DataGridView1.Rows(i).Selected = True

    End If

Next

 

 

Remember that, like the Find method, the FindRows method can search for multiple values that correspond the columns applied to the Sort property.

 

So, let’s see an example of searching for rows that have a FirstName of “Paola” and a LastName of “Rael”:

 

'Get a reference to the table's
'  default DataView
Dim view As DataView = table.DefaultView

'Set the Sort to the name of the columns
'  that we want to search:  The FirstName
'  column and the LastName column

view.Sort = "FirstName, LastName"

'Set the view as the source for DataGridView1
Me.DataGridView1.DataSource = view

'Create an array to store the rows found
Dim rows As DataRowView()

'Search for all rows containing the FirstName of
'  "Paola" and the LastName of "Rael"

rows = view.FindRows( _
    New Object() {"Paola", "Rael"})

'Loop through each row in DataGridView1
For i As Integer = 0 To _
    Me.DataGridView1.Rows.Count - 1

    'See if the row in the DataView that is
    '  bound to row in the grid is in the
    '  array of rows that were found.
    If rows.Contains( _
        DataGridView1.Rows(i).DataBoundItem) Then

        'If it was, select it.
        DataGridView1.Rows(i).Selected = True

    End If

Next

 

 

Filtering…

Ok, so now we’ve learned how to Sort a DataView, and how to Search it using the Find and FindRows method.  Now we’re ready for filtering.

 

Filtering is, in my opinion, one of the most useful features of a DataView.  It is very common to have a DataTable filled with records, and then filter a DataView so that a subset of the records can be displayed on a form for a user.

 

There are, essentially, two filters that can be applied to a DataView, they are:  a RowFilter and a RowStateFilter.

 

Let’s begin by looking at the RowFilter.

 

Using the RowFilter

Filtering rows in a DataView is as easy as setting the RowFilter property to an expression that is similar to an SQL WHERE clause.  You have to include the name of the column(s) you want to filter, and the value(s) you want to filter by.

 

An important thing to keep in mind, is the type of data in the column you are filtering.  If a column contains data of a String data type, then be sure to enclose your criteria in single quotes (‘), if a column contains data of a Date data type, then be sure to enclose your criteria in a pound character (#), if your column contains data of a numeric data type, like Integer, then don’t use any enclosure.

 

Here are some examples:

 

'Filter the View by FirstName='Gary'
Dim criteria1 As String = "Gary"
View.RowFilter = _
    String.Format("FirstName='{0}'", criteria1)


'Fiter the View by HireDate=#12/25/2008#
Dim criteria2 As Date = #12/25/2008#
View.RowFilter = _
    String.Format("HireDate=#{0}#", criteria2)


'Filter the View by ID=3
Dim criteria3 As Integer = 3
View.RowFilter = _
    String.Format("ID={0}", criteria3)

 

 

Wild Cards

The RowFilter property allows you to perform searches using wild cards at either the beginning or ending of a string search.  Either * or % can be used as a wild card.

 

When you use a wild card character to perform a search, you must also use the LIKE keyword, instead of the equal (=) sign.

 

As a note, wild card characters can only be used to search columns that have a String data type.

 

Here are some examples of performing wild card searches:

 

Dim view As DataView = table.DefaultView


'Filter by FirstName column starting with
'  the letter A

view.RowFilter = _
    String.Format("FirstName LIKE '{0}'", "A%")


'Filter by FirstName column that has the
'  letter A anywhere in it.

view.RowFilter = _
    String.Format("FirstName LIKE '{0}'", "%A%")


'Filtery b FirstName column that ends with
'  the letter N

view.RowFilter = _
    String.Format("FirstName LIKE '{0}'", "%N")

 

 

Not only can simple searches be performed, as demonstrated above, but complex searches can be performed as well.  Complex searches use more search more than 1 column for matching criteria.

 

When performing a complex search, use the AND or OR keywords.

 

Here are some examples performing complex wild card searches:

 

Dim view As DataView = table.DefaultView


'The filter syntax here is:
'  FirstName LIKE 'A%' AND LastName LIKE 'R%'

view.RowFilter = _
     String.Format("FirstName LIKE '{0}' AND " & _
                    "LastName LIKE '{1}'", _
                    "A%", "R%")


'The filter syntax here is:
'  FirstName LIKE '%M%' AND LastName LIKE "%B%'

view.RowFilter = _
    String.Format("FirstName LIKE '{0}' AND " & _
                    "LastName LIKE '{1}'", _
                    "%M%", "%B%")


'The filter syntax here is:
'  FirstName LIKE 'G%' OR FirstName LIKE 'P%'

view.RowFilter = _
    String.Format("FirstName LIKE '{0}' OR " & _
                    "FirstName LIKE '{1}'", _
                    "G%", "P%")

 

 

Using the RowStateFilter

The RowStateFilter property allows you to filter a DataView by the state of the rows in the table.  For example, a new row that has just been added to the table has a RowState of “Added”; conversely, a row that has been deleted has a RowState of “Deleted”. 

 

The RowStateFilter can be set to any of the following DataViewRowState enumerations:

 

    -  Added:  Filter for Added rows

    -  CurrentRows:  Filter for Non-deleted rows.  This is the default

    -  Deleted:  Filter for deleted rows

    -  ModifiedCurrent:  Filter for Modified rows, displaying the current values

    -  ModifiedOriginal:  Filter for Modified rows, displaying the original values

    -  None:  No rows are included

    -  OriginalRows:  Filter for deleted, modified, and unmodified rows,

       displaying the original values

    -  Unchanged:  Filter for unmodified rows

 

 

As a note both the RowFilter and RowStateFilter can be applied at the same time.

 

So, here are some examples of using the RowStateFilter:

 

Dim view As DataView = table.DefaultView

Dim row As DataRowView = Nothing


'Add a new record
row = view.AddNew()
row.Item("FirstName") = "Tom"
row.Item("LastName") = "Messner"
row.Item("HireDate") = Today
row.EndEdit()

'Modify a record
row = view(1)
row.Item("FirstName") = "Modified"

'Delete a record
row = view(5)
row.Delete()


'View all Added rows:
view.RowStateFilter = DataViewRowState.Added

'View all Deleted rows:
view.RowStateFilter = DataViewRowState.Deleted

'View all Modified rows
view.RowStateFilter = _
        DataViewRowState.ModifiedCurrent


'View all current (non-deleted) rows
'  that start with the letter A

view.RowStateFilter = _
        DataViewRowState.CurrentRows

view.RowFilter = "FirstName LIKE 'A%'"

 

 

Iteration…

Now, we’ve seen how to use a DataView, how to sort it, how to search it, and how to filter it.  The next question is, how do you iterate through the DataView?

 

There are times when you need to be able to loop through each row that is in the DataView so that you can perform a certain action, such as testing for a condition, or updating a value.  In those times, you can use Iteration or Looping to accomplish the task.

 

Looping through a table is as easy as using the Do…Loop structure, starting at the first index, and continuing through the last.  Here’s an example of looping through all of the rows in a table:

 

Dim view As DataView = table.DefaultView

'Filter the view for only current
'  (non-deleted) rows

view.RowStateFilter = _
    DataViewRowState.CurrentRows


'Declare the row as a DataRowView
'  (not a DataRow)
Dim row As DataRowView


'Loop through each row in the view
For i As Integer = 0 To view.Count - 1

    'Get a reference to the row
    row = view(i)

    'Perform an action
    '  (write to the Output window)
    Console.WriteLine(row("FirstName"))

Next

 

 

In addition to looping through each row in the view, you can also Iterate through each row using a For Each…Next structure, as follows:

 

Dim view As DataView = table.DefaultView

'Filter the view for only current
'  (non-deleted) rows

view.RowStateFilter = _
    DataViewRowState.CurrentRows


'Loop through each row in the view
For Each row As DataRowView In view

    'Perform an action
    '  (write to the Output window)
    Console.WriteLine(row("FirstName"))

Next

 

 

Getting Distinct Records…

We are almost to the end of our DataView discussion.  But before we finish, there’s one more tip I’d like to share with you.

 

The DataView has the ability to output it’s contents to a new DataTable by using it’s ToTable method.  One nice feature of the ToTable method is that it is overloaded, and one overload allows you to specify the columns you want to output, and whether or not you want only distinct records copied to the table. 

 

A Distinct record is determined by the columns you choose to copy to a new table, not whether every column in the row is an exact match.

 

This is really worth knowing, because there are times when you may suspect there are duplicate rows in a DataTable.  The question will arise, how do I remove the duplicates?  This is how:

 

'Create a new table with a Date Column
'  The default Data Type is String
Dim table As New DataTable()

'Add ID and Item columns
table.Columns.Add("ID")
table.Columns.Add("Item")

'Add 3 rows of data
'  The ID is different for each row
'  The Item is the same

table.Rows.Add(1, "Item1")
table.Rows.Add(2, "Item1")
table.Rows.Add(3, "Item1")

'Get the default DataView of the table
Dim view As DataView = table.DefaultView

'Use the ToTable method to get Distinct
'  rows, removing duplicates.
Dim distinctTable As DataTable
distinctTable = view.ToTable(True, "Item")

Me.DataGridView1.DataSource = distinctTable

 

Note:  Because the Item column was the only column that was copied to the new table, the differences in the ID column were ignored, and only the Item column was evaluated for duplicates.

 

 

Summary

A DataView is the tool of choice, when it comes to viewing the data that is stored in a DataTable, and performing actions such as sorting, searching and filtering, and even grouping.  Plus, DataViews can be used in place of DataTables when performing DataBinding.

0 comments:

Leave a Reply

Translate

Google-Translate-Chinese (Simplified) BETA Google-Translate-English to French Google-Translate-English to German Google-Translate-English to Italian Google-Translate-English to Japanese BETA Google-Translate-English to Korean BETA Google-Translate-English to Russian BETA Google-Translate-English to Spanish

Tags