4.5 Setting Up for Paging
Three properties shown earlier in Table 9 participate in paging—EnablePaging, StartRowIndexParameterName, and MaximumRowsParameterName. As the name clearly suggests, EnablePaging toggles support for paging on and off. The default value is false, meaning that paging is not turned on automatically. ObjectDataSource provides an infrastructure for paging, but actual paging must be implemented in the class bound to ObjectDataSource. In the following code snippet, the Customers class has a method, LoadByCountry,
that takes two additional parameters to indicate the page size and the
index of the first record in the page. The names of these two
parameters must be assigned to MaximumRowsParameterName and StartRowIndexParameterName, respectively.
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
TypeName="Core35.DAL.Customers"
StartRowIndexParameterName="firstRow"
MaximumRowsParameterName="totalRows"
SelectMethod="LoadByCountry">
<SelectParameters>
<asp:ControlParameter Name="country" ControlID="Countries"
PropertyName="SelectedValue" />
<asp:ControlParameter Name="totalRows" ControlID="PageSize"
PropertyName="Text" />
<asp:ControlParameter Name="firstRow" ControlID="FirstRow"
PropertyName="Text" />
</SelectParameters>
</asp:ObjectDataSource>
The implementation of paging is up to the method and must be coded manually. LoadByCountry provides two overloads, one of which supports paging. Internally, paging is actually delegated to FillCustomerList.
public static CustomerCollection LoadByCountry(string country)
{
return LoadByCountry(country, -1, 0);
}
public static CustomerCollection LoadByCountry(string country,
int totalRows, int firstRow)
{
CustomerCollection coll = new CustomerCollection();
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
SqlCommand cmd;
cmd = new SqlCommand(CustomerCommands.cmdLoadByCountry, conn);
cmd.Parameters.AddWithValue("@country", country);
conn.Open();
SqlDataReader reader = cmd.ExecuteReader();
HelperMethods.FillCustomerList(coll, reader, totalRows, firstRow);
reader.Close();
conn.Close();
}
return coll;
}
As you can see in the companion source code, FillCustomerList
simply scrolls the whole result set using a reader and discards all the
records that don’t belong in the requested range. You could perhaps
improve upon this approach to make paging smarter. What’s important
here is that paging is built into your business object and exposed by
data source controls to the pageable controls through a well-known
interface.
4.6 Updating and Deleting Data
To update underlying data using ObjectDataSource,
you need to define an update/insert/delete method. All the actual
methods you use must have semantics that are well suited to implement
such operations. Here are some good prototypes for the update
operations:
public static void Save(Employee emp)
public static void Insert(Employee emp)
public static void Delete(Employee emp)
public static void Delete(int id)
More so than with select
operations, update operations require parameters. To update a record,
you need to pass new values and one or more old values to make sure the
right record to update is located and to take into account the
possibility of data conflicts. To delete a record, you need to identify
it by matching a supplied primary key parameter. To specify input
parameters, you can use command collections such as UpdateParameters, InsertParameters, or DeleteParameters. Let’s examine update/insert scenarios first.
To
update an existing record or insert a new one, you need to pass new
values. This can be done in either of two ways—listing parameters
explicitly or aggregating all parameters in an all-encompassing data
structure. The prototypes shown previously for Save and Insert follow the latter approach. An alternative might be the following:
void Save(int id, string firstName, string lastName, ...)
void Insert(string firstName, string lastName, ...)
You can use command parameter collections only if the types involved are simple types—numbers, strings, dates.
To make a custom class such as Employee acceptable to the ObjectDataSource control, you need to set the DataObjectTypeName property:
<asp:ObjectDataSource ID="RowDataSource" runat="server"
TypeName="Core35.DAL.Employees"
SelectMethod="Load"
UpdateMethod="Save"
DataObjectTypeName="Core35.DAL.Employee">
<SelectParameters>
<asp:ControlParameter Name="id" ControlID="GridView1"
PropertyName="SelectedValue" />
</SelectParameters>
</asp:ObjectDataSource>
The preceding ObjectDataSource control saves rows through the Save method, which takes an Employee object. Note that when you set the DataObjectTypeName property, the UpdateParameters collection is ignored. The ObjectDataSource
instantiates a default instance of the object before the operation is
performed and then attempts to fill its public members with the values
of any matching input fields found around the bound control. Because
this work is performed using reflection, the names of the input fields
in the bound control must match the names of public properties exposed
by the object in the DataObjectTypeName property. A practical limitation you must be aware of is the following: you can’t define the Employee class using complex data types, as follows:
public class Employee {
public string LastName {...}
public string FirstName {...}
...
public Address HomeAddress {...}
}
Representing individual values (strings in the sample), the LastName and FirstName members have good chances to match an input field in the bound control. The same can’t be said for the HomeAddress member, which is declared with a custom aggregate type such as Address. If you go with this schema, all the members in Address will be ignored; any related information won’t be carried into the Save method, with resulting null parameters. All the members in the Address data structure should become members of the Employee class.
Unlike
the insert operation, the update operation also requires a primary key
value to uniquely identify the record being updated. If you use an
explicit parameter listing, you just append an additional parameter to
the list to represent the ID, as follows:
<asp:ObjectDataSource runat="server" ID="MyObjectSource"
TypeName="Core35.SimpleBusinessObject"
SelectMethod="GetEmployees"
UpdateMethod="SetEmployee">
<UpdateParameters>
<asp:Parameter Name="employeeid" Type="Int32" />
<asp:Parameter Name="firstname" Type="string" />
<asp:Parameter Name="lastname" Type="string" />
<asp:Parameter Name="country" Type="string" DefaultValue="null" />
</UpdateParameters>
</asp:ObjectDataSource>
Note that by setting the DefaultValue attribute to null,
you can make a parameter optional. A null value for a parameter must
then be gracefully handled by the business object method that
implements the update.
There’s an alternative method to set the primary key—through the DataKeyNames property of GridView and DetailsView controls.
<asp:GridView runat="server" ID="grid1"
DataKeyNames="employeeid"
DataSourceId="MyObjectSource"
AutoGenerateEditButton="true">
...
</asp:GridView>
When DataKeyNames
is set on the bound control, data source controls automatically add a
parameter to the list of parameters for update and delete commands. The
default name of the parameter is original_XXX, where XXX stands for the value of DataKeyNames. For the operation to succeed, the method (or the SQL command if you’re using SqlDataSource) must handle a parameter with the same name. Here’s an example:
UPDATE employees SET lastname=@lastname
WHERE employeeid=@original_employeeid
The name format of the key parameter can be changed at will through the OldValuesParameterFormatString property. For example, a value of ‘{0}’ assigned to the property would make the following command acceptable:
UPDATE employees SET lastname=@lastname
WHERE employeeid=@employeeid
Setting the DataKeyNames property on the bound control (hold on, it’s not
a property on the data source control) is also the simplest way to
configure a delete operation. For a delete operation, in fact, you
don’t need to specify a whole record with all its fields; the key is
sufficient.
Note
In data-bound controls such as GridView and DetailsView, the DataKeyNames property replaces DataKeyField, which we found on DataGrid and DataList controls in ASP.NET 1.x. The difference between the two is that DataKeyNames supports keys based on multiple fields. If DataKeyNames is set to multiple fields (for example, id,name), two parameters are added: original_id and original_name. |
4.7 Configuring Parameters at Runtime
When using ObjectDataSource with an ASP.NET made-to-measure control (for example, GridView), most of the time the binding is totally automatic and you don’t have to deal with it. If you need it, though, there’s a back door you can use to take control of the update process—the Updating event:
protected void Updating(object sender,
ObjectDataSourceMethodEventArgs e)
{
Employee emp = (Employee) e.InputParameters[0];
emp.LastName = "WhosThisGuy";
}
The event fires before the update operation climaxes. The InputParameters
collection lists the parameters being passed to the update method. The
collection is read-only, meaning that you can’t add or delete elements.
However, you can modify objects being transported, as the preceding
code snippet demonstrates.
This technique is useful when, for whatever reasons, the ObjectDataSource
control doesn’t load all the data its method needs to perform the
update. A similar approach can be taken for deletions and insertions as
well.