CSLA

Before we implement the data portal mechanism itself, we need to make sure that the server-side DataPortal can properly interact with our business objects. Once again, the server-side DataPortal just invokes the appropriate methods on our business object to create, retrieve, update, or delete the object's data. This implies that the server-side DataPortal somehow knows what methods our object has implemented to perform these operations.

To ensure that all our business objects have a consistent set of methods, we'll implement some overridable methods in our base classes. This ensures that all business objects will have these methods, even if we choose not to override them in a particular implementation. To simplify debugging, however, the default implementations will simply raise errors. That way, we'll quickly discover that we need to override them! The methods will be protected in scope so that they're not available to any UI code. They'll be named as shown in Table 5-1.

Table 5-1: Data Access Methods Required by the DataPortal Mechanism

Method

Description

DataPortal_Create()

Load the object with default values.

DataPortal_Fetch()

Load the object with data from the database, based on criteria.

DataPortal_Update()

Save the object's data to the database (insert or update).

DataPortal_Delete()

Delete the object's data from the database, based on criteria.

The details behind these methods (and how they're invoked) were discussed in Chapter 2. In Chapter 4, we implemented four base classes from which business objects will be created. Let's update each of them with these methods. First, open the CSLA project in VS .NET.

BusinessBase

Now open the BusinessBase class. This is the primary base class for any editable object, which means that any object based on this class will typically require all four operations: create, retrieve, update, and delete.

Not only that, but an editable object will also need a method to invoke the entire update process, so we'll implement a Save() method as well. In Chapter 4, we implemented BeginEdit() , CancelEdit() , and ApplyEdit() methods to support n-Level undo functionality, and it might seem logical to have the latter save the object to the database, but that isn't ideal. When developing web applications, we may never use BeginEdit() , CancelEdit() , or ApplyEdit() , since n-Level undo capabilities aren't typically important in the web environment. Because of this, we need a new method that explicitly causes the object to save its data into the database, hence Save() .

Finally, note that no matter how much help we provide, it's the business developer who will need to write the specific data-access code for his objects. That code will need to open database connections, which means that it will need access to database connection strings. There are many ways to store that piece of configuration data, but a good technique in .NET is to put the connection string into the application's configuration file. We'll add a utility function here to simplify the process of reading that file, so that the business developer has easy access to the connection string.

Tip 

If you intend to use the configuration file to store connection strings, this utility function will be helpful. If not, you could change this implementation to suit your specific environment.

DataPortal Methods

First, let's implement the four methods that will be invoked by the server-side DataPortal code. In all cases, we'll raise an error in our default implementation. If the business developer wants to support the function, she'll need to override the method in her actual business class.

  #region Data Access     virtual protected void DataPortal_Create(object criteria)     {       throw new NotSupportedException("Invalid operation - create not allowed");     }     virtual protected void DataPortal_Fetch(object criteria)     {       throw new NotSupportedException("Invalid operation - fetch not allowed");     }   virtual protected void DataPortal_Update()     {       throw new NotSupportedException("Invalid operation - update not allowed");     }     virtual protected void DataPortal_Delete(object criteria)     {       throw new NotSupportedException("Invalid operation - delete not allowed");     }     #endregion  
DataPortal_Create

The DataPortal_Create() method is called by the server-side DataPortal when our business object is to be initialized with default values. The use of this functionality is entirely optional, and in Chapter 7 we'll build some business objects that use it and some that don't.

The code in our business object to implement DataPortal_Create() will typically go to the database and retrieve the appropriate default values for the object. Those values are loaded into the object's variables , and then the fully populated object is returned to the client.

This method receives a Criteria object as a parameter, allowing us to provide criteria that might alter the ways the default values are loaded, as we discussed in Chapter 2. For instance, if we're loading a Customer object's default values, we might load different defaults depending on the user 's profile or some other information.

The code in this method may use ADO.NET to retrieve the default values from a database, or the default values may be retrieved from some other source (such as an XML file). It is up to the business developer to provide the implementation of this method to retrieve the default values properly.

DataPortal_Fetch

The DataPortal_Fetch() method is called to tell our object to populate itself with existing data from the database. It gets a Criteria object as a parameter, which should contain enough information for us to locate and load the appropriate data.

The code in this method will typically use ADO.NET to interact with the database to retrieve the data. Values from the database are placed into the object's variables, and the object is then returned to the client.

DataPortal_Update

The DataPortal_Update() method is called to tell our object to save its data to the database. If our object is new ( IsNew is true ), then it will insert itself into the database. Otherwise , it will update the existing data in the database with the values in the object itself.

The DataPortal_Update() method might also perform a delete operation. In Chapter 4, we discussed the fact that there are two approaches to deleting objects: deferred and immediate. In the deferred scenario, an object is marked for deletion, but the actual operation doesn't occur until later, when the object is updated into the database. Because of this possibility, the DataPortal_Update() code needs to check the IsDeleted flag of the object. If that flag is true , then the object is to be deleted from the database, rather than inserted or updated. We'll see how this works when we implement some actual business objects in Chapter 7.

DataPortal_Delete

The scenario in which an object is immediately deleted is the purpose behind the DataPortal_Delete() method. Sometimes, we don't want to have to retrieve the object from the database, just to mark it as deleted and then call the update process. That can amount to a lot of overhead if we already know the object's key value and could simply invoke a delete process.

The DataPortal_Delete() method is passed a Criteria object, just like DataPortal_Fetch() , except that it uses this criteria data to delete the object, rather than to retrieve it. This approach is highly efficient and can be used anytime the client or a client-side object has the key value for the object to be deleted.

The approach is typically only valid for root objects, and it's up to us to ensure that we delete not only the root object's data, but also the data of all its child objects. Child objects are deleted using the deferred approach, because we haven't designed them to be manipulated directly via the data portal mechanism.

Save Method

By implementing the DataPortal_Create() , DataPortal_Fetch() , DataPortal_Update() , and DataPortal_Delete() methods in a business object's class, we allow full manipulation of the object via the server-side DataPortal . What we haven't discussed since Chapter 2 is what starts the whole process. How does the UI code actually create, retrieve, update, or delete a business object?

The create, retrieve, and (immediate) delete processes will be started by static methods in the business object class, a concept called class in charge that we introduced in Chapter 2. We'll implement examples of this in Chapter 7 when we build our business classes. The update process, however, is a bit different. It's not invoked from a static method, because we have a specific business object that we want to update. That object itself should have a method that we can call to cause the object to save its data to the database.

The Save() method will perform this function. At a minimum, it must invoke the client-side DataPortal , which in turn invokes the server-side DataPortal , which finally invokes the object's DataPortal_Update() method. This flow of events was diagrammed and discussed in Chapter 2.

Because this method makes use of the client-side DataPortal , which we haven't written yet, the code we're about to add won't compile at this stage. Nevertheless, add the following code to the Data Access region of BusinessBase :

  virtual public BusinessBase Save()     {       if(IsChild)         throw new NotSupportedException("Cannot directly save a child object");       if(EditLevel > 0)         throw new Exception("Object is still being edited and cannot be saved");       if(!IsValid)         throw new Exception("Object is not valid and cannot be saved");       if(IsDirty)         return (BusinessBase)DataPortal.Update(this);       else         return this;     }  

There are a number of checks in this method to ensure that the object is a root object, that it isn't in the middle of an edit process, that it has no broken rules, and finally that it contains changed data.

If any of the first three checks fails, then the object can't be saved, and an error is raised. If we get past those checks and the object has changed data ( IsDirty is true ), then the client-side DataPortal is invoked and the resulting object is returned by the Save() method. On the other hand, if the object is not dirty, then we simply return the object itself as a result of the method.

Returning an object as a result of the method is important. Remember that DataPortal_Update() might change the object's data during the update process, so it returns the object as a result. We, in turn, need to return that object to the UI code so that it can use this new, updated object. Even if the object wasn't dirty and we didn't do an actual update, we need to return the object so the UI can always count on getting back a reference to a valid business object. In Chapter 8, we'll implement a UI that makes use of the Save() method.

DB Method

At this point, we're basically ready to go: we can create, retrieve, update, and delete our business objects. However, we're leaving our business developers to write the four DataPortal_xyz() methods, and to do that they'll typically use ADO.NET. Let's add a method here to make it easier for them to get the connection string they need to do so.

Tip 

Note that all we're doing here is providing an easy and standard way to get the connection string. We aren't creating or opening the connection object or anything like that. It's up to the business developers to implement that code as appropriate for their particular database or data source. In fact, this "connection string" could just as easily be a file path that the business developers use to open an XML file.

Tip 

By providing centralized access to connection strings for typical database access, we help ensure that the application always uses a consistent connection string. This is important, because database connections are pooled based on the connection string, so we want all our connection strings to be identical whenever possible.

The DB() method will take the name of the database as a parameter and will return the connection string as a result. It will look in the application's configuration file for the information, expecting that the database name will be prefixed with "DB:" . If we're after the pubs database, for example, it would expect an entry in the configuration file such as

 <add key="DB:Pubs"   value="data source=(local);initial catalog=pubs;integrated security=SSPI" /> 

This value will be read from the application's configuration file using System.Configuration.ConfigurationSettings . However, the AppSettings property on that object can return null if the value doesn't exist in the configuration file. To avoid entering a lot of repetitive code to check for null values, let's create a friendlier version of the AppSettings property.

Add a new class to the project named ConfigurationSettings . This file will actually contain two classes. The first is ConfigurationSettings , which will have an AppSettings property:

  using System; using System.Configuration; namespace CSLA {   /// <summary>   /// Provides access to configuration settings.   /// </summary>   public class ConfigurationSettings   {     static ConfigurationSettingsList _settings =       new ConfigurationSettingsList();     /// <summary>     /// Gets configuration settings in the configuration section.     /// </summary>     public static ConfigurationSettingsList AppSettings     {       get       {         return _settings;       }     }   } }  

The second, in the same namespace, is ConfigurationSettingsList , which will implement the indexer for our new AppSettings property:

  public class ConfigurationSettingsList   {     public string this [string name]     {       get       {         string val =           System.Configuration.ConfigurationSettings.AppSettings[name];         if(val == null)           return string.Empty;         else           return val;       }     }   }  

First we read the value in from the application configuration file. Then we see if it's a null. If so, we return an empty string value; otherwise we return the value from the configuration file itself. By consolidating this code here, we avoid having to do this null check throughout much of our other code.

Then we can add the DB() function to the Data Access region:

  protected string DB(string databaseName)     {       return ConfigurationSettings.AppSettings["DB:" + databaseName];     }  

This is just the regular code for retrieving a value from the application's configuration file. Normally AppSettings may return a null value, which wouldn't be useful to us. Since we didn't add a using statement to bring in System.Configuration , however, we're using our new ConfigurationSettings class, which automatically handles the null problem for us.

At this point, we're done with BusinessBase . The server-side DataPortal can now properly interact with any business objects that subclass BusinessBase . Of course, it's up to the business developer to provide meaningful implementations of the four DataPortal_xyz() methods, but we've provided a framework into which the business logic can easily be inserted.

BusinessCollectionBase

In many ways, the BusinessCollectionBase class is similar to BusinessBase . It too can act as either a root or a child, and if the collection is a root object, then it can be created, retrieved, updated, or deleted by the data portal mechanism.

Because of the similarities in functionality, the code we'll insert into BusinessCollectionBase is identical to the code we added to BusinessBase . Obviously, the business logic in the four DataPortal_xyz() methods will be quite different when working with a collection than when working with a simple object, but that has no impact on our framework .

Add the Data Access region:

  #region Data Access    virtual public BusinessCollectionBase Save()    {      if(IsChild)        throw new NotSupportedException("Cannot directly save a child object");      if(_editLevel > 0)        throw new Exception("Object is still being edited and cannot be saved");      if(!IsValid)        throw new Exception("Object is not valid and cannot be saved");      if(IsDirty)        return (BusinessCollectionBase)DataPortal.Update(this);      else        return this;    }    virtual protected void DataPortal_Create(object criteria)    {      throw new NotSupportedException("Invalid operation - create not allowed");    }    virtual protected void DataPortal_Fetch(object criteria)    {      throw new NotSupportedException("Invalid operation - fetch not allowed");    }    virtual protected void DataPortal_Update()    {      throw new NotSupportedException("Invalid operation - update not allowed");    }    virtual protected void DataPortal_Delete(object criteria)    {      throw new NotSupportedException("Invalid operation - delete not allowed");    }    protected string DB(string databaseName)    {      return ConfigurationSettings.AppSettings["DB:" + databaseName];    }    #endregion  

Other than adjusting the data type of the object returned from the Save() method, this code is identical to that from BusinessBase . This means that the business developer can implement comparable functionality to create, retrieve, update, or delete a collection, to what he can implement for a simpler business object.

ReadOnlyBase

The ReadOnlyBase class is used as a base for creating read-only business objects. Read-only objects obviously can't be updated or deleted. It also makes little sense to load a new read-only object with default values. This means that the only valid operation we can perform, from a data access perspective , is to retrieve such an object.

Having said that, the data portal mechanism assumes that all four DataPortal_xyz() methods exist on all business objects, so we need to provide implementations for all of them, not just DataPortal_Fetch() . However, because only DataPortal_Fetch() is actually valid, it will be the only one we create as virtual . The other three won't be virtual and will simply return an error if invoked.

The preceding provisions also mean that there's no need for a Save() method, so we won't implement one of those either. On the other hand, the business developer will use ADO.NET to implement the DataPortal_Fetch() method, and she'll need a database connection string for that purpose, so we'll implement a DB() method.

Add the following code to ReadOnlyBase :

  #region Data Access     private void DataPortal_Create(object criteria)     {       throw new NotSupportedException("Invalid operation - create not allowed");     }     virtual protected void DataPortal_Fetch(object criteria)     {       throw new NotSupportedException("Invalid operation - fetch not allowed");     }     private void DataPortal_Update()     {       throw new NotSupportedException("Invalid operation - update not allowed");     }     private void DataPortal_Delete(object criteria)     {       throw new NotSupportedException("Invalid operation - delete not allowed");     }     protected string DB(string databaseName)     {       return ConfigurationSettings.AppSettings["DB:" + databaseName];     }     #endregion  
Tip 

The server-side DataPortal will be using reflection to invoke both the protected and private methods. More accurately, it will use reflection to invoke non- public methods, so there's no problem with declaring these three methods as private .

By exposing only the one method for overriding by the business developer, we're employing a form of self-documentation. It isn't possible for the business developer to see the other three methods as being available for overriding accidentally , because the business developer simply won't see those methods at all.

ReadOnlyCollectionBase

Like ReadOnlyBase , the ReadOnlyCollectionBase class is intended to act as a base on which we can easily build read-only collections. It will have the same data access methods as ReadOnlyBase .

Implement the Data Access region:

  #region Data Access     private void DataPortal_Create(object criteria)     {       throw new NotSupportedException("Invalid operation - create not allowed");     }     virtual protected void DataPortal_Fetch(object criteria)     {       throw new NotSupportedException("Invalid operation - fetch not allowed");     }     private void DataPortal_Update()     {       throw new NotSupportedException("Invalid operation - update not allowed");     }     private void DataPortal_Delete(object criteria)     {       throw new NotSupportedException("Invalid operation - delete not allowed");     }     protected string DB(string databaseName)     {       return ConfigurationSettings.AppSettings["DB:" + databaseName];     }     #endregion  

Again, the only method that's protected and virtual is the DataPortal_Fetch() method. To the business developer, it's therefore very clear that this object supports only the retrieval operation. Any attempt to ask the data portal mechanism to create, update, or delete a read-only collection will result in an error being raised.

At this point, our four primary base classes, from which all business objects will be derived, support data access functionality and can interact appropriately with the data portal mechanism (see Figure 5-2). Specifically, we've now ensured that the server-side DataPortal will be able to invoke our business objects as needed.

image from book
Figure 5-2: Server.DataPortal depends on DataPortal_xyz() methods.

We can now proceed to implement the data portal mechanism itself. We'll start with the client-side DataPortal class, and then move on to the server-side DataPortal that actually invokes the four DataPortal_xyz() methods we've just been implementing.



Expert C# Business Objects
Expert C# 2008 Business Objects
ISBN: 1430210192
EAN: 2147483647
Year: 2006
Pages: 111

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net