Synopsis
This project shows how to have a master/detail DataGridView
using the CSLA DynamicRootList
(or EditableRootListBase
) as the master list object. If you use DynamicRootList
for the master list, auto save is a standard feature. This project also shows how to implement auto save on the detail list. As a bonus, you get both lists sorted.
In part I, we explained the problem, discussed some background, analyzed the use cases, and overviewed the database and BO design.
In part II, we will start by detailing the specification of objects on CslaGen. Then, we will see the changes that must be done to the generated files in order to have proper DynamicRootList
and DynamicRoot
object types. Lastly, we will discuss the BO customization.
If you don't care about CslaGen generation and just want to know about the DataGridView
details, skip part II and go to Part III - Handling the DataGridView. Otherwise, I will presume you have a basic knowledge of CslaGen.
7. CslaGen code generation
First of all, let's mention, this started as a very big section filled with redundant information, as there were screenshots of every detail of all CslaGen objects. It was a pain to write and boring to read. On the other hand, due to the lack of documentation of CslaGen, new comers do need all the details they can get. So, I moved the complete collection of screenshots to an appendix, keeping only non-redundant and important information in the main article.
One word about the criteria: these are the parameters used on CRUD operations. I use three kinds:
- Criteria - It's used to get objects or collections. For editable objects, it's also used to update and delete. For root collections, it's normal to have no properties at all, so it will fetch all objects in the collection. In this case, a
FilteredCriteria
will be used to allow filtering. They use the same Stored Procedure that will be generated by FilteredCriteria
. The usual properties are:
- root collections - no properties
- root objects - object ID and row version
- child collections - parent object ID
- child objects - object ID and row version
- CriteriaNew - It's used only to create objects, and it doesn't exist on collections. Usually, it has no properties and the object is created with default values.
- FilteredCriteria - It's used to get a partial collection. Filtering properties could be the object's name, some date, or date range, etc.
7.1. BrandColl
BrandColl
is a collection of Brand
objects. It's an EditableRootCollection
(that's the CslaGen name for EditableRootList
). The "not to be used" name of DynamicRootList
is EditableRootListBase
and not EditableRootList
; different names, different kinds of object. In 8.1. Object type transformation, we will transform this object into a DynamicRootList
.
A collection doesn't have value properties as those are defined in the objects the collection is made of. So, we will start by the criteria section.
7.1.1. Criteria section
In order to fetch the collection, you must define a Criteria
. Although the Stored Procedure isn't generated (Procedure
is set to False
), the generated code will use an Stored Procedure so you must specify its name. Note all CreateOptions
and DeleteOptions
are False
or blank.
Fig. 3 - Criteria for BrandColl
A FilteredCriteria
should also be generated. In this particular project, it serves no purpose, but it could prove useful in other situations. Note that the procedure name is the same. The procedure is generated in such a way that it will work with or without filtering parameters. The FilteredCriteria
definition is just like Criteria
. The only differences are:
Procedure
is set to True
so the Stored Procedure is generated.FilteredCriteria
does have properties.
These properties are used to specify the filter or filters to be used. In this example, we will use only the brand name as a filter.
Fig. 4 - Properties of FilteredCriteria for BrandColl
7.1.2. Other properties section
Other BrandColl
properties include the Item Type that is set to Brand
. This binds Brand
objects to the BrandColl
collection. The counterpart is in Brand
where the Parent Type property is set to BrandColl
(Fig. 10).
Fig. 5 - Other properties for BrandColl
7.2. Brand
Brand
is an EditableSwitchable
object. In 8.1. Object type transformation, we will transform this object into a DynamicRoot
.
EditableSwitchable
objects may sometimes be root and sometimes child. On the other hand, Brand
is also the parent of ModelColl
. The next section is about Child Collection Properties, meaning the children of Brand
, i.e., ModelColl
. Later on, we will talk about properties of Brand
as a child. These properties show up in the Csla Object Info pane under 04. Child Object Options.
7.2.1. Children section
In the Child Collection Properties of Brand
, you specify all its children. ModelColl
is the child of Brand
and you specify it in TypeName. Note the properties:
LazyLoad
LoadingScheme
LoadParameters
When you load the root collection BrandColl
, you must load all Brand
objects. At this same moment, you could also load all the children and grand children (for each Brand
, load its ModelColl
and all Model
s on the collection). This is a lot of objects to load, and setting LazyLoad
to True
avoids the need to load all these objects.
The property LoadingScheme
can be set to SelfLoad
, ParentLoad
, or None
. Although I set it to SelfLoad
, you can also set it to ParentLoad
as the generated code is exactly the same - at least in this case.
The LoadParameters
property specifies what parameter must be passed in order to load a ModelColl
. This refers to a criterion of Brand
and might be confusing. A property of the parent's criterion is used as a parameter to the child's criterion that will load it. It means that you must have criteria on both parent and child with at least one common property, and in the present case, this property is BrandID
.
Fig. 6 - Child Collection Properties for Brand
7.2.2. Value properties section
The Brand
object has the following value properties:
BrandID
BrandName
RowVersion
Note that the first and the last properties are read only, as you shouldn't change the values provided by the database layer.
The BrandID
property uses System.Threading.Interlocked.Decrement
to get a (temporary) default value. The static field _lastID
stores the last used ID. This is discussed with some detail in Generating temporary unique ID values for objects.
BrandID
has a PrimaryKey
property of type DBProvidedPK
, and there should be no surprise that the property ReadOnly
is set to True
. As I said earlier, I use a slightly changed version of CslaGen as you might notice by the FKConstraint
property. This feature isn't used in this example.
Fig. 7 - ValueProperties for BrandID (of Brand)
The BrandName
value property is like BrandID
with some differences:
PrimaryKey
is set to Default
DefaultValue
is set to blankPropertyType
is set to String
ReadOnly
is set to False
Last, the RowVersion
value property is like BrandName
with some differences:
PropertyType
is set to ByteArray
ReadOnly
is set to True
7.2.3. Criteria section
The Criteria
for Brand
is also pretty standard. It's used for fetching, updating, and deleting Brand
objects, but doesn't take care of creating them as all CreateOption
s are False
or blank.
Fig. 8 - Criteria for Brand
The Criteria
for Brand
uses two properties: BrandID
and RowVersion
. This means you need to supply both these parameters in order to fetch, update, or delete a Brand
object. Going back to Fig. 6 comments, at least one of these properties must also be used as a Criteria
on the child object. The BrandID
property fulfils this requirement.
The CriteriaNew
for Brand
is used just for creating Brand
objects and nothing else. All DeleteOption
s and GetOption
s are False
or blank. Of course, it doesn't use any property. As the object doesn't exist yet, it has no ID or row version.
Fig. 9 - CriteriaNew for Brand
7.2.4. Other properties section
Other properties of Brand
are shown below. Usually, all 04. Child Object Options are very important in order to make it work. As we said before, they refer to the properties of Brand
as a child.
- Lazy Load is set to
False
. - Parent Insert Only is set to
False
. - Parent Properties is blank.
- The property Parent Type is the complement of the property Item Type in
BrandColl
(Fig. 5).
This might confuse you (I know it confused me!), but in fact, it's quite correct. This refers to the properties of the object Brand
, and this object isn't supposed to be lazy loaded (otherwise, there would be no data to display in the DataGridView
). What we said before in Child Collection Properties for Brand
(Fig. 6) is that the child of Brand
is to be lazy loaded. This means ModelColl
is to be lazy loaded.
Fig. 10 - Other Brand properties
7.3. ModelColl
ModelColl
is a collection of Model
objects. It's an EditableChildCollection
(that's the CslaGen name for EditableChildList
). Again, we will start by the criteria section as a collection has no value properties.
7.3.1. Criteria section
ModelColl
must have a Criteria
where we specify how it will be fetched by its Brand
parent. Only the GetOption
s are set and all CreateOption
s and DeleteOptions
are False
or blank. The only difference to what shows in Fig. 3 is Procedure
that is set to True
so the Stored Procedure is generated. Of course, you could use a FilteredCriteria
as we did for BrandColl
.
The BrandID
property is used to fetch the ModelColl
as specified in CriteriaProperties
. Going back to Fig. 6 comments, the BrandID
property must also be used as a Criteria
on the parent object.
7.3.2. Other properties section
Other properties of ModelColl
are shown below. As usual, all 04. Child Object Options are very important in order to make it work.
- The property Lazy Load is similar to the property
LazyLoad
in Child Collection Properties of Brand
(Fig. 6). We want ModelColl
to be lazy loaded (re-read the comments on Fig. 10). - The property Parent Properties is set to
BrandID
and is the complement of the property LoadParameters
in Child Collection Properties of Brand
(Fig. 6). The latter property is set to Criteria.BrandID
(here Criteria
refers to a Brand
criteria). This property says how to bind the ModelColl
collection to the parent object. - The property Parent Type is the complement of the property
TypeName
in Child Collection Properties of Brand
(Fig. 6). This property says who the parent object of ModelColl
is. - Add Parent Reference is set to
False
. I guess it could be set to True
if you need to have a reference to your Brand
parent object in ModelColl
. - Item Type is set to
Model
.
Fig. 11 - Other ModelColl properties
7.4. Model
Model
is an EditableChild
object and has no children. We will start by the value properties.
7.4.1. Value properties section
The Model
object has the following value properties:
ModelID
(like BrandID
on Brand
)ModelName
(like BrandName
on Brand
)Price
RowVersion
(like RowVersion
on Brand
)
The Price
property data type is defined as money
under SQL Server, and in the .NET Framework, it "translates" to Decimal
.
You may find it difficult to understand why the BrandID
property isn't included in the Values Properties collection. No need for this as the Csla framework takes care of adding a Parent
property as soon as you call MarkAsChild
in the default constructor.
How is BrandID
stored in the Models
table? As you will see in Fig. 12, in 04. Child Object Options, the Parent Properties references BrandID
. And, CslaGen generates the Stored Procedure accordingly.
If you add the BrandID
property to the Values Properties collection, you will end up with duplicates. To avoid these duplicates, I tried a smart move: I added the BrandID
property to the Values Properties collection and set Parent Properties to blank. The problem was that the BrandID
value was always the default value and not the correct ID of the parent Brand
I was expecting.
7.4.2. Criteria section
This section is very similar to the Brand
criteria and you can refer to 7.2.3. Criteria section.
The Criteria
for Model
is used for fetching, updating, and deleting Model
objects, but doesn't take care of creating them as all CreateOption
s are False
or blank. The Criteria
for Model
uses two properties: ModelID
and RowVersion
. This means you need to supply both these parameters in order to fetch, update, or delete a Model
object.
The CriteriaNew
for Model
is used just for creating Model
objects and nothing else. You can see all DeleteOption
s and GetOption
s are False
or blank. Of course, it doesn't use any property. As the object doesn't exist yet, it has no ID or row version.
7.4.3. Other properties section
Other properties of Model
are shown below. As I said before, all 04. Child Object Options are very important in order to make it work.
Fig. 12 - Other Model properties
8. Changes to the generated code
In the Project Properties tab of CslaGen, you must check Use ".Designer" in file names. This way, CslaGen will generate two files per object (both public partial class
files):
- the <Object Name>.Designer.cs file is always generated
- the <Object Name>.cs file is generated only when it doesn't exist
The intended use is to make all your customizations on the non .Designer file so customizations aren't wiped out every time you make changes to any object and need to re-generate the project.
8.1. Object type transformation
In order to transform the CslaGen objects EditableRootCollection
+ EditableSwitchable
into the more convenient DynamicRootList
+ DynamicRoot
(CSLA template naming), there are two levels of changes that can be done. The minimal changes involve:
- change the base class of
BrandColl
- make sure
Brand
is never a child - adding an
AddNewCore
method to BrandColl
while full changes involve cleaning the useless code.
Full changes mean cleaning all traces of the Brand
object "switchability", including of couple of if
statements and deleting un-needed classes:
- one of the overloads of
GetBrand
on Brand.Designer.cs. DeleteBrand
on Brand.Designer.cs.GetBrandChild
on Brand.Designer.cs.NewBrandChild
on Brand.Designer.cs.DataPortal_Update
on BrandColl.Designer.cs.DataPortal_Delete
on BrandColl.Designer.cs.
We won't analyze these changes any further. The untouched generated files are included in CslaERLB1.zip as well as Brand.Designer.cs.minimal
so you can compare the files to find out the exact differences.
Starting with minimal changes, in BrandColl.Designer.cs, you need to change the base class from BusinessListBase<BrandColl, Brand>
to EditableRootListBase<Brand>
and comment some lines as shown below.
8.1.1. Changes to BrandColl.Designer.cs
public partial class BrandColl : EditableRootListBase<Brand>
(...)
public void Add()
{
Brand brand = Brand.NewBrand();
Add(brand);
}
*/
8.1.2. Changes to Brand.Designer.cs
In Brand.Designer.cs, you need to comment just one line.
internal static Brand GetBrand(SafeDataReader dr)
{
Brand obj = new Brand();
obj.Fetch(dr);
obj.MarkOld();
obj.ValidationRules.CheckRules();
return obj;
}
8.1.3. Changes to BrandColl.cs
In BrandColl.cs, you need to:
- add the
AddNewCore
method, as the DataGridView
requires it. - comment the
Initialize
method, as the DynamicRootList
base class doesn't define this method.
So. there is nothing to override.
#region Factory Methods
protected override object AddNewCore()
{
Brand item = Brand.NewBrand();
Add(item);
return item;
}
#endregion
(...)
8.2. BO customization
8.2.1. Changes to Brand.cs
These changes concern the validation rules only. CSLA 2.1 introduced several improvements in this area, namely RuleSeverity
. A word of warning concerning the DataGridView
: it uses its own internal error provider and not the standard ErrorProvider
. There is an enhanced ErrorWarnInfoProvider
that takes advantage of this feature and shows the different icons according to the severity, but you can't use it with the DataGridView
.
Since CSLA 3.0, you can specify a PropertyFriendlyName
, and brandNameRules
is used for that exact purpose.
It would be nice to have a CommonRules
for NoDuplicates
. That is possible since each editable child has a reference to its parent collection (as soon as you Add
it to the collection). Note the use of ToUpperInvariant
to make sure the match is case insensitive.
#region Validation Rules
protected override void AddBusinessRules()
{
var brandNameRules = new RuleArgs("BrandName", "Brand name");
ValidationRules.AddRule(CommonRules.StringRequired, brandNameRules);
ValidationRules.AddRule(CommonRules.StringMaxLength,
new CommonRules.MaxLengthRuleArgs(brandNameRules.PropertyName,
brandNameRules.PropertyFriendlyName, 20));
ValidationRules.AddRule<Brand, RuleArgs>(NoDuplicates, brandNameRules);
}
private static bool NoDuplicates<T>(T target, RuleArgs e) where T : Brand
{
var parent = (BrandColl)target.Parent;
if (parent != null)
{
foreach (var item in parent)
if (item.BrandName.ToUpperInvariant() ==
target.BrandName.ToUpperInvariant() &&
!ReferenceEquals(item, target))
{
e.Description = e.PropertyFriendlyName + " must be unique.";
return false;
}
}
return true;
}
#endregion
8.2.2. Changes to ModelColl.cs
In this file, all you need to do is:
- Add a
AddNewCore
method so the DataGridView
can save each row as soon as you leave the row.
The code involved is basically the same presented in 8.1.3. Changes to BrandColl.cs
8.2.3. Changes to Model.cs
In this file, you need to add the validation rules section:
- Add a
AddBusinessRules
method - Add a
NoDuplicates
method
The code involved is basically the same presented in 8.2.1. Changes to Brand.cs. Note that the uniqueness of the model name is checked for the parent Brand
only as specified by the use cases.
8.3 Stored procedures changes
The generated stored procedure code is almost right, except it should delete all models of a brand before deleting the brand itself. To correct that, edit Brand.sql and just before:
add these lines:
DELETE FROM [Models]
WHERE [BrandID] = @BrandID
The untouched generated code is included in CslaERLB1.zip.
This is the solution adopted on the ProjectTracker sample. It can be avoided if the Foreign Key specification is ON DELETE CASCADE
. We can argue about each solution's speed. I prefer an explicit statement in the Stored Procedure because it's more transparent and also more portable. CslaGen doesn't try to solve this problem and you must solve it yourself.
One interesting point to note is why this problem exists. When you delete an object, CSLA starts by deleting it from the database and then deletes the BO itself along with all its children.
Other parts of this article:
History
- Document version 1: 12 March 2009.