Interestingly, this is not really a simple question at all
Nor is it at all uncommon.
This question gets at the fundamental OO design philosophy you need to use to succeed with CSLA .NET, and is a topic that is discussed quite frequently, and often at great length, here on the forum.
Remember that your objects should be modeled around behavior, not data.
Or to be more pragmatic: look at your use case and define objects to meet the needs of that specific use case. Don't worry about reuse - it will or won't happen, but is really incidental.
So in your example, the Invoice absolutely does not include or contain a Salesman. A Salesman is its own thing - a standalone object. What an Invoice does include is an AssociatedSalesman (or something like that), which is an object representing the fact that there's a Salesman associated with the Invoice.
What I'm getting at, is that Salesman and Invoice could be data entities, in which case they have a direct relationship. But we're not doing that - we're doing object modeling, and so Salesman is almost certainly an object from the enter-and-edit-sales-person-data use case, while Invoice is from the enter-and-edit-an-invoice use case. They are unrelated.
But the enter-and-edit-an-invoice use case does require that the Invoice be associated with a salesman. So the use case doesn't need the Salesman object - it needs an AssociatedSalesman object.
The responsibility of Salesman is probably to "enter and edit sales person data", while the responsibility of AssociatedSalesman is probably to "associate a sales person with an invoice". Very different responsibilities, that happen to have some overlap in the data they require.
If I may I'll jump in and see if I can give you one possible answer.
here is how I see the "Invoice and Salesman" working from my POV
SalesMan has it's BO that manages Create,Update,Read and so on...
Invoice BO has a child BO InvoiceDetails
Invoice may have olther childern depending on how you need / want it to work, possible childern might include:
ShipTo (an adrress BO - may be filled with data from a Customer BO)
BillTo (an adrress BO - may be filled with data from a Customer BO)
WhoSold / SalesMan may be a child BO or may just be an ID like an INT or a GUID
generally I'd have a NameValueList of SalesMan (ID,Name)
and have the Invoice Rules say that the ID of the sales man must be found in the NameValue List.
but you could if you need to have a child BO that could have more info.
but it does not need to be the full data from the SalesMan BO, like what I gave on ShipTo and BillTo
you could have a "simple" object with a few items and an ID to point back to the root Object
for example ShipTo over time for a customer can chnage, this lets an Invoice show the address at that time.
any BO can have any member of any type.
so a BO can have members that can be other BO's
generaly you would create for the example I gave one or more EditableChild Objects.
in my sample a ShipTo and a BillTo might share a common "InvoiceAddress" object that is based on EditableChild.
that way they can each read default values from an "Account" Object or Database row.
if you did this then your SQL DB will have tables with names like InvoiceBillTo and InvoiceShipTo
that each have an invoice ID to link them to an Invoice.
OR
you could store all the data in the Invoice table and when you create the Invoice Object (BO)
you could populate the child address objects with the data and just use the BO's in memory to help manage and organise the app's OO
each of the two has it's pro's and con's -- you need to evaluate what way is best in your case.
what you gain depends on what you need....
for example:
look at the "Invoice" as a logical "Object" now if you need to drill down and break that into more detaild units then I'd start with
Invoice IS
Header and
Details.
then you say "what's a Header?"
well the ID, the date, the customer, the payment terms, the ship to address, the bill to address and....
and then you can go from there with more details.... about what a header is.
if you load all the details in one object you *CAN* but you may find it more "manageable" and more "Object Oriented" to see that the "Addresses" are sub-objects / child objects of the header.
relate this to for example a "Work Order" which may have
Billing Address, Customer Address, Work-site Address and in some cases more than that... like say a phone company work order may have C/O address, Pole-EquipmentAddress etc...
but all of them could be a common class of "Address" and if you for example have an invoice with ship-to and bill-to that are the same then you only create one object - if you save them in different tables then you might save 1 invoiceaddress record and let the invoice header have ID's
Shipto = 123, billto=123
so that you have 2 references to one object in that case.
this also gives an OO design and view of the thing.
also if you find that invoices in your system ever need more than 2 addesses you could add one more property / filed set to the invoice object and re-use the "address" object and make the chnages to support the new vewrsion minimal.... and keep your app running w/o a huge update.
white911:Thanks Rocky for responding.
I just want to clarify what you mean with AssociatedSalesman object. Is it enough if I add a SalesmanID field in the invoice which would contain just the ID of the salesman and create a method where I can get a the full Salesman object using the ID, or do I create a new object AssociatedSalesman and include it in my Invoice object as:
Matt,
I'm doing something similar to what you described, but instead of having a readonly textbox with a button besides it, my text box is editable and the user is free to typein a textual identification of the wanted object (in this case it would be the salesman number or something).
I bind a salesmanString property of the invoice to this textbox and whatever the user types there I try to fetch from the DB using what the user typed.
I did this because I noticed that many users dont use combo boxes or lookup windows because they already know (or have in hand) the ID or name or code of the needed object, and this allows them to work a bit faster.
Regards
What I'm doing is providing the user the ability to lookup the needed item. Now the user can choose not to lookup the data and instead type it in. I setup a string property and bind it to the textbox, on my set procedure for this property I fetch the read-only object from the db using whatever value typed. Yes, I go back and hot the db again to fetch a ro object. (in my current config this happens very quickly and it does not bother the user much). On my ro objects i've implemented an IsValid property which I set whenever the object is fetched from the db. So if the user types in something not found on the db, I'll have an "invalid" ro object and use it's valid property on my checkrules.
Eventually what I'll do to better the process a bit is to have this in-edit fetch happen within a separate thread so the user does not have to pause not even for the second or two the fetch takes to complete.
Regards,
Mario Estrella
Thanks Rocky.
From: white911 [mailto:cslanet@lhotka.net]
Sent: Thursday, October 05, 2006 4:05 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Newbie simple question
In my case, where would I save the commission a salesman gets for the invoice. The commission % may differ for each invoice. Would I save it in the AssociatedSalesman object or this information (how much commission a salesman gets) belongs in the Invoice object?
White911,
This is the way I've approached a similar situation.
Being a newbie on CSLA also I used a similar technique as the one described by Matt although I think I took things a bit further that what I had to (I think).
I constructed ReadOnly classes to use in RO collections from which the user would choose some BO for editing and/or lookup selection (in your case that would be RO salesmen). That same read-only class I used as the type for the child object of invoice (ie. mInvoiceSalesMan as ROSalesman).
On the invoice editing screen I place a salesman lookup edit control which has the elipsis button Matt described (mine is a looking glass glyph) which launches a search/lookup/select window that displays a ROSalesmen collection in a read-only grid; from that the user has several options. He can simply select one of the salesmen and populate the lookup edit control text box of the invoice screen, he can also press a button to create a new salesman record, or double click an existing salesman to bring up an editor screen where the user views/modifies a Salesman BO.
To the invoice table on the db I only save the associated salesman ID along the other invoice fields. Things get complicated when I need to fetch an invoice BO because now I have to initialize a complete ROSalesman variable to populate the mInvoiceSalesMan member of the invoice BO.
So trying to reuse code this is what I came up with: I made a sql view to represent each of my RO entities (ie. salesman) and use it from the fetch stored procedure to get a list of salesman for my lookup collection. I then re-use that same view in the stored procedure that fetches an invoice object and link it through the salesmanID on the invoice table.
On the DataPortal_Fetch method for the invoice the datareader now has both the natural fields required for the invoice and the complete list of fields required to populate a ROSalesman instance I then just call the "friendly" GetSalesman(dr as safedatareader) method from the ROSalesman class and throw the invoice datareader as it's parameter.
This way I not only have the salesman name but also a full set of it's most relevant data for whatever display purposes I need on the invoice screen.
What I don't like from this approach is that some of the stored procedures (invoice for example) return quite a bit of fields. If bill-to and ship-to were also customer and/or location RO classes their data would also be returned alongside the invoice and salesman data.
But then again, if that is what your use-case requires then OK.
From newbie to newbie ...
Regards,
I don’t know how you are going to end up implementing your solution but you need to keep in mind how the CSLA handles BeginEdit and CancelEdit. Remember that the CSLA does not take a snapshot of an object reference, it only takes a snapshot of the properties of the object.
Assuming that you use the full Salesman object and save the reference to the Salesman object on you Invoice object as:
Private mSalesman As New Salesman
What are you going to do if you need to update the Salesman object after you BeginEdit? Are you going to retrieve a new Salesman object using the factory method and then replace the old reference with the new one? If you do that, what do you thing will happen if you Apply or Cancel Edit? You are not going to get what you expect, the old reference to the Salesman object (where the BeignEdit happened) is long gone.
Copyright (c) Marimer LLC