I've been trying to revise the structural design of the C#.NET-based system I'm currently working on. The new design involves a rather light-weight object-relational mapping framework (we're trying to stay away from ADO or linq2sql). Sorry, this came rather lengthy, but I cannot describe it shorter - it's a bit complex.
The system has two primary functions:
Retrieve rows from an SQL relational disk-based database, then serve them up using
[DataContract]-marked objects via ASP to a web-based client, as well as receive UPDATEs to those and reroute them to the database.
Read off and serve the same kinds of objects to server-side / distributed modules which work with these, generate some result, and push it back to the database.
The results (some might ask: "why're you storing generated data?") of module computation do have to be stored in the database due to both size and length of computation required to obtain them, only to be retrieved in parts by the web-client through the (1) function.
Currently, all row-representing objects are defined as separate C# classes with a common trend but little to no inheritance, and no common base class. I want to completely break this pattern, and instigate more extensive inheritance based on a tree of abstract classes and possibly interfaces.
I've whipped up a "due-for-test" implementation in the past few days, but I'm concerned about the sanity of this design. Obviously, there is the object-relational impedance mismatch, which I deal with by only initializing the database-column-backed fields unless the other encapsulated objects are provided to the constructor. I'm not so concerned about that, and it's an issue for the old system design as well.
Here's what I have so far:
DatabaseColumnAttribute class, which targets fields, and defines
- name of the column where the data for the field resides.
- database type (enum-backed locally) for this column. There's a fixed set of database types this system is designed to support, so be it.
- whether or not the column is nullable.
- read phrase(i.e. how do we represent this column in a SELECT query within an sql statement): this is generated automatically based on column type. There's a fixed number of types the data can be accessed in, so if there's a conversion involved when reading data from a column this will include it. Otherwise, just the column name.
- write wrapper (i.e. database function that converts the data sent into the column from C# to the appropriate representation) - also generated based on column type.
All this is used within a
DatabaseFieldColumnWrapper object, which contains all the methods required to transfer the data from the column to the field or vice-verse.
DatabaseFieldColumnWrapper is constructed for each location in the code where a
DatabaseColumnAttribute is used, meaning it's not quite "static", but rather class-based, since it modifies fields of derived classes. The fields can back properties, there's a
BackedByFieldAttribute class as well, I'll skip it for brevity.
All of these are used within an abstract
DataElement class, root of hierarchy.
It defines the following abstract members: function that provides the backing-table's name for this class of objects, a read-only public Id property, a private function to set the Id (which corresponds to the primary key of the row in the table).
It also provides a protected static helper function to retrieve the
DatabaseFieldColumnWrapper objects for specific fields based on Linq Expressions (to avoid "magic strings"). This can be used in derived types to easily synch the database representation for specific fields/columns.
It provides a
Delete() method, which is (not a destructor, but a function) responsible for only deleting the database representation. The whole system is designed to allow asynchrony and multiple memory representations unless otherwise specified.
Here is the "less sane" part:
There is a
DataElementAttribute class, which is designed to modify non-abstract derived types. It is not fully constructed completely right away where it's defined, instead filled-in in a thread-safe manner upon the construction of the first object of a derived type. It not inherited, and contains only data specific to the derived class required to speed up construction (i.e. construct all the necessary
DatabaseFieldColumnWrapper objects before-hand and associate them with the fields).
This basically violates the C# OO model, since it's "static only with respect to current class in the hierarchy", not static to the whole chain. Basically, it makes the
DataElmentAttribute object associate with a particular Type object. It's like a convenience structure, which is generated according to the rules specified in the base class only, but initializes its members based on the fields marked as columns for each derived type. Each instance of that particular type has a reference to it.
I don't know how to circumvent this issue, it seems to be best in terms of "code reuse", but it also seems like I'm violating a lot of things I shouldn't be when I do it. Any ideas, suggestions?
In a more "question"-form: do you know of any better way to associate things with Types (without inheritance) and have these accessible from each instance of that Type, which conforms to the OO model?
P.S. I'm not looking for a "you shouldn't" kind of answer, since that wouldn't allow us to reuse more code.