OT: Design Question

OT: Design Question

Old forum URL: forums.lhotka.net/forums/t/1629.aspx


mr_lasseter posted on Monday, October 30, 2006

I have the following scenario in an invoice processing system.  I am wondering, what is the best way to go about creating the CSLA business objects.

 

MasterBill – Editable Root

->SubBill – Editable Collection

            ->BillDetail – Editable Collection

 

The MasterBill can contain multiple SubBills which can contain multiple detail items.  The problem is there are several types of detail items (pager, calling card, telephone, etc), thankfully there can only be one type on each MasterBill, but each type has a different information that needs to be displayed with each charge.   My initial thoughts are to have a one MasterBill class and one SubBill class.  I would create 1 BillDetail Base class that deals with loading/persisting all the BillDetail items to/from the database.  The MasterBill class would be responsible for determining which BillDetail type to create. 

 

Is this the right approach?  Thanks in advance.

david.wendelken replied on Monday, October 30, 2006

I'm a bit confused by your example.

Pager, calling card, telephone, etc. sound like customer information instead of billing data - unless you're doing work for a telephone company. :)

Can one customer have more than one master bill?

Is there data on a sub-bill that would also be on a master bill? (other than the master bill id?)

 

 

mr_lasseter replied on Monday, October 30, 2006

I guess I should have included a lot more information.  The system we are writing pays telecom related bills.  We get several types of bills:  telephone, pager, calling cards, etc.  There are basically two ways bills get entered into the system, either manually or imported (via edi, cd, or other billing media).  The bills are broken down in the following way.  Master Number (Invoice Number, Bill Date, Total Amount) which contains one to many Sub Bills (basically just an amount) which contains one to many charges.  Think of it as a sub bill being an actually bill, but the Master Number is just a consolidated account that contains many bills (Sub Bills). 

 

We receive many types of Master Bills. Each master bill will only contain one type of charge (calling card or pager, but not both).  The problem is that each type of charge displays different information.  For example calling cards display total calls and total minutes used where a pager charge only displays total pages. 

 

What is the best way to approach this?

 

MasterNumber -> Editable Root

 

SubBill -> EditableRoot

SubBills -> Editable List of SubBill

 

BaseCharge -> EditableRoot

PagerCharge -> EditableRoot (inherits BaseCharge)

CallingCardCharge ->EditableRoot (inherits BaseCharge)

TelephoneCharge ->EditableRoot (inherits BaseCharge)

 

So the MasterNumber class would contain a collection of subbills which would contain a collection of BaseCharges.  If this is correct then how would you handle this from the front end?  Would you create a different form for each type of charge? 

 

Hopefully this makes more sense.

Bayu replied on Tuesday, October 31, 2006

Hey Mr. Lasseter,

From what I understand you recieve different kinds of bills. And they all have some things in common (like billing date, etc.) but the complexity is that they can contain different kinds of 'billable items' (you call these billdetails I think). So far it makes sense to me.

In my opinion, this would reflect on your DB as follows:
- bill table (with columns for the standard properties like billing date, bill number, etc)
- lineitem table (with a single record for every line in your bill, this table has a foreign key to bill of course)

Lineitems could ismply be a line of text with an associated dollar amount. I don't know what your information needs are, you could of course further normalize your data with another table:
- lineitemdetail table (with a foreign key to lineitem)
    - this would have a 'detailtype' column so you know what is stored on each record (i.e. number-of-pages, or total-minutes)
    - and a column to store values, or perhaps a varchar column and a double column, you choose which one to use depending on the detailtype


Then you also mention the distinction between masterbills and subbills. You said one masterbill could contain one or more subbills, so this would mean another table:
- masterbill
and you alter bill so it has a foreign key to masterbill.



Regarding your UI: I would refrain from building specific views for different kinds of bills/lineitems. The differences would be rather small, and by making your view a bit smarter (dynamic/data-driven) you would avoid a lot of maintenance overhead. For the purpose of dynamic UIs you may have to store additional in your database, I prefer to make use of 'type' tables (e.g. billtype, lineitemtype, etc) which store relevant metadata.

Let's first check if my proposed database model makes some sense. If so, then at least we have a common understanding of what needs to be done. Then we may be able to help you further with how to deal with your data model from the UI side, and subsequently which design of CSLA objects would help you to bridge the gap between your normalized DB model and aggregated UI view.

NB: this datamodel may appear to be directly translatable into CSLA objects, but that's not always the best thing to do. When regarding your CSLA objects as the bridge between your data and UI, you will get most value from CSLA

Regards,
Bayu

mr_lasseter replied on Tuesday, October 31, 2006

You are right on the money with the database.  Although I chose not to normalize the lineitems table since there were only 5 extra fields that needed to support the data for all the different types of line items. 

 

As far as the UI, I like were you are going.  I don't want to create multiple views for each type of bill, but have been stuggling on how to handle multiple items in the UI due to the problems of generics.

 

I appreciate your  help.

 

Thanks,

Mike

Bayu replied on Tuesday, October 31, 2006

Allright,

If it is just 5 fields and you also don't expect this number to grow much (at least for now), then let's not make it more complicated than necessary.

EDIT: I could not resist, so in the sidenote below I included how you could further normalize your relational model, just in case ;-)

At this point you have a three-level hierarchy:
- master bill --> bill --> lineitem

What you could do in order to make your view database-driven, is add some metadata tables:
- lineitemtype
    - will tell you what kind of lineitem you are dealing with
    - lineitem would be altered to have a foreign key to this table
- lineitemconfig
    - will tell you which fields are applicable for a certain lineitemtype
    - so it has a foreign key to lineitemtype
    - the idea is that this table holds a single record per field that applies
    - so lineitemtype -> lineitemconfig is one to many
    - it would refer to a field by name, using a varchar column

[begin sidenote]
If you want to play it neat, you could normalize your model further:
- lineitemdetail
    - 'child' table of lineitem, so has a foreign key to lineitem.
    - you would drop your 5 columns in lineitem, and bring them back as records in here instead

The metadata would be extended accordingly:
- lineitemdetailtype
    - similar to lineitemtype, but now for lineitemdetail
    - lineitemdetail has a foreign key to lineitemdetailtype
    - there is no need for a foreign key from lineitemdetailtype to lineitemtype, so I would omit it. Up to you.
- lineitemconfig would alter:
    - it would not refer to particular fields by name (error prone)
    - but refer to lineitemdetailtype by a foreign key
[end of sidenote]

Anyways, in summary:
- you extend lineitem (and lineitemdetail) with metadata
- in lineitemconfig you specify which combinations of lineitemtypes and lineitemdetailtypes are valid



Now your UI:
- when you want to render a particular lineitem
- you can query the DB for the applicable details following the lineitemtype foreign key
- based on the records that are returned you can render appropriate controls to display the details
- of course your DB also tells you which details to show (either by name or by foreign key, depending on how far you push your normalization)
- anyhow, you should be able to acquire the name and value of the lineitemdetail
- in case your choose your DB to store the property name, you can use reflection to access that particular property on your BO. (I could tell you how, let me know)

If you want to edit or insert a new lineitem, you can use the same technique:
- you could even make your lineitemdetailtype table store the type of control to use for editing the particular detail (e.g. textbox, checkbox, datetimepicker or numericupdown)



It's quite a detailed how-to, but what it brings to you is a fully data-driven view for displaying and/or editing your lineitems. Support for new types of lineitems, with new types of details or a different subset of details is then trivial and would only need modifications in your database. The UI follows suit.


I  hope the design I propose is somewhat clear to you. I successfully implemented something like this, so I could provide you with further specific details when needed.

Regards,
Bayu

mr_lasseter replied on Tuesday, October 31, 2006

OK.  If I understand you correctly my business objects would look like such (without the sidebar comment):

MasterBill à Contains a Collection of SubBills à Contains a collection of LineItemCharges

 

Then the LineItemCharges class would have properties of all the fields in the LineItemCharges table.    I would than create another class LineItemChargesDisplay whose sole responsibility is to determine what properties need to be displayed depending on what type of charge it is. 

 

If I chose to implement your sidebar quote, what would be the performance impact?  It seems like it would add significant overhead when loading a bill to display especially if the bill had a lot of records.  Also how would I go about loading the records to display since the LineItemCharges class would not have all the properties that are contained in the database?

 

Thanks,

Mike

 

Bayu replied on Tuesday, October 31, 2006

Sounds about right to me. :-)

Regarding your proposed LineItemChargesDisplay class, you would be one option. This would not really be a businessobject, merely a helper class that serves your UI.

Regarding my sidenotes: the performance impact should be minimal:
- your metadata would only need readonlybase-objects, or perhaps even simple namevaluelists
- I prefer to cache these once they are fetched (it's easy to hide a shared hashtable behind the factory method and use the hashtable as a cache, you could also provide a shared ClearCache member that you invoke when you know your metadata was updated), so database hits are minimized
- further normalization should never cause severe performance loss. Your queries will have additional inner joins, true, but you may expect your database system to have query optimizers that handle this fine (the more joins, the more to merges but also the more possible parallellizations). I know of a database system (Monet) that behind the scenes always fully vertically fragments everything in order to maximize query evaluation.

There are several possibilites in order to deal with a varying number of bill-details:
- merge all details into one concatenated (or otherwise formatted) string property. It's the quick and dirty way, but you see it on many invoices. In this case you would be writing some reflection code in your BO. You could even extend this approach by defining how to merge your details by adding a column to your lineitemtype table.
- another approach would be to apply one of the central rules of thumb of DBAs: all dynamics should always go in vertical direction, i.e. in the number of records. Translated in UI terms: display the lineitem details in a separate detail-view (ugly) or use Infragistics Grid (costs some bucks, but works beautifully) which supports sub-tables. (sort of marrying a treeview with a gridview).

I use the former for my Contact object. Contacts have a FirstName, LastName, Title, etc. They also have a readonly property FullName which is derived from the other properties and which I use for my list-of-contacts-view.
I also use the latter for my Product Configurator view (highly dynamic, different producttypes with each their own set of aspects and each aspect can have one of a set of values). This view is based on the mentioned Infragistics Grid.

Bayu

pirithoos replied on Wednesday, November 01, 2006

Hi Bayu

- in case your choose your DB to store the property name, you can use reflection to access that particular property on your BO. (I could tell you how, let me know)

For me it would be of great interest if you could explain this reflection issue.

Frank

Bayu replied on Wednesday, November 01, 2006

pirithoos:
Hi Bayu

- in case your choose your DB to store the property name, you can use reflection to access that particular property on your BO. (I could tell you how, let me know)

For me it would be of great interest if you could explain this reflection issue.

Frank



Here you go.

I copied (and modified) an earlier post of mine in another thread.

Assume you have a variable oObject of arbitrary type (eg Object)

- TypeDescriptor.GetProperties(oObject.GetType) will return a PropertyDescriptorCollection
- each PropertyDescriptor in this PropertyDescriptorCollection provides metadata about a particular property
- with a PropertyDescriptor at hand, you can obtain a PropertyInfo object using
dim oInfo as PropertyInfo = oObject.GetType.GetProperty(<somePropertyDescriptor>.Name)
- then, using dim oValue as Object = oInfo.GetValue(oObject, Nothing) you get the particular value of the particular instance

When you retrieve a property name from your DB, you could skip the PropertyDescriptor stuff and try to obtain a PropertyInfo object directly. Note that the PropertyInfo is not tied to a particular instance of an object (it's just metadata), so that's why you need to provide an instance (i.e. oObject) to the GetValue member.


- additionally, if you want to use type conversions then better make use of the mechanisms that also drive the PropertyGrid control, i.e.: TypeConverter
- you get a type converter using 
dim oConverter as TypeConverter = somePropertyDescriptor.Converter
or when you don't have the PropertyDescriptor at hand, use this one:
dim oConverter as TypeConverter = TypeDescriptor.GetConverter(pInfo.PropertyType)

- to check if it can convert property values to a particular target type, check oConverter.CanConvertTo(GetType(String)) (replace String with anything you need)
- if this returns true, you can perform the actual conversion with:
dim sValue as String = DirectCast(oConverter.ConvertTo(oValue, GetType(String)), String)


This is pretty powerfull stuff. Using these objects (descriptors, converters and metadata) you have all the tools at hand to create dynamic UIs.

Good luck and have fun! ;-)
Bayu

pirithoos replied on Wednesday, November 08, 2006

Bayu,

thanks a lot. Will follow up your instructions shortly...

brgds,

Frank

Copyright (c) Marimer LLC