What The NuSoft Framework Gives You: EntityBase

posted on 12/17/07 at 09:00:05 pm by Joel Ross

While I'm away on vacation, I figured I'd put up a series of posts on the NuSoft Framework. This is one of those posts.

Let's dive deeper into some of the files in the framework and see what they offer us, and how we can exploit them for our own benefit. We'll start with EntityBase.

EntityBase is the root of all of our entities. It defines what an entity should support as well as providing utility methods for loading entities and mapping database values to .NET values.

We'll start from the beginning and look at the constructors. There's three of them, and each one has it's own purpose. The first is a protected default constructor and will be called whenever a default constructor is called for an entity. It calls the OnInit method, which fires the Init event, but more importantly, OnInit can be overridden in our entities to allow us to set default values for fields. The second and third constructors take either a IDataReader or a DataRowView. Both call the OnInit method (and thus, fire the Init event), but after that, it loads data into the object from a record in the database.

Next, let's take a look at the properties EntityBase gives you. There's three: IsNew, IsDirty, and IsDeleted. These are all public properties (with internal sets) that are used to maintain the state of the object. The default for these are that the object is new, dirty, and not deleted. Every time a property is set on an entity, IsDirty will be set to true. Calling Save() (see below) will set IsDirty back to false.

The public methods on EntityBase are Save() Delete(), Clone(), Rollback(), and SetDeleted(). These are what the external applications will call. The first two methods are essentially the same - they create a SqlHelper and start a transaction, then call the protected Save/Delete method passing in the SqlHelper. Clone is what allows us to be ICloneable, and it can create either a shallow or deep copy of our objects. A shallow copy will only grab primitive property types, while a deep copy will grab copies of entities that are related to the cloned entity. Being able to clone an object is what allows us to support rollbacks of our objects. Rollback is what actually rolls back changes to an entity. SetDeleted flags the entity as deleted, but it does not actually delete the entity until you call the save method on it or one of it's parents.

There are four main methods that deal with getting data in and out of the database. They all take a SqlHelper as an argument, and they are Save(), Update(), Delete() and Insert(). This allows our objects to persist themselves. There are utility-type functions that worry about getting parameters for data access, so those get built automatically for us. This version of save is more of a distributer. It doesn't touch the database directly at all. If the entity is new, it'll call Insert. If it's not new, but dirty, it will call Update, and if it's deleted, it will call Delete. It also calls UpdateChildren, which will call Save on any EntityList-based collections it has (such us OrderDetails on an Order entity). The Insert, Update, and Delete methods basically handle their designated operation, including getting the parameters ready for the database. Note also that Insert and Update return the record it's working on (using SQL Server 2005's output keyword), so that any changes to the object by the database are automatically pulled back into the object, such as things changed by a trigger or identity column values.

Passing around the SqlHelper into the Save, Delete, Insert, and Update methods allows each operation to take part in the same transaction - including saves done on child objects - and if an operation fails any place in the save process, the whole transaction can be rolled back, ensuring your database will be left in a stable state.

The last protected method in EntityBase is Initialize, and it's overloaded to take either an IDataReader or a DataRowView. It is responsible for loading each property from the data record. It uses reflection to find any properties marked with the DatabaseColumnAttribute, and then tries to find it in the data record. If found, it loads the property, and if not, it doesn't do anything - no error either. Lastly, to ensure our entity is in a pristine state, we flag it as not new (it's been loaded from the database) and not dirty.

Before we move on to the static methods, let's touch on the events defined in EntityBase. There are 4 sets of "-ing" and "-ed" events that happen before ("-ing") and after ("-ed") a method's guts take place. We use a custom EventArgs class which takes the current SqlHelper, allowing any custom database updates written as part of the event handlers can participate in the transaction. Here's the 10 events we have:

  1. Init: This event fires when the object is instantiated.
  2. Saving: This event fires before an entity is saved. It can be canceled through the event arguments.
  3. Saved: This event fires after the entity has been saved.
  4. Deleting: This event fires before an entity is deleted. It can be canceled through the event arguments.
  5. Deleted: This event fires after the entity has been deleted.
  6. Inserting: This event fires before an entity is inserted. It can be canceled through the event arguments.
  7. Inserted: This event fires after the entity has been inserted.
  8. Updating: This event fires before an entity is updated. It can be canceled through the event arguments.
  9. Updated: This event fires after the entity has been updated.
  10. PropertyChanged: This event fires anytime a property value changes. This is mainly so we can support two-way binding.

Let's move on to the static methods/properties that the EntityBase gives you. We'll start with properties, because that's the easiest. Depending on whether you are using stored procedures or dynamic SQL, you'll have properties for either the stored procedures to do inserts and updates, or the SQL to do an insert or update. If you use Dynamic SQL, you'll also get a property called SelectFieldList which returns the fields in the table, so you can use it in your custom queries and not have to rewrite all of your custom queries if you add a field later. Dynamic SQL also gives you one other: DefaultSortOrder. This is the sort order that static GetList methods will use to determine the sort order a query should use when executed. But it's in the base, and has a value of "" - how does that help you? Well, you can't exactly override static properties, but you can hide them, so in your entity, you can do this:

   1:  public static new string DefaultSortOrder
   2:  {
   3:     get { return "order by OrderID";
   4:  }

Adding the new keyword tells any queries in that class to use the new value for DefaultSortOrder when it runs it's queries. If you're using Stored Procedures, we don't have anything like this, so you'll have to modify your get list stored procedures by hand if you want a custom sort order (or you could use the EntityComparer to sort them in memory).

The last component of the EntityBase is it's static methods. None are public, and all are meant to be used by the entities that inherit from EntityBase. First up are a dozen or so methods that take primitive types and determine if they still have a default value, and if so, return a DBNull value. This allows us to pull a null out of a database, and if it's never changed, ensure that the null gets back into the database (and not the default value used by .NET). The methods are all called GetDatabaseValue, and are differentiated by the input parameter type.

The last set of methods are used to instantiate objects and get lists of objects. GetEntityInstance<T>() will find a default constructor (either public or non-public), and return an instance, using reflection. This'll be called later by the static Initialize methods.

While the other static methods are valuable, the three most useful ones, at least to entities inheriting from EntityBase, are GetOne<T>(), GetList<T>(), and GetListReadOnly<T>. These methods take a command and parameters, and run the command against the database, getting back a data reader. They then call either Initialize<T>() (called by GetOne) or InitializeList<T>() (called by GetList and GetListReadOnly) to get either one entity or a list of entities. We do specify that T has to have EntityBase in it's object hierarchy.

Initialize and InitializeList both have overloads that take in data readers or data views (lists) / data row views (non lists), but they both do roughly the same thing: construct an entity, and then call Initialize on that entity, passing in the data row. The only real difference is that InitializeList creates a collection of entities, and does the entity creation in a loop.

And that's it! There's a lot that is done for you and provides a lot of flexibility in how it can be used by entities that inherit from it.

Tags: | |

Categories: Development, Software, RCM Technologies