CSLA : Step-by-Step, Complex Data Types & some gotchas

Friday, 5 October 2007 16:00 by pieter

Welcome to the third instalment of CSLA: Step-by-Step. In the previous 2 parts we looked at which tools to use to get started and creating business objects with parent-child relationships.

In this part we'll do some pretty cool stuff with CSLA as well as tackling one or two gotchas that pops up every now and again.
All right, let's get cracking. Open our NorthwindTraders solution, we created in part 1. If you followed the previous two tutorials you should already have the following classes in your project:

  • Customer (BusinessBase/EditableRoot)
  • Database (Module)
  • Order (BusinessBase/EditableRoot)
  • OrderDetail (BusinessBase/EditableChild)
  • OrderDetails (BusinessListBase/EditableChildList)

The Gotchas

One gotcha that had me scratching my had every now and again is when you try to create a new object i.e. Order.NewOrder(), you get the following error:

Csla.DataPortalException : DataPortal.Create failed (System.NotSupportedException: Invalid operation - create not allowed

You can test this by adding the following code to your OrderUnitTests class in your Northwind solution's Order.vb :

<Test()> _
Public Sub CreateOrder()
    Dim objOrder As Order = Order.NewOrder
    objOrder.CustomerID = "ALFKI"
    objOrder.EmployeeID = 1
    objOrder.ShipVia = 1
    objOrder.Save()
End Sub
Generated using PrettyCode.Encoder

At first it seems like an extremely weird error, but the solution and explanation is actually very easy. You'll notice that your object's DataPortal_Create, expect a criteria parameter of type object. Remove the parameter from and test your code again.Viola! it works.

You'll notice this error only happens when you have an object for a table that has a primary key that also has an Identity Specification. The code generated expects you pass the primary key/identifier for the object, but when it is assigned by the database, you obviously wouldn't need to. The Customer object in our Northwind Solution, will work without any hiccups, because you have to specify the  CustomerID when creating a new Customer.

Another gotcha is when you manually add a child property to your business object. You have to change the IsValid and IsDirty property of your object to include the new child object. Also make sure you change your Data Access code to include the new child object. The easiest way this can be overcome by just re-generating your business object and including both child object properties.

The Cool Stuff

Right, let's get to the nifty bit.

Create a new BusinessBase/EditableRoot object called Product. Use CodeSmith to generate it from the Northwind Products table and MyGeneration to generate the sql stored procedures. See part 1, if you need to know how.
Your CodeSmith property grid should look something like this:

We are going to change the ProductID property on you OrderDetail object to be of type Product and not Integer. This will give us some pretty cool and powerful flexibility later on.

First open your OrderDetail class. Change the ProductID variable and property to look like this:

    Private _product As Product = Nothing

    Public Property Product() As Product
        Get
            CanReadProperty("Product"True)
            Return _product
        End Get
        Set(ByVal value As Product)
            _product = value
        End Set
    End Property
Generated using PrettyCode.Encoder

Change FetchObject, AddInsertParameters and AddUpDateParameters to look like this:

 Private Sub FetchObject(ByVal dr As SafeDataReader)
        _orderID = dr.GetInt32("OrderID")
        _product = Product.GetProduct(dr.GetInt32("ProductID"))
        _unitPrice = dr.GetDecimal("UnitPrice")
        _quantity = dr.GetInt16("Quantity")
        _discount = dr.Item("Discount")
    End Sub

 Private Sub AddInsertParameters(ByVal cm As SqlCommandByVal parent As Order)
        cm.Parameters.AddWithValue("@OrderID"parent.OrderID)
        cm.Parameters.AddWithValue("@ProductID"_product.ProductID)
        cm.Parameters.AddWithValue("@UnitPrice"_unitPrice)
        cm.Parameters.AddWithValue("@Quantity"_quantity)
        cm.Parameters.AddWithValue("@Discount"_discount)
    End Sub

    Private Sub AddUpdateParameters(ByVal cm As SqlCommandByVal parent As Order)
        cm.Parameters.AddWithValue("@OrderID"_orderID)
        cm.Parameters.AddWithValue("@ProductID"_product.ProductID)
        cm.Parameters.AddWithValue("@UnitPrice"_unitPrice)
        cm.Parameters.AddWithValue("@Quantity"_quantity)
        cm.Parameters.AddWithValue("@Discount"_discount)
    End Sub
Generated using PrettyCode.Encoder

Instead of passing the ProductID variable, we're passing the Product object's ProductID property and we're fetching a Product object rather than an integer value.

In theory we can now also change our UnitPrice property to a readonly property and return the Product.UnitPrice, like so:

    Public ReadOnly Property UnitPrice() As Decimal
        Get
            CanReadProperty("UnitPrice"True)
            Return _product.UnitPrice
        End Get
    End Property
Generated using PrettyCode.Encoder

Note: You can then also change you data access code, practically you would no longer need to save the unit price on the OrderDetail level. This would all depend on the design of your application. For now, we'll just assume, we no longer require the unit price on the order detail, since we have it on the product level.

Now we need to see if the changes we've made actually works. Let's create our Order with Details Unit test, it can look something like this:

 <Test()> _
        Public Sub CreateOrder()
            ' We Get the Product we want to add to this order
            ' our client is a fan of Grandma's Boysenberry Spread so he's ordered a 100
            Dim objProduct As Product = Product.GetProduct(6)

            ' We create the order
            Dim objOrder As Order = Order.NewOrder
            objOrder.CustomerID = "CHOPS"
            objOrder.EmployeeID = 1
            objOrder.ShipVia = 1

            ' We create the Order Detail
            Dim objDetail As OrderDetail = OrderDetail.NewOrderDetail
            objDetail.Product = objProduct
            objDetail.Quantity = 100

            ' Add the OrderDetail to the order
            objOrder.Orderdetails.Add(objDetail)

            ' Save the Order
            objOrder.Save()

            ' Display our new orderID
            Console.Write(objOrder.OrderID.ToString)
        End Sub
Generated using PrettyCode.Encoder

Note: I've add a constructor to the OrderDetail object to make things easier. Also when you compile there will be a few errors complaining about the _productID, remember it's now the _product.ProductID.Here's the code for the constructor:

    Friend Shared Function NewOrderDetail() As OrderDetail
        Return New OrderDetail()
    End Function

    Private Sub New()
        ValidationRules.CheckRules()
        MarkAsChild()
    End Sub
Generated using PrettyCode.Encoder

I've made a couple of changes to the code, that I did not mention here, you can download the files here.

I hope part 3 was of use, tune in next time for some Data Binding Fun with CSLA.

Happy Coding!

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5
Tags:   ,
Categories:   CSLA
Actions:   E-mail | Permalink | Comments (0) | Comment RSSRSS comment feed