The Factory Methods region will contain the static factory methods to create or retrieve the object, along with the static delete method if the object is an editable root object.. These
Trang 1■ Note If you’re calling a remote data portal, you must avoid object designs that require IDisposable.
Alternatively, you can modify the SimpleDataPortalclass to explicitly call Dispose()on your business objects on the server
Business Class Structure
As you’ve seen, business objects follow the same sequence of events for creation, retrieval, andupdates Because of this, there’s a structure and a set of features that are common to all of them.Although the structure and features are common, however, the actual code will vary for each busi-ness object Due to the consistency in structure, however, there’s great value in providing somefoundations that make it easier for the business developer to know what needs to be done
Also, there are differences between editable and read-only objects, and between root and childobjects After discussing the features common to all business objects, I’ll create “templates” to illus-trate the structure of each type of business object that you can create based on CSLA NET
The Serializable Attribute
All business objects must be unanchored so that they can move across the network as needed Thismeans that they must be marked as serializable by using the [Serializable()] attribute, as shownhere:
Common Regions
When writing code in VS NET, the #region directive can be used to place code into collapsible regions.This helps organize the code, and allows you to look only at the code pertaining to a specific type offunctionality
Trang 2All business collection classes will have a common set of regions, as follows:
Trang 3The Business Methods region will contain the methods that are used by UI code (or other client
code) to interact with the business object This includes any properties that allow retrieval or ing of values in the object, as well as methods that operate on the object’s data to perform businessprocessing
chang-The Validation Rules region will contain the AddBusinessRules() method, and any custom rule
methods required by the object
The Authorization Rules region will contain the AddAuthorizationRules() method It will also
contain a standard set of static methods indicating whether the current user is authorized to get,add, save, or delete this type of business object
The Factory Methods region will contain the static factory methods to create or retrieve the
object, along with the static delete method (if the object is an editable root object) It will alsocontain the default constructor for the class, which must be scoped as non-public (i.e., private
or protected) to force the use of the factory methods when creating the business object
Trang 4The Data Access region will contain the DataPortal_XYZ methods It will also contain the Criteria
class used to create, retrieve, or delete the object
Your business objects may require other code that doesn’t fit neatly into these regions, and youshould feel free to add extra regions if needed But these regions cover the vast majority of code
required by typical business objects, and in most cases they’re all you’ll need
Private Default Constructor
All business objects will be implemented to make use of the class-in-charge scheme discussed in
Chapter 1 Factory methods are used in lieu of the new keyword, which means that it’s best to
pre-vent the use of new, thereby forcing the UI developer to use the factory methods instead
The data portal mechanism, as implemented in Chapter 4, requires business classes to include
a default constructor As I reviewed the create, fetch, update, and delete processes for each type of
object earlier in this chapter, each sequence diagram showed how the server-side data portal created
an instance of the business object This is done using a technique that requires a default constructor
By making the default constructor private or protected (and by not creating other public structors), you ensure that UI code must use the factory methods to get an instance of any object:
The Csla.CriteriaBase approach is intended primarily for use with code-generation tools
The Criteria class simply contains the data that’s required to identify the specific object to
be retrieved or the default data to be loaded Since it’s passed by value to the data portal, this class
must be marked as [Serializable()]
■ Tip Technically, the Criteriaclass can have any name, as long as it’s [Serializable()], and is either
nested in the business class or inherits from CriteriaBase Some objects may have more than one criteria class,
each one defining a different set of criteria that can be used to retrieve the object
Since this class is no more than a way to ferry data to the data portal, it doesn’t need to befancy Typically, it’s implemented with a constructor to make it easier to create and populate the
object all at once For example, here’s a Criteria class that includes an EmployeeID field:
Trang 5private string _employeeId;
public string EmployeId {
get { return _employeeId; } }
public Criteria(string employeeId) { _employeeId = employeeId; } }
private string _employeeId;
public string EmployeId{
get { return _employeeId; }}
public Criteria(string employeeId)
: base(typeof(MyBusinessClass))
{ _employeeId = employeeId; }}
//
All Criteria classes are constructed using one of these two schemes Nested criteria classesare scoped as private because they are only needed within the context of the business class TheCriteriaBase class is typically used by code-generation tools, in which case the class is typicallyprotected in scope so that it is available to subclasses as well
■ Note Code generation is outside the scope of this book For good information on code generation, including the rationale behind CriteriaBase, please refer to Kathleen Dollard’s book, Code Generation in Microsoft NET(Apress, 2004)
Even though the Criteria object is passed through the data portal, it’s passed as a type object,
so the DataPortal code doesn’t need access to the object’s code This is ideal, because it means that
UI developers, or other business object developers, won’t see the Criteria class, thus improving thebusiness object’s overall encapsulation
Trang 6The Criteria classes shown thus far include a constructor that accepts the criteria data value.
This is done to simplify the code that will go into the static factory methods Rather than forcing
the business developer to create a Criteria object and then load its values, this constructor allows
the Criteria object to be created and initialized in a single statement In many cases, this means
that a static factory method will contain just one line of code! For instance:
public static Project GetProject(Guid id)
collection in which you’re directly retrieving a collection of child objects, the Criteria class may not
define a single object, but rather act as a search filter that returns the collection populated with all
matching child objects
In other cases, an object may have no criteria data at all In that case, a Criteria class is stillrequired, but it would be empty:
business object to be retrieved This is typically used when retrieving a root collection object for
which you want all the child objects in the database returned at all times I’ll use this technique
to create the ProjectList and ResourceList collection classes in Chapter 8
Class Structures
At this point in the chapter, I’ve walked through the life cycle of typical business objects, so you
know the sequence of events that will occur as they are created, retrieved, updated, and deleted
I’ve also discussed the code concepts and structures that are common to all business classes Now
let’s dive in and look at the specific coding structure for each type of business class that you can
create based on the CSLA NET framework These include the following:
• Editable root
• Editable child
• Editable, “switchable” (i.e., root or child) object
• Editable root collection
• Editable child collection
• Read-only object
• Read-only collection
• Command object
• Name/value listFor each of these object types, I’ll create the basic starting code that belongs in the class
In a sense, these are the templates from which business classes can be built
Trang 7■ Tip You can use this code to create either snippets or class templates for use in Visual Studio The Csla\Snippetssubdirectory in the code download (available from www.apress.com) contains a set of sample snippets you may find valuable.
Editable Root Business Objects
The most common type of object will be the editable root business object, since any object-orientedsystem based on CSLA NET will typically have at least one root business object or root collection.(Examples of this type of object include the Project and Resource objects discussed in Chapter 8.)These objects often contain collections of child objects, as well as their own object-specific data
As well as being common, an editable object that’s also a root object is the most complex objecttype, so its code template covers all the possible code regions The basic structure for an editableroot object, with example or template code in each region, is as follows:
public int id {
get { CanReadProperty(true);
return _id;
} set { CanWriteProperty(true);
if (_id != value) {
_id = value;
PropertyHasChanged();
} } } protected override object GetIdValue() {
// TODO: add validation rules //ValidationRules.AddRule(null, "");
}
Trang 8#region Authorization Rules protected override void AddAuthorizationRules() {
// TODO: add authorization rules //AuthorizationRules.AllowWrite("", "");
} public static bool CanAddObject() {
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return true;
} public static bool CanGetObject() {
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return true;
} public static bool CanEditObject() {
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return true;
} public static bool CanDeleteObject() {
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return DataPortal.Create<EditableRoot>();
} public static EditableRoot GetEditableRoot(int id) {
return DataPortal.Create<EditableRoot>(new Criteria(id));
} public static void DeleteEditableRoot(int id) {
DataPortal.Delete(new Criteria(id));
}
Trang 9private EditableRoot() { /* Require use of factory methods */ }
get { return _id; } }
public Criteria(int id) { _id = id; }
} private void DataPortal_Create(Criteria criteria) {
// TODO: load default values }
private void DataPortal_Fetch(Criteria criteria) {
// TODO: load values }
protected override void DataPortal_Insert() {
// TODO: insert values }
protected override void DataPortal_Update() {
// TODO: update values }
protected override void DataPortal_DeleteSelf() {
DataPortal_Delete(new Criteria(_id));
} private void DataPortal_Delete(Criteria criteria) {
// TODO: delete values }
#endregion
}
You must define the class, including making it serializable, giving it a name, and having it inheritfrom BusinessBase
Trang 10The Business Methods region includes all member or instance field declarations, along with any
business-specific properties and methods These properties and methods typically interact with the
instance fields, performing calculations and other manipulation of the data based on the business
logic
Notice the GetIdValue() method, which is required when inheriting from BusinessBase Thismethod should return a unique identifying value for the object The value is directly returned by the
default ToString() method in BusinessBase, and is used in the implementation of the Equals() and
GetHashCode() methods as well For details, refer to Chapter 3
The Validation Rules region, at a minimum, overrides the AddBusinessRules() method In this
method, you call ValidationRules.AddRule() to associate rule methods with properties This region
may also include custom rule methods for rules that aren’t already available in Csla.Validation
CommonRules or in your own library of rule methods
The Authorization Rules region overrides the AddAuthorizationRules() method and implements
a set of static authorization methods
The AddAuthorizationRules() method should include calls to methods on the AuthorizationRules object: AllowRead(), AllowWrite(), DenyRead(), and DenyWrite() Each
one associates a property with a list of roles that are to be allowed read and write access to that
property
The static authorization methods are CanGetObject(), CanAddObject(), CanEditObject(), andCanDeleteObject() These methods should check the current user’s roles to determine whether the
user is in a role that allows or denies the particular operation The purpose of these methods is so
the UI developer can easily determine whether the current user can get, add, update, or delete this
type of object That way, the UI can enable, disable, or hide controls to provide appropriate visual
cues to the end user
Since these are static methods, there’s no way to make them part of the BusinessBase class,and they must be directly declared and implemented in each business class
In the Factory Methods region, there are static factory methods to create, retrieve, and delete
the object Of course, these are just examples that must be changed as appropriate The parameters
accepted and Criteria object used must be tailored to match the identifying criteria for your ular business object
partic-Finally, the Data Access region includes the Criteria class and the DataPortal_XYZ methods.
These methods must include the code to load defaults, retrieve object data, update object data, and
delete object data, as appropriate In most cases, this will be done through ADO.NET, but this code
could just as easily be implemented to read or write to an XML file, call a web service, or use any
other data store you can imagine
The [RunLocal()] attribute is for objects that do not load default values from the database
when they are created The use of the [RunLocal()] attribute on DataPortal_Create() is optional,
and is used to force the data portal to always run the method locally When this attribute is used,
the DataPortal_Create() method should not access the database, because it may not be running
in a physical location where the database is available
The [Transactional()] attributes on the methods that insert, update, or delete data specifythat those methods should run within a System.Transactions transactional context You may opt
instead to use the TransactionTypes.EnterpriseServices setting to run within a COM+ distributed
transaction, or TransactionTypes.Manual to handle your own transactions using ADO.NET
■ Tip Many organizations use an abstract, metadata-driven data access layer In environments like this, the
business objects don’t use ADO.NET directly This works fine with CSLA NET, since the data access code in the
DataPortal_XYZmethods can interact with an abstract data access layer just as easily as it can interact with
ADO.NET directly
Trang 11The key thing to note about this code template is that there’s very little code in the class that’snot related to the business requirements Most of the code implements business properties, valida-tion, and authorization rules or data access The bulk of the nonbusiness code (code not specific
to your business problem) is already implemented in the CSLA NET framework
Immediate or Deferred Deletion
As implemented in the template, the UI developer can delete the object by calling the static deletemethod and providing the criteria to identify the object to be deleted Another option is to imple-
ment deferred deletion, whereby the object must be retrieved, marked as deleted, and then updated
in order for it to be deleted The object’s data is then deleted as part of the update process
To support deferred deletion, simply remove the static delete method:
// public static void DeleteEditableRoot(int id)
Editable Child Business Objects
Most applications will have some editable child objects, or even grandchild objects Examples of theseinclude the ProjectResource and ResourceAssignment objects In many cases, the child objects are con-tained within a child collection object, which I’ll discuss later In other cases, the child object might bereferenced directly by the parent object Either way, the basic structure of a child object is the same; insome ways, this template is very similar to the editable root:
public int id {
get { CanReadProperty(true);
return _id;
} set { CanWriteProperty(true);
if (_id != value) {
_id = value;
PropertyHasChanged();
} } }
Trang 12protected override object GetIdValue() {
// TODO: add validation rules //ValidationRules.AddRule(null, "");
}
#endregion
#region Authorization Rules protected override void AddAuthorizationRules() {
// TODO: add authorization rules //AuthorizationRules.AllowWrite("", "");
}
#endregion
#region Factory Methods internal static EditableChild NewEditableChild() {
// TODO: change to use new keyword if not loading defaults return DataPortal.Create<EditableChild>();
} internal static EditableChild GetEditableChild(SqlDataReader dr) {
return new EditableChild(dr);
} private EditableChild() {
MarkAsChild();
} private EditableChild(SqlDataReader dr) {
Trang 13protected override void DataPortal_Create(object criteria) {
// TODO: load default values, or remove method }
private void Fetch(SqlDataReader dr) {
// TODO: load values MarkOld();
} internal void Insert(object parent) {
// TODO: insert values MarkOld();
} internal void Update(object parent) {
// TODO: update values MarkOld();
} internal void DeleteSelf() {
// TODO: delete values MarkNew();
The Business Methods region is the same as with a root object: it simply implements the erties and methods required by the business rules Similarly, the Validation Rules region is the same
prop-as with a root object
The Authorization Rules region is simpler, as it only implements the AddAuthorizationRules()
method Control over retrieving, adding, updating, and deleting child objects is controlled by theparent object or collection, so no static methods are needed here for that purpose
The Factory Methods region is a bit different The factory methods are internal rather than
public, as they should only be called by the parent object, not by the UI code Also, there’s no needfor a static delete method because BusinessBase implements a DeleteChild() method that is auto-matically called by BusinessListBase when the child is removed from a collection
Notice that the NewEditableChild() method invokes the data portal to create the child object.This allows the child object to load itself with default values from the database when it is created I’ll discuss an alternative approach that avoids using the database shortly
The GetEditableChild() method uses the new keyword to create an instance of the child object.See how it accepts a data reader as a parameter and passes it to the constructor The idea is that theparent object will have already retrieved the necessary data from the database and is providing it tothe child object through this parameter That parameterized constructor then calls a Fetch() method
in the Data Access region where the object loads its data.
Trang 14If you are using a data store other than a relational database, the data reader parameter would
be replaced by some other type of object For instance, if the object’s data is being loaded from an
XML document, the parameter would likely be an XmlNode that contains the child object’s data
The biggest difference from a root object comes in the Data Access region The DataPortal_
Create() method is implemented to support the loading of default values from the database on
the creation of a new child object, but no other DataPortal_XYZ methods are implemented
Instead, there’s a private Fetch() method to load the object with data, and internal methodsnamed Insert(), Update(), and DeleteSelf() to handle insert, update, and delete operations These
mirror the functionality of the DataPortal_XYZ methods, but they are called by the parent object
rather than by the data portal
Notice that Insert() and Update() both accept a reference to the parent object as a parameter Theassumption is that any child object will need data from the parent while being inserted or updated into
the database Most often, the parent contains a foreign key value required by the child object during
data access
■ Note Typically, the parentparameter will be strongly typed based on the class of the parent object itself
As an example, the ProjectResource child object will need the Id property from its parentProject object so that it can store it as a foreign key in the database By getting a reference to its
parent Project object, the ProjectResource gains access to that value as needed
The Fetch(), Insert(), and Update() methods all call MarkOld() when they are done, becausethe object’s data in memory matches that in the database at those points, so the object is neither
new nor dirty The DeleteSelf() method calls MarkNew() as it completes, because the object’s
pri-mary key value is not in the database at that point, so the object qualifies as a new object.
Object Creation Without Defaults
As implemented, the template uses DataPortal.Create() to load the child object with default values
from the database As discussed earlier, if the object doesn’t need to load default values from the
database, the code can be implemented more efficiently by changing the static factory method
to create the child object directly:
internal static EditableChild NewEditableChild()
It’s possible that some classes must be instantiated as root objects on some occasions and as child
objects on others This can be handled by conditionally calling MarkAsChild(), based on how the object
is being created
■ Note In most cases, the need for a switchable object indicates a flawed object model While there are exceptions
for which this makes sense, you should carefully examine your object model to see if there’s a simpler solution before
implementing a switchable object
Trang 15Conditionally calling MarkAsChild() typically can’t be done in the default constructor, because
there’s no way to determine whether the object is being created as a root or a child object at that point
Instead, you need to go back to your object’s life cycle to see where you can make this decision In fact,
since the default is for an object to be a root object, all you need to do is determine the paths by which
a child object can be created, and make sure to call MarkAsChild() only in those cases.
The template for creating a “switchable” object is the same as the editable root template, withthe following exceptions:
• Dual criteria objects
• Dual create and fetch factory methods
• Dual create and fetch data access methodsLet’s discuss each change in turn
Dual Criteria Classes
The object’s criteria must now include a flag to indicate whether the object is being created as a root
or a child object (this is in addition to any object-specific criteria fields in this class) This can be doneeither by adding an actual flag field to the criteria class, or by creating a second criteria class I pre-fer the second approach as it makes the code simpler overall
Remember that for a child object, the criteria class is only used for the create operation, and so
it typically doesn’t need any actual criteria data The result is that there are two criteria classes; forexample:
get { return _id; } }
public RootCriteria(int id) { _id = id; }
public RootCriteria() { }
}
[Serializable()]
private class ChildCriteria
{ }
These two classes will be used to differentiate the way the object should be created
Dual Factory Methods
Instead of single factory methods to create and retrieve the object, there will be two methods foreach operation: one public, the other internal
public static Switchable NewSwitchable()
{ return DataPortal.Create<SwitchableObject>(
new RootCriteria());
}
Trang 16internal static SwitchableObject NewSwitchableChild() {
return DataPortal.Create<SwitchableObject>(
new ChildCriteria());
} public static SwitchableObject GetSwitchableRoot(int id) {
return DataPortal.Create<SwitchableObject>(
new RootCriteria(id));
} internal static SwitchableObject GetSwitchableChild(
SqlDataReader dr) {
return new SwitchableObject(dr);
}
Notice how the NewSwitchable() methods are each designed The public version (used to ate a root object) uses the RootCriteria object, while the internal version (called by a parent object
cre-to create a child object) uses ChildCriteria The DataPortal_Create() methods, which follow, are
called based on the type of the criteria object
The two GetSwitchable() methods are even more different The public one is called by UI code
to retrieve a root object In this case, the data portal is called to retrieve the object based on the
sup-plied criteria The internal one follows the pattern for child objects, accepting a data reader from
the parent object and passing it along to a private constructor, which in turn calls a private Fetch()
method
Dual Data Access Methods
The data access methods that handle create and fetch operations are different for a root and child
object Because of this, these methods are duplicated in a switchable object In most cases, they can
delegate to a shared implementation that is private to the class For instance:
private void DataPortal_Create(RootCriteria criteria)
Trang 17Similarly, the data retrieval operations are duplicated:
private void DataPortal_Fetch(RootCriteria criteria)
{
// TODO: create data reader to load values using (SqlDataReader dr = null)
{ DoFetch(dr);
} } private void Fetch(SqlDataReader dr) {
MarkAsChild();
DoFetch(dr);
} private void DoFetch(SqlDataReader dr) {
// TODO: load values }
If the object is being loaded from the UI, then it is treated as a root object and DataPortal_Fetch() is called, passing in appropriate criteria This method opens the database, and sets up andexecutes a database command object to get back a data reader That data reader is then passed to
a central DoFetch() helper method to copy the data from the data reader into the object’s fields
On the other hand, if the object is being loaded from a parent object as a child, then its terized constructor is called, which in turn calls the Fetch() method This method calls MarkAsChild()
parame-to mark the object as a child, and then the DoFetch() helper is called parame-to copy the data from the datareader into the object’s fields
Object Creation Without Defaults
When creating the object using the new keyword instead of calling DataPortal.Create(), theinternal factory method can directly call MarkAsChild(), as shown here:
internal static SwitchableObject NewSwitchableChild()
Editable Root Collection
At times, applications need to retrieve a collection of child objects directly To do this, you need to ate a root collection object For instance, the application may have a Windows Forms UI consisting of
cre-a Dcre-atcre-aGridView control thcre-at displcre-ays cre-a collection of Contcre-act objects If the root object is cre-a collection
of child Contact objects, the UI developer can simply bind the collection to the DataGridView, and theuser can do in-place editing of the objects within the grid
This approach means that all the child objects are handled as a single unit in terms of dataaccess They are loaded into the collection to start with, so the user can interact with all of them,
Trang 18and then save them all at once when all edits are complete This is only subtly different from having
a regular root object that has a collection of child objects Figure 7-10 shows the regular root object
approach on the left, and the collection root object approach on the right
This approach isn’t recommended when there are large numbers of potential child objects,because the retrieval process can become too slow, but it can be very useful in cases where you can
specify criteria to limit the number of objects returned To create an editable root collection object,
use a template like this:
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return true;
} public static bool CanGetObject() {
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return true;
} public static bool CanEditObject() {
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return true;
} Figure 7-10.Comparing simple root objects (left) and collection root objects (right)
Trang 19public static bool CanDeleteObject() {
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return new EditableRootList();
} public static EditableRootList GetEditableRootList(int id) {
return DataPortal.Fetch<EditableRootList>(new Criteria(id)); }
private EditableRootList() { /* Require use of factory methods */ }
get { return _id; } }
public Criteria(int id) { _id = id; }
} private void DataPortal_Fetch(Criteria criteria) {
RaiseListChangedEvents = false;
// TODO: load values using (SqlDataReader dr = null) {
while (dr.Read()) {
this.Add(EditableChild.GetEditableChild(dr));
} } RaiseListChangedEvents = true;
}
Trang 20protected override void DataPortal_Update() {
else item.Update(this);
RaiseListChangedEvents = true;
}
#endregion
}
The Authorization Rules region contains the standard static methods discussed earlier for
editable root objects Since collection objects don’t have detailed properties, there’s no need or
support for the AddAuthorizationRules() method
The Factory Methods region implements factory methods to create, retrieve, and (optionally)
delete the collection The create method simply uses the new keyword to create an instance of the
collection There’s no need to load default values for the collection itself The retrieve and delete
methods rely on the data portal to do much of the work, ultimately delegating the call to the
appropriate DataPortal_XYZ method
In the Data Access region, the DataPortal_Fetch() method is responsible for getting the data
from the database, typically via a data reader It then calls the static factory method of the child
class for each row in the data reader, thereby allowing each child object to load its data The static
factory method in the child class calls its own private constructor to actually load the data from
the data reader
The DataPortal_Update() method must loop through all the child objects contained in thedeleted object collection, calling each object’s DeleteSelf() method in turn An alternative is to have
the collection object dynamically generate a SQL statement to delete all the items in the DeleteList
with a single call The specific implementation is up to the business developer and may vary
depend-ing on the database design
Once the child objects have been deleted from the database, that list is cleared Then the activechild objects are either inserted or updated based on their IsNew property value
■ Note It’s critical that the deleted child objects be processed first.
It’s quite possible for the user to delete a child object from the collection, and then add a new
child object with the same primary key value This means that the collection will have the original
child object marked as deleted in the list of deleted child objects, and the new child object in the
list of active objects This new object will have its IsNew property set to true, because it’s a new
object If the original child object isn’t deleted first, the insertion of the new child object will fail
Trang 21Thus, the code first processes the list of deleted child objects, and then moves on to process thelist of active child objects.
Both the DataPortal_Fetch() and DataPortal_Update() methods set the RaiseListChanged➥Events property to false before changing the collection, and then restore it to true once the opera-tion is complete Setting this property to false tells the base BindingList<T> class to stop raising the ListChanged event When doing batches of updates or changes to a collection, this can increaseperformance
Editable Child Collection
The most common type of collection is one that is contained within a parent object to manage
a collection of child objects for that parent; like ProjectResources and ResourceAssignments in thesample application
■ Tip Note that the parent object here might be a root object, or it might be a child itself—child objects can benested, if that’s what the business object model requires In other words, this concept supports not only root-child,but also child-grandchild and grandchild-to-great-grandchild relationships
A child collection class inherits from BusinessListBase and calls MarkAsChild() during its ation process to indicate that it’s operating in child mode This also means that it won’t be directlyretrieved or updated by the DataPortal, but instead will be retrieved or updated by its parent object:
return new EditableChildList();
} internal static EditableChildList GetEditableChildList(
SqlDataReader dr) {
return new EditableChildList(dr);
} private EditableChildList() {
MarkAsChild();
} private EditableChildList(SqlDataReader dr) {
Trang 22private void Fetch(SqlDataReader dr) {
RaiseListChangedEvents = false;
while (dr.Read()) {
this.Add(EditableChild.GetEditableChild(dr));
} RaiseListChangedEvents = true;
} internal void Update(object parent) {
else item.Update(parent);
RaiseListChangedEvents = true;
}
#endregion }
As you can see, this code is very similar to a root collection in structure The differences start withthe factory methods Since only a parent object can create or fetch an instance of this class, the static
factory methods are scoped as internal The static method to create an object simply returns a new
collection object As with the EditableChild template, the constructor calls MarkAsChild() to indicate
that this is a child object
Likewise, the static method to load the child collection with data creates a new collection objectand then calls a parameterized constructor just like in the EditableChild template That constructor
calls a Fetch() method to load the data
The Update() method is identical to the DataPortal_Update() method in the EditableRootList
It loops through the list of deleted child objects, calling their DeleteSelf() methods, and then loops
through the active child objects, calling Insert() or Update() as appropriate
Notice, however, that the Update() method accepts a reference to the parent object as a eter, and this value is provided to the child objects’ Insert() and Update() methods As discussed
param-earlier, this allows the child objects to use data from the parent object as needed for things like
for-eign key values and so forth
Read-Only Business Objects
Sometimes, an application may need an object that provides data in a only fashion For a only list of data, there’s ReadOnlyListBase; but if the requirement is for a single object containing
read-read-only data, it should inherit from ReadOnlyBase This is one of the simplest types of object to
create, since it does nothing more than retrieve and return data, as shown here:
Trang 23// TODO: add your own fields, properties and methods private int _id;
public int Id {
get { CanReadProperty(true);
return _id;
} } protected override object GetIdValue() {
// TODO: add authorization rules //AuthorizationRules.AllowRead("", "");
} public static bool CanGetObject() {
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return DataPortal.Fetch<ReadOnlyRoot>(new Criteria(id)); }
private ReadOnlyRoot() { /* require use of factory methods */ }
Trang 24public int Id {
get { return _id; } }
public Criteria(int id) {
_id = id;
} } private void DataPortal_Fetch(Criteria criteria) {
// TODO: load values _id = criteria.Id;
}
#endregion }
Like other business objects, a read-only object will have instance fields that contain its data
It will typically also have read-only properties or methods that allow client code to retrieve values
As long as they don’t change the state of the object, these may even be calculated values
Like editable objects, read-only objects must override the GetIdValue() method and provide
a unique identifying value for the object This value is used by the Equals(), GetHashCode(), and
ToString() implementations in the ReadOnlyBase class If those implementations are inadequate
for your needs, you can override them and provide your own implementations
The AddAuthorizationRules() method only needs to add roles for read access, since no erties should be implemented to allow altering of data It also includes a CanGetObject() method
prop-so the UI can enable or disable options based on that result
In the Factory Methods region, there’s just one factory method that retrieves the object by calling
DataPortal.Fetch() This means there’s also a Criteria class, which should be modified to contain
the criteria data needed to select the correct object for retrieval
The Data Access region just contains DataPortal_Fetch() Of course, there’s no need to support
updating or deleting a read-only object
Read-Only Collections of Objects
Applications commonly retrieve read-only collections of objects The CSLA NET framework
includes the ReadOnlyListBase class to help create read-only collections It throws an exception any
time there’s an attempt to change which items are in the collection by adding or removing objects
■ Note The template shown here is for the most common scenario: a read-only root collection You can adapt
this to provide a read-only child collection if desired
However, there’s no way for the collection object to stop client code from interacting with thechild objects themselves Typically, the items in the collection will expose only read-only proper-
ties and methods If read-write objects are put into the collection, client code will be able to alter
their data A read-only collection only guarantees that objects can’t be added or removed from the
collection
Trang 25The child objects may be derived from ReadOnlyBase, but more often they will be simple objectsthat don’t inherit from any CSLA NET base class The only requirements for these child objects is thatthey are implemented with read-only properties and that they are marked as [Serializable()].The code for a typical read-only collection object looks like this:
// TODO: customize to check user role //return ApplicationContext.User.IsInRole("");
return DataPortal.Fetch<ReadOnlyList>(new Criteria(filter));
} private ReadOnlyList() { /* require use of factory methods */ }
private string _filter;
public string Filter {
get { return _filter; } }
public Criteria(string filter) {
_filter = filter;
} } private void DataPortal_Fetch(Criteria criteria) {
RaiseListChangedEvents = false;
IsReadOnly = false;
// load values using (SqlDataReader dr = null)