ADO.NET in a Nutshell
The DataView object also gives you the opportunity to apply sorting and filtering logic that customizes how data will appear without modifying the underlying data itself. 12.2.1 Sorting with the DataView
To apply a sort to bound data, you simply set the DataView.Sort property with a string with the corresponding sort information. ADO.NET sorting uses the same syntax as the ORDER BY clause in a SQL query. For example, you might use the following SQL statement to order results by country: SELECT * FROM Customers ORDER BY Country ASC The equivalent ADO.NET code is shown here: ds.Tables["Customers"].DefaultView.Sort = "Country ASC"; dataGrid1.DataSource = ds.Tables["Customers"]; The sort is according to the sort order of the data type of the column. For example, string columns are sorted alphanumerically without regard to case ( assuming the DataTable.CaseSensitive property is false ). Numeric columns are ordered using a numeric sort. Columns that contain binary data can't be sorted. Add ASC after a column name for an ascending sort (with smallest values first) or DESC for a descending sort. Keep in mind that if you want to bind a control to the full DataSet , setting the DataView.Sort property will have no effect because the default DataView isn't used. Instead, you must modify the DataViewSetting.Sort property exposed through the DataViewManager : ds.DefaultViewManager.DataViewSettings["Customers"].Sort = "Country ASC"; dataGrid1.DataSource = ds;
You can also use nested sorts. To sort using multiple columns, just add a comma between each sort specification. For example, the following code sorts first by country and then orders all rows that have the same country by city: ds.Tables["Customers"].DefaultView.Sort = "Country ASC, City ASC"; dataGrid1.DataSource = ds.Tables["Customers"]; Alternatively, instead of setting the DataView.Sort property, you can set the DataView.ApplyDefaultSort property to true . In this case, ADO.NET automatically creates a sort order in ascending order based on the primary key column of the DataTable . ApplyDefaultSort applies only when the Sort property is a null reference or an empty string, and when the table has a defined primary key. 12.2.2 Filtering by Column
To filter a DataView , you set a filter expression in the DataView.RowFilter property. Filtering by column works similarly to the SQL WHERE clause: it allows you to select rows that match the filter criteria. For example, consider the following SQL query, which filters rows based on two column values: SELECT * FROM Customers WHERE Country='Argentina' AND City='Buenos Aires' This translates into the ADO.NET code shown here: ds.Tables["Customers"].DefaultView.RowFilter = "Country='Argentina' AND City='Buenos Aires'"; dataGrid1.DataSource = ds.Tables["Customers"]; If you use this code with the Northwind table, you receive three rows. The other rows are still present in the underlying DataTable , but they are hidden from view. 12.2.2.1 Filter operators
Like the WHERE clause, the RowFilter property allows a wide range of operators and functions for both numeric and string data types. Consider some of the following examples: // Find all the rows that match one of the three specified countries. ds.Tables["Customers"].DefaultView.RowFilter = "Country IN ('Argentina', 'Canada', 'Japan')"; // Find all the rows where a Country isn't specified. ds.Tables["Customers"].DefaultView.RowFilter = "Country IS NULL"; // Use alphabetic comparison to find all the rows where the Country // starts with S or any letter after it in the alphabet // (including Switzerland, USA, UK, Venezuela, and so on). ds.Tables["Customers"].DefaultView.RowFilter = "Country > 'S'";
With numeric values, you can use ranges or mathematical operators to filter rows. For example, here are some filters for the Products table: // Find all the rows where UnitPrice is greater than 10. ds.Tables["Products"].DefaultView.RowFilter = "UnitPrice > 10"; // Find all the rows where UnitPrice is above 10 but below 15. // This is an exclusive range. ds.Tables["Products"].DefaultView.RowFilter = "UnitPrice > 10 AND UnitPrice < 15"; // Find all the rows where UnitPrice is anywhere from 10 to 15. // This is an inclusive range. ds.Tables["Products"].DefaultView.RowFilter = "UnitPrice BETWEEN 10 AND 15"; // Find all prodcuts where the total stock value is at least 00. ds.Tables["Products"].DefaultView.RowFilter = "UnitPrice * UnitsInStock > 1000"; Table 12-1 lists the most common filter operators. Table 12-1. Filter operators
12.2.2.2 Pattern-matching filters
The LIKE keyword performs pattern matching on strings. Pattern matching is akin to regular-expression syntax but is much less powerful. Unfortunately, the pattern matching provided by ADO.NET, while similar to that provided in SQL Server, lacks a few features. Notably, the _ character (which represents a single variable character) and the [ ] brackets (which specify a character from a range of allowed values) aren't supported. However, you can use the % character to specify zero or more characters . Here are two examples of pattern matching with ADO.NET: // Use pattern matching to find all the countries that start with // the letter "A" (includes Argentina, Austria, and so on.) ds.Tables["Customers"].DefaultView.RowFilter = "Country LIKE 'A%'; // Matches contacts that contain the word "Manager" // (includes Sales Manager, Marketing Manager, and so on). ds.Tables["Customers"].DefaultView.RowFilter = "ContactTitle LIKE '%Manager%'"; 12.2.2.3 Filter-supported functions
Finally, you can also use a few built-in SQL functions to further refine a column sort. These features (detailed in Table 12-2) allow you to perform comparisons that include null values, parse a portion of a string, or even perform an aggregate query on related child rows. // Display records where the country name is longer than eight characters // (includes Venezuela, Argentina, and Switzerland). ds.Tables["Customers"].DefaultView.RowFilter = "Len(Country) > 8"; // Display records where the second and third letter are "ra" // (includes Brazil and France). // Note that this expression uses 1-based counting. ds.Tables["Customers"].DefaultView.RowFilter = "Substring(Country, 2, 2) = 'ra'"; // Display all the columns that have a region code of SP, or a null value. ds.Tables["Customers"].DefaultView.RowFilter = "IsNull(Region, 'SP') = 'SP'"; Table 12-2. Filter-supported functions
12.2.2.4 Aggregate functions and relations in filters
You can also use aggregate functions to create a filter that restricts related child rows. For example, you can look at all customers that have total orders greater than a certain dollar figure. You can also return all the region records that have at least 20 matching customers. In order to use this technique, however, you need to create a DataRelation between the related tables first. The basic syntax is Child(RelationName).ColumnName or Parent(RelationName).ColumnName . Here are a few examples that use the Suppliers and Products tables, which are linked on the SuppliersID column using a relation named Suppliers_Products : // Only display products for a specific supplier. ds.Tables["Products"].DefaultView.RowFilter = "Parent(Suppliers_Products).CompanyName='Tokyo Traders'"; dataGrid1.DataSource = ds.Tables["Products"]; // Display suppliers that have at least five related products. ds.Tables["Suppliers"].DefaultView.RowFilter = "Count(Child(Suppliers_Products).SupplierID) >= 5"; dataGrid1.DataSource = ds.Tables["Suppliers"]; // Display suppliers that have at least one product with more than 50 units // in stock. ds.Tables["Suppliers"].DefaultView.RowFilter = "Max(Child(Suppliers_Products).UnitsInStock) > 50"; dataGrid1.DataSource = ds.Tables["Suppliers"]; Example 12-2 presents the full code needed to create a relationship, add it to the DataSet , and then use it with a relational filter expression. Example 12-2. Using a relational filter expression
private void RelationTest_Load(object sender, System.EventArgs e) { string connectionString = "Data Source=localhost;" + "Initial Catalog=Northwind;Integrated Security=SSPI"; string SQL = "SELECT * FROM Suppliers"; // Create ADO.NET objects. SqlConnection con = new SqlConnection(connectionString); SqlCommand com = new SqlCommand(SQL, con); SqlDataAdapter adapter = new SqlDataAdapter(com); DataSet ds = new DataSet("Northwind"); // Execute the command. try { con.Open(); adapter.Fill(ds, "Suppliers"); com.CommandText = "SELECT * FROM Products"; adapter.Fill(ds, "Products"); } catch (Exception err) { Console.WriteLine(err.ToString()); } finally { con.Close(); } // Create references to the parent and child columns. DataColumn parentCol = ds.Tables["Suppliers"].Columns["SupplierID"]; DataColumn childCol = ds.Tables["Products"].Columns["SupplierID"]; // Create the DataRelation object. DataRelation relation = new DataRelation("Suppliers_Products", parentCol, childCol); // Add the relation to the DataSet. ds.Relations.Add(relation); // Define the filter expression for the Suppliers table. ds.Tables["Suppliers"].DefaultView.RowFilter = "Count(Child(Suppliers_Products).SupplierID) > 3"; // Display the table. dataGrid1.DataSource = ds.Tables["Suppliers"]; } When you try this code, you'll find that as a side effect, the DataGrid automatically adds navigation links that allow you to view the related child rows of a supplier. These navigational links (shown in Figure 12-3) use the name of the corresponding DataRelation . Figure 12-3. Relational data in the DataGrid 12.2.3 Filtering by Row State
The DataView.RowStateFilter property allows you to hide or show rows based on their state. Table 12-3 shows the DataViewRowState enumeration values that set the RowStateFilter . You can use any one of these values or a bitwise combination of values. // Show only deleted rows. ds.Tables["Products"].DefaultView.RowStateFilter = DataViewRowState.Deleted; // Show deleted and added rows. ds.Tables["Products"].DefaultView.RowStateFilter = DataViewRowState.Deleted DataViewRowState.Added; By default, the RowStateFilter is set to CurrentRows and shows everything except rows that are scheduled for deletion. Table 12-3. Values from the DataViewRowState enumeration
12.2.4 Displaying Multiple Views
One of the most useful aspects of the DataView is the ability to create multiple DataView objects to provide different representations of the same data. This technique is quite straightforward and is shown in Example 12-3 with three separate DataGrid controls. Each DataView applies a different SQL filter expression using the RowFilter property. Example 12-3. Binding the same data with different views
private void MultipleView_Load(object sender, System.EventArgs e) { string connectionString = "Data Source=localhost;" + "Initial Catalog=Northwind;Integrated Security=SSPI"; string SQL = "SELECT * FROM Customers"; // Create ADO.NET objects. SqlConnection con = new SqlConnection(connectionString); SqlCommand com = new SqlCommand(SQL, con); SqlDataAdapter adapter = new SqlDataAdapter(com); DataSet ds = new DataSet("Northwind"); // Execute the command. try { con.Open(); adapter.Fill(ds, "Customers"); } catch (Exception err) { Console.WriteLine(err.ToString()); } finally { con.Close(); } // Create views. DataView viewArgentina = new DataView(ds.Tables["Customers"]); DataView viewBrazil = new DataView(ds.Tables["Customers"]); // Filter views. viewArgentina.RowFilter = "Country = 'Argentina'"; viewBrazil.RowFilter = "Country = 'Brazil'"; // Perform data binding. gridArgentina.DataSource = viewArgentina; gridBrazil.DataSource = viewBrazil; gridAll.DataSource = ds.Tables["Customers"].DefaultView; } Notice that if you modify a row in one view, the changes appear automatically in all other views. Remember, there is only one data sourcethe linked DataTable . Figure 12-4 shows the three views, each of which contains only a subset of the full data in the DataTable . Figure 12-4. Multiple views of the same data |