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
|
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
|
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 SqlCommand, ByVal 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 SqlCommand, ByVal 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
|
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
|
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
|
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
|
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!