Databinding with ASP.NET Web forms

In ASP.NET web forms, databinding is fully 2-way, and can be setup declaratively, which means you can setup databinding completely in HTML, without the necessity of code in the code-behind file. This section is about databinding with a set of data using a DataSource control.

LLBLGen Pro datasource controls

The LLBLGen Pro runtime framework ships with its own DataSourceControl controls: LLBLGenProDataSource for selfservicing and LLBLGenProDataSource2 for adapter. These also are located in the SD.LLBLGen.Pro.ORMSupportClasses.Web dll. To use them at design time, you have to add them to the toolbox first.

This can be done manually by right-clicking the toolbox when a web form is open in the Visual Studio editor (HTML or design view) and then by selecting 'Choose items...' which allows you to browse to the SD.LLBLGen.Pro.ORMSupportClasses.Web dll.

Getting started with the LLBLGenProDataSource2 control

Dragging the datasource control of choice onto a form in design mode will show you the smart tag to configure the datasource. The LLBLGenProDataSource2 control can be used for an EntityCollection<T>, TypedList or TypedView. It's recommended you use the smart-tag to setup the LLBLGenProDataSource2 control in the web form designer. To learn more about the specific properties of the LLBLGenProDataSource2 control, please consult the LLBLGen Pro reference manual for the LLBLGenProDataSource2 control.

The LLBLGenProDataSource2 control accepts a type specification which is used for the particular container type: so an entity factory for the EntityCollection<T>, the type of a TypedList or the type of a TypedView. The container type, thus what kind of object is contained in the LLBLGenProDataSource2 control, is specified using the property DataContainerType, accessable in the designer for the LLBLGenProDataSource2 control and also in the property grid of Visual Studio.

The contained object itself is exposed through the property belonging to the value of this property: so if the DataContainerType is set to EntityCollection, an EntityCollection<T> is inside the LLBLGenProDataSource2 control and the EntityCollection property is valid, though when DataContainerType is set to TypedList, a TypedList object is contained by the LLBLGenProDataSource2 control, the TypedList property is valid.

The GroupBy property is suppored in TypedList/TypedView scenario's while Prefetch path objects (settable through the PrefetchPathToUse property) are supported in EntityCollection scenarios.

Caching of data.

The data contained by the LLBLGenProDataSource2 control is cached in-between post-backs, until the data has to be refreshed.  If caching is enabled (default), the place where this data is cached is either in the ViewState (default), ASP.NET cache or the Session. You can control where the data is cached by setting the LLBLGenProDataSource2 control's property CacheLocation.

You can disable this caching by setting the CacheLocation property to None. If CacheLocation is set to Session, the data is stored in the session object with a key with the following name:

__LLBLGENPRODATASOURCEDATA_controlUniqueID_BindingContainerName

ControlUniqueID is the UniqueID of the DataSource control on the page. BindingContainerName is the name of the container the control is located in. This key is stored in the control state and is always preserved. If the CacheLocation is set to ASPNetCache, the data is stored in the ASP.NET Cache using the following key:

__LLBLGENPRODATASOURCEDATA_Guid

where the Guid is a new Guid per LLBLGenProDataSource2 control instance. You can control the ASP.NET Cache duration as well by using the property ASPNetCacheDuration which indicates in minutes the time the cached data should stay in the ASP.NET cache. Default is 20 minutes.

Disabling caching, by setting it to DataSourceCacheLocation.None, has the effect that if the LLBLGenProDataSource2 control is forced to fetch data by a call to ExecuteSelect, it always will refetch the data from the database. This can lead to different data in the page between post-backs (pre post-back and post post-back), so you should be aware of this when using the None setting.

As no data is cached, a bound control should use the data in a read-only fashion. When CacheLocation is set to None, the LLBLGenProDataSource2 control will still cache its own state in the ASP.NET control state.

Setting the data container manually.

The controls offer properties (EntityCollection, TypedList, TypedView) to set the contained object to an external object, for example an EntityCollection<T> object you've fetched in a method in your business logic tier. This also offers e.g. the ability to bind myCustomer.Orders to grids through the datasource controls by simply setting the property EntityCollection in the code behind file of the web form.

You can do this too with TypedLists by setting the TypedList property and with TypedViews by setting the TypedView property. Be sure to first set the DataContainerType property to the right container type, i.e. EntityCollection, TypedList or TypedView.

Two way databinding.

Two way databinding can be done with the DataContainerType set to EntityCollection. This means the LLBLGenProDataSource2 control manages not only the binding of data to a control but also the manipulation of data through the bound control (e.g. a GridView) of the data in the contained EntityCollection<T>.

As TypedList and TypedView classes are read-only by definition, you can't manipulate data inside these classes through the LLBLGenProDataSource2 control. How the LLBLGenProDataSource2 control fetches data (automatically or through code placed inside an eventhandler) as well as how it saves data (automatically or through event handlers) is discussed in the next section.

LivePersistence and events

A boolean property of the LLBLGenProDataSource2 control, called LivePersistence, is used to signal the LLBLGenProDataSource2 control to perform the select, insert, update and delete actions directly on the database (true) or modify the entity collection and add the actions to a Unit of Work object (false).

If LivePersistence is set to false, fetching data and saving data isn't performed by the LLBLGenProDataSource2 control but an event is raised instead which passes an event arguments object which contains the parameters for the fetch or save and allows your own code to perform the fetch or save. Also, the changed event is raised so the bound control(s) can refetch the data from the LLBLGenProDataSource2 control. LivePersistence causes 3 events to be raised:

  • PerformSelect. This event is raised when the LLBLGenProDataSource2 control needs to retrieve data from the database. Use the passed-in PerformSelectEventArgs2 object to perform the Fetch action. You should fetch the data into the appropriate object inside the PerformSelectEventArgs2 object, for example the ContainedCollection using the parameters available in the PerformSelectEventArgs2 object.
  • PerformGetDbCount. This event is raised when the LLBLGenProDataSource2 control needs to retrieve the number of items in the complete resultset. This event is raised when server side paging is enabled and the total number of items in the resultset is required by the bound control(s). Your handler should fetch the count of the set to fetch by using the passed in PerformGetDbCountEventArgs2 object which contains all information necessary for the retrieval of the count value. Set the DbCount property of the PerformGetDbCountEventArgs2 object to the value read from the database. Be sure to pass to the DataAccessAdapter.GetDbCount call the filter, prefetch path, sort expression and other objects available to you via the passed-in PerformGetDbCountEventArgs2 object.
  • PerformWork. This event is raised when ExecuteInsert/Update/Delete is called on the LLBLGenProDataSource2 control by a bound control. Typically this is done after an entity is edited in a GridView or FormView control for example, or a new entity is added through a bound control. The work is tracked in a UnitOfWork2 object which is available to you in the passed in PerformWorkEventArgs2 object.

When a refetch of the data has taken place, the UnitOfWork2 object contained in the control is cleared. This means that any update/insert/delete work pending has to be completed by that point.

Please examine the events and special event argument classes in the LLBLGen Pro Reference manual.

Refetch

To ensure fresh data from the database is retrieved, a flag on the LLBLGenProDataSource2 called Refetch can be set to true, so the control will refetch the data, even if for example the page number is the same. This can be necessary if the code-behind code decides the data represented by the LLBLGenProDataSource2 control is invalidated and has to be refetched from the database.

Using the LLBLGenProDataSource2 control.

Binding a LLBLGenProDataSource2 control works like any other web form DataSourceControl: just add the ID as DataSourceID in the bound control's HTML, or select the LLBLGenProDataSource2 control from the dropdown box for available datasources in the bound control's smart-tag. The designer of the LLBLGenProDataSource2 control will allow the user to select the DataAccessAdapter, the DataContainerType and the type of object needed for the container (factory, typedlist or typedview type).

By default no filter is set, no groupby, no prefetch path, no sorting. To set a filter, prefetch path or sort expression, a code behind page is required to set these parameters. This is due to the requirement that these are compile time checked. You can however also produce filters at runtime by using the ASP.NET parameter binding feature. This allows you to setup a binding between a control (or cookie, form etc.) which produces a value and the datasource control so the value produced by the other control is used for filtering. See the example below which uses two drop down boxes to create a filter at runtime for fetching data.

Paging is supported as well: if you want server-side paging, define the paging parameters on the LLBLGenProDataSource2 in the Visual Studio property grid. If you want paging inside your bound control (client-side paging), define the paging parameters in the bound control, e.g. the GridView. To be able to work with paging, you of course have to enable paging on the bound control.

The PerformWork event in an AJAX environment

When you use a grid like the DevExpress ASPxGrid for .NET, you can have all edit activities on the client side, using AJAX communication with the server. When you've setup the grid to be used on the client side, and the LLBLGenProDataSource2 control has LivePersistence set to false, the changes made to the data on the client will be performed in one go when the page gets a post-back.

In this scenario, it's more efficient not to bind to PerformWork, but to place a button on the form which simply performs the 'save', e.g. it says "Save changes". By not binding to the PerformWork event, all ExecuteInsert/Update/Delete actions will take place on the data, but aren't propagated to the database just yet, because LivePersistence is set to false.

In the button handler of your save button, you then retrieve the UnitOfWork2 object from the LLBLGenProDataSource2 control from the property UnitOfWorkObject, **** which contains all changes made and you can commit the changes in one transaction.

Filtering on the fly

The LLBLGenProDataSource2 control supports filtering on the fly based on parameters specified which can retrieve values from other controls, forms, cookies or other objects supported by the SelectParameters feature of ASP.NET. This is available in a LLBLGenProDataSource2 control via its SelectParameters property.

The SelectParameters property follows the same specification as the ObjectDataSource and are specified declaratively using the <SelectParameters> HTML elements inside a LLBLGenProDataSource2 control declaration. You can also use the Visual Studio designer for setting up the SelectParameters, to do that simply click the [...] button next to SelectParameters in the Property grid of Visual Studio.

Be sure that the parameter name has the same name as a field in the entity, typed list row or typed view row. You can't use SelectParameters to build a filter on non-entity fields as it builds a filter for the next fetch.

Trapping invalid input values

As the LLBLGenProDataSource2 control is the receiver of the data filled into a form, grid or other bound control, it can be these values are invalid, for example they don't match the type of the entity field or they are too big in size. By default, the LLBLGenProDataSource2 control will ignore these values and won't throw an exception.

To make the control throw an exception after all values have been evaluated, you can set the LLBLGenProDataSource2 property ThrowExceptionOnIllegalFieldInput to true, which signals the control to throw an exception if one or more fields received an illegal value which wasn't convertible to the type of the field in an update/insert scenario.

If set to true all illegal values are collected and added to one single ORMValueTypeMismatchException so your code will receive just one exception to handle them all. If set to false, the illegal values are ignored and the fields don't get set to a new value.

Usage examples

Below are two examples: one example using LivePersistence set to true and one using LivePersistence set to false. They're basically the same form. For VB.NET users: the HTML is for C#, you've to change the first line of the given HTML snippets into the following line to use it with VB.NET:

<%@ Page Language="VB" AutoEventWireup="true" CodeFile="Default.aspx.vb" Inherits="_Default" %>

Example using LivePersistence

This example contains a form, which filters a list of Order entities, provided by the LLBLGenProDataSource2 control orderDS, on the OrderEntity field ShippingCountry, provided by a static drop down box, and a drop down box with all customers from Northwind, provided by LLBLGenProDataSource2 control customerDS. Using parameter binding the two dropdown boxes produce filter information at runtime to produce the proper order list. No code required, it's completely declarative HTML.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="SD.LLBLGen.Pro.ORMSupportClasses.Web" 
    Namespace="SD.LLBLGen.Pro.ORMSupportClasses" TagPrefix="llblgenpro" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        All customers:<br />
        Customers:
        <asp:DropDownList ID="DropDownList1" runat="server" DataSourceID="customerDS" 
            DataTextField="CompanyName" DataValueField="CustomerId" AutoPostBack="True">
        </asp:DropDownList><br />
        ShipCountry:
        <asp:DropDownList ID="DropDownList2" runat="server" AutoPostBack="True">
            <asp:ListItem>Spain</asp:ListItem>
            <asp:ListItem>Germany</asp:ListItem>
        </asp:DropDownList><br />
        
        <llblgenpro:llblgenprodatasource2 id="customerDS" runat="server" 
            cachelocation="Session" datacontainertype="EntityCollection" enablepaging="True" 
            AdapterTypeName="NW20.DatabaseSpecific.DataAccessAdapter, NW20DBSpecific" 
            EntityFactoryTypeName="NW20.FactoryClasses.CustomerEntityFactory, NW20">
        </llblgenpro:llblgenprodatasource2>
        
        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="orderDS" 
            DataKeyNames="OrderId" AllowPaging="True" PageSize="5">
            <Columns>
                <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
                <asp:BoundField DataField="ShipAddress" HeaderText="ShipAddress" SortExpression="ShipAddress" />
                <asp:BoundField DataField="ShipName" HeaderText="ShipName" SortExpression="ShipName" />
                <asp:BoundField DataField="ShipCountry" HeaderText="ShipCountry" SortExpression="ShipCountry" />
                <asp:BoundField DataField="CustomerId" HeaderText="CustomerId" SortExpression="CustomerId" />
                <asp:BoundField DataField="ShipRegion" HeaderText="ShipRegion" SortExpression="ShipRegion" />
                <asp:BoundField DataField="ShipCity" HeaderText="ShipCity" SortExpression="ShipCity" />
                <asp:BoundField DataField="OrderId" HeaderText="OrderId" SortExpression="OrderId" />
                <asp:BoundField DataField="ShipPostalCode" HeaderText="ShipPostalCode" SortExpression="ShipPostalCode" />
            </Columns>
        </asp:GridView>
        
        <llblgenpro:llblgenprodatasource2 id="orderDS" runat="server" cachelocation="Session" 
            datacontainertype="EntityCollection" enablepaging="True" 
            AdapterTypeName="NW20.DatabaseSpecific.DataAccessAdapter, NW20DBSpecific" 
            EntityFactoryTypeName="NW20.FactoryClasses.OrderEntityFactory, NW20">
            <SelectParameters>
                <asp:ControlParameter ControlID="DropDownList1" Name="CustomerId" 
                    PropertyName="SelectedValue" Type="String" />
                <asp:ControlParameter ControlID="DropDownList2" Name="ShipCountry" 
                    PropertyName="SelectedValue" Type="String" />
            </SelectParameters>
        </llblgenpro:llblgenprodatasource2>
    </form>
</body>
</html>

Example using Perform Event handlers

The following example is the same form as in the previous example, with the same functionality, however now it uses LivePersistence set to false, which means we've to perform the persistence logic ourselves by writing event handlers. The HTML is shown first, which is roughly the same as the previous example's HTML except it defines bindings to EventHandlers for PerformGetDbCount, PerformSelect and PerformWork. After that the code behind in VB.NET and C# is shown.

HTML Page

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<%@ Register Assembly="SD.LLBLGen.Pro.ORMSupportClasses.Web" 
    Namespace="SD.LLBLGen.Pro.ORMSupportClasses" TagPrefix="llblgenpro" %>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
        All customers:<br />
        Customers:
        <asp:DropDownList ID="DropDownList1" runat="server" DataSourceID="customerDS" 
            DataTextField="CompanyName" DataValueField="CustomerId" AutoPostBack="True">
        </asp:DropDownList><br />
        ShipCountry:
        <asp:DropDownList ID="DropDownList2" runat="server" AutoPostBack="True">
            <asp:ListItem>Spain</asp:ListItem>
            <asp:ListItem>Germany</asp:ListItem>
        </asp:DropDownList><br />
        
        <llblgenpro:llblgenprodatasource2 id="customerDS" runat="server" 
            cachelocation="Session" datacontainertype="EntityCollection" enablepaging="True" 
            AdapterTypeName="NW20.DatabaseSpecific.DataAccessAdapter, NW20DBSpecific" 
            EntityFactoryTypeName="NW20.FactoryClasses.CustomerEntityFactory, NW20" 
            LivePersistence="False" OnPerformSelect="customerDS_PerformSelect">
        </llblgenpro:llblgenprodatasource2>
        
        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataSourceID="orderDS" 
            DataKeyNames="OrderId" AllowPaging="True" PageSize="5">
            <Columns>
                <asp:CommandField ShowDeleteButton="True" ShowEditButton="True" />
                <asp:BoundField DataField="ShipAddress" HeaderText="ShipAddress" SortExpression="ShipAddress" />
                <asp:BoundField DataField="ShipName" HeaderText="ShipName" SortExpression="ShipName" />
                <asp:BoundField DataField="ShipCountry" HeaderText="ShipCountry" SortExpression="ShipCountry" />
                <asp:BoundField DataField="CustomerId" HeaderText="CustomerId" SortExpression="CustomerId" />
                <asp:BoundField DataField="ShipRegion" HeaderText="ShipRegion" SortExpression="ShipRegion" />
                <asp:BoundField DataField="ShipCity" HeaderText="ShipCity" SortExpression="ShipCity" />
                <asp:BoundField DataField="OrderId" HeaderText="OrderId" SortExpression="OrderId" />
                <asp:BoundField DataField="ShipPostalCode" HeaderText="ShipPostalCode" SortExpression="ShipPostalCode" />
            </Columns>
        </asp:GridView>
        
        <llblgenpro:llblgenprodatasource2 id="orderDS" runat="server" cachelocation="Session" 
            datacontainertype="EntityCollection" enablepaging="True" 
            AdapterTypeName="NW20.DatabaseSpecific.DataAccessAdapter, NW20DBSpecific" 
            EntityFactoryTypeName="NW20.FactoryClasses.OrderEntityFactory, NW20" LivePersistence="False" 
            OnPerformGetDbCount="orderDS_PerformGetDbCount" OnPerformSelect="orderDS_PerformSelect" 
            OnPerformWork="orderDS_PerformWork">
            <SelectParameters>
                <asp:ControlParameter ControlID="DropDownList1" Name="CustomerId" 
                    PropertyName="SelectedValue" Type="String" />
                <asp:ControlParameter ControlID="DropDownList2" Name="ShipCountry" 
                    PropertyName="SelectedValue" Type="String" />
            </SelectParameters>
        </llblgenpro:llblgenprodatasource2>
    </form>
</body>
</html>

Code behind

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;

using SD.LLBLGen.Pro.ORMSupportClasses;
using NW20.DatabaseSpecific;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
    }

    protected void customerDS_PerformSelect(object sender, PerformSelectEventArgs2 e)
    {
        // fetch all customers using the information passed in via the 
        // PerformSelectEventArgs2 object. This select doesn't have to perform any paging, as the
        // data is for a combo box.
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntityCollection(e.ContainedCollection, e.Filter, 
                    e.MaxNumberOfItemsToReturn, e.Sorter, e.PrefetchPath);
        }
    }

    protected void orderDS_PerformGetDbCount(object sender, PerformGetDbCountEventArgs2 e)
    {
        // get the total number of orders which match the filter passed in via the
        // PerformGetDbCountEventArgs2.
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            e.DbCount = adapter.GetDbCount(e.ContainedCollection, e.Filter);
        }
    }

    protected void orderDS_PerformSelect(object sender, PerformSelectEventArgs2 e)
    {
        // fetch all orders which are in the selected page using the filter passed in
        // via the PerformSelectEventArgs2 object. 
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            adapter.FetchEntityCollection(e.ContainedCollection, e.Filter,
                    e.MaxNumberOfItemsToReturn, e.Sorter, e.PrefetchPath, 
                    e.PageNumber, e.PageSize);
        }
    }

    protected void orderDS_PerformWork(object sender, PerformWorkEventArgs2 e)
    {
        // Perform the work passed in via the PerformWorkEventArgs2 object. 
        // Start a new transaction with the passed in unit of work.
        using(DataAccessAdapter adapter = new DataAccessAdapter())
        {
            // pass the adapter to the Commit routine and tell it to autocommit
            // when the work is done. 
            e.Uow.Commit(adapter, true);
        }
    }
}

Imports SD.LLBLGen.Pro.ORMSupportClasses
Imports NW20.DatabaseSpecific

Partial Class _Default
    Inherits System.Web.UI.Page

    Protected Sub customerDS_PerformSelect(ByVal sender As Object, ByVal e As PerformSelectEventArgs2) _
        Handles customerDS.PerformSelect
        
        ' fetch all customers using the information passed in via the 
        ' PerformSelectEventArgs2 object. This select doesn't have to perform any paging, as the
        ' data is for a combo box.
        Using adapter As New DataAccessAdapter()
            adapter.FetchEntityCollection(e.ContainedCollection, e.Filter, _
                    e.MaxNumberOfItemsToReturn, e.Sorter, e.PrefetchPath)
        End Using
    End Sub

    Protected Sub orderDS_PerformGetDbCount(ByVal sender As Object, ByVal e As PerformGetDbCountEventArgs2) _
        Handles orderDS.PerformGetDbCount

        ' get the total number of orders which match the filter passed in via the
        ' PerformGetDbCountEventArgs2.
        Using adapter As New DataAccessAdapter()
            e.DbCount = adapter.GetDbCount(e.ContainedCollection, e.Filter)
        End Using
    End Sub

    Protected Sub orderDS_PerformSelect(ByVal sender As Object, ByVal e As PerformSelectEventArgs2) _
        Handles orderDS.PerformSelect

        ' fetch all orders which are in the selected page using the filter passed in
        ' via the PerformSelectEventArgs2 object. 
        Using adapter As New DataAccessAdapter()
            adapter.FetchEntityCollection(e.ContainedCollection, e.Filter, _
                    e.MaxNumberOfItemsToReturn, e.Sorter, e.PrefetchPath, _
                    e.PageNumber, e.PageSize)
        End Using
    End Sub

    Protected Sub orderDS_PerformWork(ByVal sender As Object, ByVal e As PerformWorkEventArgs2) _
        Handles orderDS.PerformWork

        ' Perform the work passed in via the PerformWorkEventArgs2 object. 
        ' Start a new transaction with the passed in unit of work.
        Using adapter As New DataAccessAdapter()
            ' pass the adapter to the Commit routine and tell it to autocommit
            ' when the work is done. 
            e.Uow.Commit(adapter, True)
        End Using
    End Sub
End Class

Setting values for insert/update using bound parameters

ASP.NET supports bound parameters, where you can define parameters which retrieve the values from other controls, cookies, query string etc. One example is given above, using filtering based on the SelectParameters. The LLBLGenProDataSource2 control supports also InsertParameters and UpdateParameters. You can define these parameters for insert (saving a new entity) and update (saving a changed entity) resp. the same way as you do with SelectParameters.

This way you can for example set the 'EmployeeId' on a new order entity where you retrieve the EmployeeId from a dropdown control. The value retrieved through a parameter overrules a set value through the bound control. If no value is passed in by the bound control and it's available through the InsertParameters (when inserting) or UpdateParameters (when updating), the value in the Insert/UpdateParameters collection is chosen.

Converting empty string values to NULL values for inserts/updates

In a web-application, form values which are empty are represented as an empty string (""). When an entity is edited through a form, it can be some textboxes or other controls bound to fields of the entity are left empty / point to an empty value: "". The LLBLGenProDataSource2 control will convert "" into NULL for all fields which .NET type isn't the string type. If the field is the string type, this can give a problem: what if the empty string is a valid value?

To tell the LLBLGenProDataSource2 control that a field should get the empty string as a valid value instead of NULL, you have to pass a List<string> object with all the fieldnames of the fields which should accept "" as the valid value to the property FieldNamesKeepEmptyStringAsValue of the LLBLGenProDataSource2 control. You should do this in the code behind of your webform.

Example

// in your Page Load handler routine
if( !Page.IsPostBack )
{
    var fieldsWhichShouldKeepEmptyString = new List<string>();
    fieldsWhichShouldKeepEmptyString.Add( "ShipAddress" );
    _ordersDS.FieldNamesKeepEmptyStringAsValue = fieldsWhichShouldKeepEmptyString;
}

This example tells the LLBLGenProDataSource2 control called '_ordersDS' that the field ShipAddress should get the value "" instead of NULL if the form value is "" for that field.

Normally you don't need to set the property FieldNamesKeepEmptyStringAsValue, if "" is not used for string values and NULL is acceptable instead.

It can be that the list of names which keep the empty string as the value is actually the complete set of fields of the entity. In that case, you can set the property AllFieldsKeepEmptyStringAsValue to true, which makes the LLBLGenProDataSource2 control to simply not convert empty strings to NULL values for entity fields. Setting this property to true will make LLBLGenProDataSource2 control to ignore FieldNamesKeepEmptyStringAsValue.

The SortingMode property

The LLBLGenProDataSource2 control is capable to apply sorting when data has to be fetched, or for example when you've clicked a column header in a bound GridView control. By default, the LLBLGenProDataSource2 control sorts on the server-side, by producing a SortExpression which is then used by the fetch logic.

Which SortExpression is used depends on the value of the property SorterToUse of the LLBLGenProDataSource2 control and the columns specified by the bound control (e.g. clicked column header). It's possible to tell the LLBLGenProDataSource2 control to sort on the client-side instead. Do this by setting the LLBLGenProDataSource2 property SortingMode.

By default this is set to ServerSide. If you set it to ClientSide, sorting is applied after the fetch, by sorting the DefaultView object of the LLBLGenProDataSource2.

Server-side sorting only uses EntityField2 objects, so if the entity has a field which isn't a field mapped onto a table/view field, it's ignored in the server-side sorting actions because it's not part of the query send to the database. This is also true for fields mapped onto related fields. In these situations, use client-side sorting.

Be aware that if the CacheLocation is set to None, the LLBLGenProDataSource2 control always has to fetch the data from the database again, as it can't sort cached data in-memory. If you want to avoid the roundtrip to the database, set CacheLocation to another value than None.