4

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:

  1. 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.

  2. 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:

A 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.

The 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.

1
  • When downvoting the question, please explain the reason for the downvote. It will help both the author of the question to improve it, and the readers to understand what's wrong with it. Feb 15, 2012 at 20:25

1 Answer 1

2

If you're looking for dynamic runtime polymorphism, have you checked out the dynamic keyword in C# 4.0? If it meets your need, I would imagine it's a lot simpler than what you're trying to do.

See Rob Conery's work with Massive, where he demonstrates how to build a dynamic ORM in C# 4.0 using 400 lines of code.

Creating proxy classes at runtime is not exactly an unprecedented technique, see Castle Dynamic Proxy and FasterFlect. If your only worry is that you're breaking some architectural austronaut's rule book, I'm not sure it's worth worrying about.

1
  • I've had good results parsing XML docs with simple extensions to XDocument, but the thing that keeps me from using dynamic more is the limitation surrouding their use inside lambdas.. They have to be evaluated outside of blocks that are evaluated at a later time. Feb 15, 2012 at 21:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.