Walden H. Leverich wrote:
That's not far off, save two things. First, we generate our classes,
saves us writing all the grunt-code. Second, we've got a single base
class that does the IO. So a class would look something like:
[DatabaseTable("Customer")]
class Customer : BusinessObject {
//define some private fields
[DatabaseField("CustomerId", IsKey=true, OmitFromInsert=true)]
int _customerId;
[DatabaseField("CustomerName", Length=100)]
string _customerName;
[DatabaseField("AddressLine1", Length=100)]
String _address1;
[DatabaseField("LastOrderDate")]
DateTime? _lastOrderDate;
//define some properties
public int CustomerID {
get {return _customerId;}
}
public string Name {
get {EnsureLoaded(); return _customerName;}
set {SetField("_customerName", value);}
}
public string AddressLine1{
get {EnsureLoaded(); return _ AddressLine1;}
set {SetField("_AddressLine1", value);}
}
public DateTime? LastOrderDate {
get {EnsureLoaded(); return _ LastOrderDate;}
set {SetField("_LastOrderDate ", value);}
}
//And define the factory methods
public static Customer GetCustomer(int CustomerId)
{
Customer c = new Customer();
c.LoadFromDB(CustomerId);
return c;
}
public static Customer NewCustomer()
{
return new Customer();
}
}
It appears you allow for a field configuration to be declared?
DatabaseField("CustomerId", IsKey=true, OmitFromInsert=true) I assume
this tells the BaseBusinessObject that there is a database field that is
a key and omit it when generating an insert because it's an identify
field?
What we have done is expand this concept to include many configurable
field attributes and intelligent DatabaseFields such as
"SocialSecurityField", ZipCodeField, AreaCodeField,
CreditCardNumberField which all inherit from a base FIELD object instead
of using standard datatypes such as Strings and ints. This allows for
automatic validation in many cases. For example, when the user enters a
social security value, into a business object it can be auto validated
before the insert or update. So a businessObject has a collection or
list of intelligent fields.
This is where we have seen a lot of value in inheritance although it
tends to be very shallow inheritance. For example: We have a Field
base class with a StringField subclass further subclassed by
ZipCodeField and SocialSecurityField. Each field is soft coded for easy
configuration to include things like: a) is the field required? b)
length, concurrency rules, possible values (pick list) c) Security (who
is authorized to view it or edit it. d) key position e) associations
to other tables etc....
We are then able to create UI generators that given a "businessObject"
are able to dynamically generate a "HTML" interface appropriate for that
object including field sizes, required field attributes, picklists,
security, etc. NOTE: The business code and UI are separate but the UI
code has a consistent model to discover how the UI should be rendered
based on the business rules. For example, if a field is required, then
we want a red marker indicated to the user that they must fill it in.
All database CRUD operations are then automatically enabled without
generating any code. When a insert operation runs, it can generically
extract all UI input, generically validate basic business rules such as
required fields, SSN's, zipcode lengths, etc. This is done by the base
business object looping through it's contained fields and asking each to
validate themselves (this uses polymorphism) I.E. A SSNFIELD overrides
the "validate" method and checks it's data. This leaves the programmer
only needing to program for the exception cases.
For us that's a complete definition of a customer. The customer object
inherits a ton of stuff from BusinessObject, not the least of which are
the methods LoadFromDB, EnsureLoaded, SetField and Save.
LoadFromDB looks up the customer in the Customer table (from the
DatabaseTable attribute) and loads up the data, it's overloaded to
handle single and multiple-key tables.
I understand but how do you allow the programmer to specify what fields
they want included in their "ghost" or SKINNY object? If I say
"Customer.GetCustomer(12345);" how many fields do I get? As you know
many files can have hundreds of fields and bring back too many (for a
subfile) hurts performance.
What we did here is actually leverage the power of SQL something like:
SQLContext sql = new SQLContext("CONNECTION POOL NAME");
sql.setSQL("select a, b, c, d from lib.table where state = "CA"");
sql.setRowCount(100); // page by 100
sql.setBusinessObject(Customer.class); // optional only needed for
special coding is used
sql.setCache(true);
RowCollection rows = DataEngine.getRows(sql);
This allows us to specify what fields to return from what table.
Resultset Metadata is used to discover the fields and identify what
their field configuration data is. If field b is a social security
number then a SSNFIeld is created. The generic "Row" business object
holds all the fields. Row instances are stored in a generic
"RowCollection".
EnsureLoaded makes sure we're not
looking at a "ghost" object, which is a concept we support to delay-load
an object, if it's a ghost is LoadFromDB's it.
We call this the fly weight pattern. Initial subfile list only contains
fields needed for list view but then you can "fill up" the object by
running a SQL to retrieve all fields by key value.
SetField actually sets
the member fields with the new values, but also checks that strings,
byte arrays etc. aren't too long (Length on the DatabaseField
attribute), and maintains a list of changed fields.
Here is where we actually check basic business data such as SSN, zip,
area codes, etc as well. We also check if an updated field value is
really different than the previous and if it is set to not allow
concurrent updates, it gets built into the SQL update (optimistic locking)
And Save would
actually save changes (or insert a new row) into the customer table.
Only columns that have changed (and key columns) are included in the IO
requests (hence the list of changed columns).
We would consume this via:
Customer myCustomer = Customer.GetCustomer(12345);
myCustomer.Name = "Walden";
myCustomer.AddressLine1 = "123 Main st;
myCystomer.Save();
-or-
Customer myCustomer = Customer.NewCustomer();
myCustomer.Name = "Paul";
myCustomer.Save();
Console.WriteLine("New customer Id is: {0}", myCustomer.CustomerId);
What if the addressLine1 was required before save? I.E, it is a
required field and the user failed to put a value in it.
I can see a lot of effort has went into your framework and the value it
gives you and I understand many of the challenges.
The most beneficial technique for us was to stop with the static buiness
object approach and utilize a dynamic business object with a collection
of intelligent fields. This does very much allow for inheritance of
business objects (both at a individual Field level as well as a
businessObject level. Our goal all along was productivity and with this
technique and design pattern, we are able to create simple to fairly
robust web applications with zero coding or code generation. In any
case with certain qualifications I am a firm believer in OO inheritance.
Anyway ... my .02 Thanks, Paul Holm
As an Amazon Associate we earn from qualifying purchases.