ComponentOne DataObjects for .NET
Tutorial 2: Defining Business Logic
DataObjects for .NET (Enterprise Edition) > DataObjects for .NET Tutorials > Tutorial 2: Defining Business Logic

In this tutorial, you will learn how to:

In the previous tutorial, we defined and used data objects in the same project. This is not how DataObjects for .NET is supposed to be used. A better way to use DataObjects for .NET is to define your business objects (data objects) in a separate assembly, data library, so it can be used by multiple applications. Although this is not mandatory, your enterprise can assign a special team of "data-oriented" developers to the task of creating business object projects (data libraries) and another team of "GUI-oriented" developers to creating client applications using data libraries. This is how DataObjects for .NET enables you to benefit from the right design and development technology. However, DataObjects for .NET fits equally well into smaller development projects, where business objects and client applications are developed by the same team. The main architectural benefit of using DataObjects for .NET is in clear separation between business logic and presentation layer (GUI). Encapsulating your data model and logic in a centralized place (data library) that can be reused by multiple clients makes your applications robust, scalable and fun to develop. This is not to mention numerous tools and enhancements DataObjects for .NET adds to your data access toolbox, including such a powerful and extremely important one as virtual mode (cached access to large recordsets), see Tutorial 4: Virtual Mode: Dealing with Large Datasets.

A data library is an assembly (DLL) containing DataObjects for .NET schema and business logic components and code that defines your data objects. These data objects can then be used in any project by simply referencing the library in the project and using a DataObjects for .NET C1DataSet component to connect to the library. All database access and business logic code is encapsulated in the library, so it can be created and maintained independently of client applications.

  1. Create a new Data Library project:
    1. Select File | New Project in the Visual Studio menu, and in the New Project dialog box under Project types, choose either Visual Basic or Visual C#, according to your language preference. Note that one of these options may be located under Other Languages.
    2. Select ComponentOne Data Library from the list of Templates in the right pane.
    3.   Enter Northwind in the Name text box, specify a location and click OK.
      The Northwind data library project is created for you. The main file of the resulting data library project is DataClass.vb (.cs), a component class where you will host the schema and business logic. Note that in large projects, it is also possible to distribute business logic code over multiple files. A C1SchemaDef component is automatically added to DataClass.
  2. Select the SchemaDef1 component and set the DataObjectsAssemblyFlags property to None in the Properties window. By doing this, we avoid creating a separate namespace for each dataset definition in the automatically generated dataobjects assembly.
  3. Right-click SchemaDef1 and choose Schema Designer from the context menu.

    The Schema Designer opens and the Import Wizard appears.
  4. We will use the schema created in Tutorial 1: Creating a Data Schema, so click Cancel to close the Import Wizard.
  5. Select File | Open in the Schema Designer menu and open the schema file that was saved in Tutorial 1: Creating a Data Schema. The schema appears in the designer.
  6. Close the Schema Designer and click Yes to save the changes.
  7. Compile the data library project by selecting Build | Build Solution.

To define business logic:

With the schema now in place, we can specify business logic. You already saw some elements of business logic, namely constraint expressions and calculation expressions, in Tutorial 1: Creating a Data Schema. Expressions are an easy and straightforward way of defining business logic. However, when expressions are not enough, some code must be written. In this tutorial, we will show how to write business logic code.

Every schema object can be represented by a special business logic component. Business logic components (components C1.Data.C1TableLogic and C1.Data.C1DataSetLogic) have events where you can write code responding to various occurrences in data objects.

  1. Right-click the C1SchemaDef component and select Create Business Logic Components from the context menu. This creates a business logic component for each table and each data set in the schema. We only need three of them, so delete all but the following three components: table_Customers, table_Order_Details, and dataset_ProductsOrders.

    Alternatively, you can create the three business logic components manually from the Visual Studio Toolbox by adding two C1TableLogic components and one C1DataSetLogic component. Then you must set each of their properties: set SchemaComponent to SchemaDef1, C1TableLogic1.Table to Customers, C1TableLogic2.Table to Order Details and C1DataSetLogic1.DataSetDef to ProductsOrders.
  2. Add the following code to create an event handler table_Customers_BeforeFieldChange and convert the proposed CustomerID value to upper case:

    To write code in Visual Basic

    Visual Basic
    Copy Code
    Private Sub table_Customers_BeforeFieldChange(ByVal sender As Object, ByVal e As C1.Data.FieldChangeEventArgs) Handles table_Customers.BeforeFieldChange 
        If e.Field.Name = "CustomerID" Then
            e.NewValue = CStr(e.NewValue).ToUpper()
        End If
    End Sub
    

    To write code in C#

    C#
    Copy Code
    private void table_Customers_BeforeFieldChange(object sender, C1.Data.FieldChangeEventArgs e) 
    {
        if (e.Field.Name == "CustomerID")
            e.NewValue = ((string)e.NewValue).ToUpper();
    }
    

    This code will be executed each time the value of a field in the Customers table is about to change. As with all other DataObjects for .NET business logic events, it is triggered at any attempt to change a field of any object, be it a table view or a composite table, that would change a Customers table field. So the logic we put here will be enforced in any object in any context where the Customers table is involved.
  3. Add the following code to create an event handler table_Order_Details_BeforeFieldChange:

    To write code in Visual Basic

    Visual Basic
    Copy Code
    Private Sub table_Order_Details_BeforeFieldChange(ByVal sender As Object, ByVal e As C1.Data.FieldChangeEventArgs) Handles table_Order_Details.BeforeFieldChange 
        If e.Field.Name = "Quantity" Then
            If (CShort(e.NewValue) < 1) Then
                e.NewValue = CShort(1)
            End If
        ElseIf e.Field.Name = "Discount" Then
            If CSng(e.NewValue) < 0 Or CSng(e.NewValue) > 1 Then
                Throw New ApplicationException("Discount must be between 0 and 1")
            End If
    End If
    End Sub
    

    To write code in C#

    C#
    Copy Code
    private void table_Order_Details_BeforeFieldChange(object sender, C1.Data.FieldChangeEventArgs e)
    {
        if (e.Field.Name == "Quantity")
        {
            if ((short)e.NewValue < 1)
                e.NewValue = (short)1;
        }
        else if (e.Field.Name == "Discount")
        {
            if ((float)e.NewValue < 0 || (float)e.NewValue > 1)
                throw new ApplicationException("Discount must be between 0 and 1");
        }
    }
    

    The code for Quantity turns a negative value to 1 before it is assigned to the Quantity field of the Order Details table.
    The code for Discount tests a constraint 0 < Discount < 1 and shows a message if it is not satisfied. It is executed before changing the Discount value.
  4. Add the following code to create an event handler table_Order_Details_AfterFieldChange:

    To write code in Visual Basic

    Visual Basic
    Copy Code
    Private Sub table_Order_Details_AfterFieldChange(ByVal sender As Object, ByVal e As C1.Data.FieldChangeEventArgs) Handles table_Order_Details.AfterFieldChange
        If e.Field.Name = "Quantity" Then
            Dim orderDetail As Order_DetailsRow, product As ProductsRow
            Dim oldValue, unitsOrdered As Short
            orderDetail = Order_DetailsRow.Obj(e.Row)
        product = orderDetail.GetProductsRow()
            If Not (product Is Nothing) Then
                If e.OldValue Is Convert.DBNull Then
                    oldValue = CShort(0)
                Else
                    oldValue = CShort(e.OldValue)
                End If
                unitsOrdered = CShort(CShort(e.NewValue) - CShort(oldValue))
            End If
            product.UnitsInStock = CShort(product.UnitsInStock - unitsOrdered)
            If product.UnitsInStock < 0 Then
                product.UnitsInStock = 0
            End If
            product.UnitsOnOrder = product.UnitsOnOrder + unitsOrdered
        End If
    End Sub
    

    To write code in C#

    C#
    Copy Code
    private void table_Order_Details_AfterFieldChange(object sender, C1.Data.FieldChangeEventArgs e) 
    {
        if (e.Field.Name == "Quantity")
        {
            Order_DetailsRow orderDetail = Order_DetailsRow.Obj(e.Row);
            ProductsRow product = orderDetail.GetProductsRow();
            if (product != null)
            {
                short unitsOrdered = (short)((short)e.NewValue -
                    (e.OldValue == Convert.DBNull ? (short)0 : 
                    (short)e.OldValue));
                product.UnitsInStock = (short)(product.UnitsInStock - unitsOrdered);
                if (product.UnitsInStock < 0)
                    product.UnitsInStock = 0;
                product.UnitsOnOrder += unitsOrdered;
            }
        }
    }
    

    This code executes after the value of the Quantity field in the Order Details table has changed. It makes necessary changes in the corresponding Products row. 

    Click here for an explanation of some important features in this code.

    There are two data object classes generated by DataObjects for .NET in this code: Order_DetailsRow and ProductsRow. Class Order_DetailsRow represents an Order Details row, and ProductsRow represents a Products row. Such classes are automatically generated and maintained for each table and table view in the schema. For example, ProductsRow is an object (business object, data object) where each field has a corresponding property (ProductsRow.UnitPrice, ProductsRow.UnitsInStock, and so on), and each relation has a corresponding method (Products.GetOrder_DetailsRows, and so on), allowing you to obtain child rows and the parent row.

    Using these classes, you can write business logic code in a convenient, type-safe way, and benefit from Visual Studio code-completion features giving you the lists of properties and methods to choose from.

    The data object classes belong to the Northwind.DataObjects namespace (substitute your data library name instead of Northwind, if different). They are hosted in the assembly Northwind.DataObjects.dll. This data objects assembly is generated by DataObjects for .NET each time you change the schema and save it in the C1SchemaDef component. A reference to this assembly is added to your data library project References.


  5. Create an event handler dataset_ProductsOrders_AfterEndAddNew and enter the following code:

    To write code in Visual Basic

    Visual Basic
    Copy Code
    Private Sub dataset_ProductsOrders_AfterEndAddNew(ByVal sender As Object, ByVal e As C1.Data.RowChangeEventArgs) Handles dataset_ProductsOrders.AfterEndAddNew
        If e.DataTable.SchemaTable.Name = "CustOrdersDetails" Then
            Dim order As CustOrdersDetailsRow_tableView
            order = CustOrdersDetailsRow_tableView.Obj(e.Row)
            If order.IsShipNameNull() Then order.ShipName = order.CompanyName
            If order.IsShipAddressNull() Then order.ShipAddress = order.Address
            If order.IsShipCityNull() Then order.ShipCity = order.City
            If order.IsShipRegionNull() Then order.ShipRegion = order.Region
            If order.IsShipPostalCodeNull() Then order.ShipPostalCode = order.PostalCode
            If order.IsShipCountryNull() Then order.ShipCountry = order.Country
        End If
    End Sub
    

    To write code in C#

    C#
    Copy Code
    private void dataset_ProductsOrders_AfterAddNew(object sender, C1.Data.RowChangeEventArgs e)
    {
        if (e.DataTable.SchemaTable.Name == "CustOrdersDetails")
        {
            CustOrdersDetailsRow_tableView order = CustOrdersDetailsRow_tableView.Obj(e.Row);
            if (order.IsShipNameNull())
                order.ShipName = order.CompanyName;
            if (order.IsShipAddressNull())
                order.ShipAddress = order.Address;
            if (order.IsShipCityNull())
                order.ShipCity = order.City;
            if (order.IsShipRegionNull())
                order.ShipRegion = order.Region;
            if (order.IsShipPostalCodeNull())
                order.ShipPostalCode = order.PostalCode;
            if (order.IsShipCountryNull())
                order.ShipCountry = order.Country;
        }
    }
    

    This code executes after a new row has been added to the CustOrdersDetails table view, and all primary key fields have been specified, so the row is no longer a "temporary new row", meaning the primary key is yet undefined. This code fills shipping attributes given known values of billing attributes. Apart from another example of using data object classes in business logic code, this code shows yet another important DataObjects for .NET feature, specifically:
    You can specify business logic on the table level, as shown in the previous examples, and then it will be executed wherever this table is involved. But you can also specify business logic on the dataset level, in other words, on the table view level, as in this example. Such code will be executed only in this dataset. Using dataset-level business logic, you can enforce rules that are specific to a certain data set.  

    Click here for a list of business logic events.

    This tutorial demonstrates only a limited number of business logic events. Here is a brief list of business logic events available in DataObjects for .NET (we omit prefixes Before and After pertaining to most events, retaining the prefix only if an event occurs only Before or only After):

    Event Description
    AddNew Fired when a new (empty) row is added.
    AfterChanges Fired when all changes initiated by a field change are done and handled by the business logic code, see the FieldChange event.
    BeginEdit Fired when the user starts editing a row (data-bound controls start editing a row immediately after they position on it, even though no changes have been made yet).
    CancelEdit Fired when the user cancels editing a row reverting the changes made to it.
    Delete Fired when a row is deleted.
    EndAddNew Fired when a newly added row becomes a regular row in the rowset. When a row is added, it is added empty, its primary key is unknown. A row with unknown primary key is in special transitory state, it is not a regular rowset row. Only after its primary key is set it becomes a regular (added) row, which is signaled by this event.
    EndEdit Fired when the user finishes editing a row (data-bound controls finish editing a row when they leave that row, even if no changes have been made).
    FieldChange Fired when a field value is set. Inside this event, your code can set other fields triggering recursive FieldChange events, DataObjects for .NET handles this situation correctly. Only after all changes are done and handled, AfterChanges event is triggered.
    FirstChange Fired when a first change is made to the row (a field value changed) after BeginEdit.
    UpdateRow This event is not fired in a client application, unless it is a direct client, that is a 2-tier application updating the database directly from client, see Tutorial 3: Creating Distributed 3-Tier Applications. In a 3-tier deployment, it is fired only on the server, when a modified row is committed to the database.
  6. Compile the Northwind project. Now the data library can be used in a client application.
  7. Create a new Windows Application project.
  8. Select Project | Add Reference:
    1. In the Add Reference dialog box, click the Browse tab.
    2. Locate and select Northwind.dll and click OK. The Northwind.dll assembly is added to your client project. The Northwind DLL should be located in the bin folder in the Northwind project directory.
  9. Place the following components on the form as shown in the figure:
    Number of Components Name Namespace
    1 C1DataSet C1DataSet1 C1.Data.C1DataSet
    2 C1TrueDBGrid C1TrueDBGrid1
    C1TrueDBGrid2
    C1.Win.C1TrueDBGrid.C1TrueDBGrid
    1 command Button Button1 System.Windows.Forms.Button

  10. Set the Button1.Text property to Commit Changes.
  11. Select the C1DataSet1 component and set the following properties:
    1. Enter Northwind in the DataLibrary property
    2. Select ProductsOrdersfrom the drop-down list of the DataSetDef property.
      These settings connect C1DataSet1 to the ProductsOrders data set of the data library. Now we can use the C1DataSet1 component as a data source for data-bound GUI component grids.
  12. Open the C1TrueDBGrid Tasks menu for each C1TrueDBGrid and select C1DataSet1 under Choose DataSource.
  13. Set the DataMember properties of C1TrueDBGrid1 and C1TrueDBGrid2 to CustOrdersDetails and ProductOrderDetailsCust, respectively. Click Yes to replace the existing column layout.
  14. Double-click Button1 to create the Button1_Click event. In order to be able to send data modifications to the database, add the following code to the Button1_Click event:

    To write code in Visual Basic

    Visual Basic
    Copy Code
    C1DataSet1.Update()
    

    To write code in C#

    C#
    Copy Code
    c1DataSet1.Update();
    
  15. As in Tutorial 1: Creating a Data Schema, add the following code to create the c1DataSet1_BeforeFill event handler to restrict (filter) the data set:

    To write code in Visual Basic

    Visual Basic
    Copy Code
    Private Sub C1DataSet1_BeforeFill(ByVal sender As Object, ByVal e As C1.Data.FillEventArgs) Handles C1DataSet1.BeforeFill 
        Dim dataSetDef As C1.Data.SchemaObjects.DataSetDef
    dataSetDef = e.DataSet.Schema.DataSetDefs("ProductsOrders")
        e.Filter.Add(New C1.Data.FilterCondition(dataSetDef.TableViews("ProductsOrderDetailsCust"), "[CategoryID] = 1"))
        e.Filter.Add(New C1.Data.FilterCondition(dataSetDef.TableViews("CustOrdersDetails"), "[CategoryID] = 1"))
    End Sub
    

    To write code in C#

    C#
    Copy Code
    private void c1DataSet1_BeforeFill(object sender, C1.Data.FillEventArgs e) 
    {
        C1.Data.SchemaObjects.DataSetDef dataSetDef = e.DataSet.Schema.DataSetDefs["ProductsOrders"];
        e.Filter.Add(new C1.Data.FilterCondition(dataSetDef.TableViews 
       ["ProductsOrderDetailsCust"], "[CategoryID] = 1"));
        e.Filter.Add(new C1.Data.FilterCondition(dataSetDef.TableViews 
       ["CustOrdersDetails"], "[CategoryID] = 1"));
    }
    

Run the program and observe the following: