I realize this might be a question that "depends on your situation", but I am just trying to get my head around the best way to approach this scenario. Basically, I like the way Rocky designed this framework to get all of the database data in one shot. That is - unless you are also including the dropdown lists on the UI, then it seems like you need to make another round trip for each one.
I realized this when designing a project that I made a couple years ago and ended up putting the NVL objects inside the business objects. While this seems to solve the round-trip problem, it also means that I will always pull up the NVL data whether I am binding to a UI or not.
An example would be having a Product object that belongs to a specific Category. The Product object technically only needs to know the assigned CategoryID. However, for the user to select it on the UI, they will need a dropdown list filled with all of the CategoryIDs and Category Names. So, is it generally acceptable to pull up the list of CategoryIDs and Names every time we get an instance of a Product, whether it is used by the UI or not? Technically speaking, the Category NVL doesn't belong to the Product, it is only required to select the value that does belong.
I am just curious if anyone uses this approach and if there are any known drawbacks to it other than the extra overhead involved.
As I see it, CategoryList should be a separate class. If you are worried about extra trips to the server side to load it with data, then you might perhaps consider some caching techniques ? In practice, data in many lookup tables is quite static which makes them good candidates for caching.
robert_m:As I see it, CategoryList should be a separate class. If you are worried about extra trips to the server side to load it with data, then you might perhaps consider some caching techniques ? In practice, data in many lookup tables is quite static which makes them good candidates for caching.
Yes, we basically use "lazing loading" for these where they are loaded only on the first request for the factory method (GetList() in our case), and then from then on they stay around for the duration of the application. Subsequent calls just immediately return the one already fetched.
Here’s some pseudo-code that shows the basic concept.
public class Record
{
[NonSerialized]
private OfficeList _officeList;
[Browsable(false)]
public int OfficeList
{
get
{
if (_officeList ==
null)
_officeList = OfficeList.GetList(this.Company);
return _officeList;
}
}
[NonSerialized]
private DepartmentList _departmentList;
[Browsable(false)]
public int DepartmentList
{
get
{
if (_departmentList
== null)
_departmentList = DepartmentList.GetList(this.Office);
return
_departmentList;
}
}
private static PropertyInfo<int>
CompanyProperty = RegisterProperty<int>(typeof(ProjectResource), new
PropertyInfo<int>("Company"));
public int Company
{
get { return
GetProperty<int>(CompanyProperty); }
set { SetProperty<int>(CompanyProperty,
value); }
}
private static PropertyInfo<int>
OfficeProperty = RegisterProperty<int>(typeof(ProjectResource), new
PropertyInfo<int>("Office"));
public int Office
{
get { return
GetProperty<int>(OfficeProperty); }
set { SetProperty<int>(OfficeProperty,
value); }
}
private static PropertyInfo<int>
DepartmentProperty = RegisterProperty<int>(typeof(ProjectResource), new
PropertyInfo<int>("Department"));
public int Department
{
get { return
GetProperty<int>(DepartmentProperty); }
set { SetProperty<int>(DepartmentProperty,
value); }
}
protected override void AddBusinessRules()
{
ValidationRules.AddRule<Record>(InvalidateOfficeList<Record>,
CompanyProperty);
ValidationRules.AddRule<Record>(InvalidateDepartmentList<Record>,
OfficeProperty);
ValidationRules.AddDependentProperty(CompanyProperty, OfficeProperty);
ValidationRules.AddDependentProperty(CompanyProperty, DepartmentProperty);
ValidationRules.AddDependentProperty(OfficeProperty, DepartmentProperty);
}
private static bool
InvalidateOfficeList<T>(T target, RuleArgs e) where T: Record
{
target._officeList = null;
}
private static bool
InvalidateDepartmentList<T>(T target, RuleArgs e) where T: Record
{
target._departmentList = null;
}
}
I’ve used the logic you sent below to build the object built
when I try to use this object in BusinessListBase (I
want to show all the Record values in a list) I cannot get it to change Office values.
For instance, the 1st five records have the same Company and
Office. The 6th value has the same Company but the office is
different. I cannot get it to select the correct value in the Office on
the change. Does that make sense? Thanks for your help.
_________________________________________________
Jonathan Freeman| LBMC Technologies, LLC
5250 Virginia Way| 3rd Floor | Brentwood, TN 37027
p: 615.309.2447 | f: 615.309.2747 | jfreeman@lbmc.com
From: Rockford Lhotka
[mailto:cslanet@lhotka.net]
Sent: Thursday, February 21, 2008 11:39 AM
To: Jonathan Freeman
Subject: RE: [CSLA .NET] Should NVL objects for dropdowns be inside
Business Objects?
Here’s some pseudo-code that shows the basic concept.
public class Record
{
[NonSerialized]
private OfficeList _officeList;
[Browsable(false)]
public int OfficeList
{
get
{
if (_officeList ==
null)
_officeList = OfficeList.GetList(this.Company);
return _officeList;
}
}
[NonSerialized]
private DepartmentList _departmentList;
[Browsable(false)]
public int DepartmentList
{
get
{
if (_departmentList
== null)
_departmentList = DepartmentList.GetList(this.Office);
return
_departmentList;
}
}
private static PropertyInfo<int>
CompanyProperty = RegisterProperty<int>(typeof(ProjectResource), new
PropertyInfo<int>("Company"));
public int Company
{
get { return
GetProperty<int>(CompanyProperty); }
set {
SetProperty<int>(CompanyProperty, value); }
}
private static PropertyInfo<int>
OfficeProperty = RegisterProperty<int>(typeof(ProjectResource), new
PropertyInfo<int>("Office"));
public int Office
{
get { return GetProperty<int>(OfficeProperty);
}
set {
SetProperty<int>(OfficeProperty, value); }
}
private static PropertyInfo<int>
DepartmentProperty = RegisterProperty<int>(typeof(ProjectResource), new
PropertyInfo<int>("Department"));
public int Department
{
get { return
GetProperty<int>(DepartmentProperty); }
set {
SetProperty<int>(DepartmentProperty, value); }
}
protected override void AddBusinessRules()
{
ValidationRules.AddRule<Record>(InvalidateOfficeList<Record>,
CompanyProperty);
ValidationRules.AddRule<Record>(InvalidateDepartmentList<Record>,
OfficeProperty);
ValidationRules.AddDependentProperty(CompanyProperty, OfficeProperty);
ValidationRules.AddDependentProperty(CompanyProperty, DepartmentProperty);
ValidationRules.AddDependentProperty(OfficeProperty, DepartmentProperty);
}
private static bool
InvalidateOfficeList<T>(T target, RuleArgs e) where T: Record
{
target._officeList = null;
}
private static bool
InvalidateDepartmentList<T>(T target, RuleArgs e) where T: Record
{
target._departmentList = null;
}
}
Excuse me tagging on to this thread. My BOs expose some namevalue lists as properties that i bind a comboBox dartasource property to, However the act of binding the datasource alters the SelectedValue property which is not desirable. Having traced the databinding process I can see the SelectedValue binding then the DataSource binding which alters the SelectedValue.
Anyone have any ideas as to how I should be doing this properly
Regards
Simon
example of City, Country and Region of how I implemented it:
Most of the code is generated with codesmith templates.
Maybe what is interesting is the code in bold: I added to the City class a property Country in a lazy way loading.
So it is possible for the person who is using the business layer to write something like Employee.City.Country.
But in the code the way I implemented everytime the database will be hit. Does somebody have an idea how to avoid that?
Another question what is the relationship between city, country and region. I've treated them as seperated root objects. Is that correct?
using System;
namespace Fim.HR.JL.Bus.Components
{
[Serializable()]
public partial class City : Csla.ReadOnlyBase<City>
{
public int CityId { get; private set; }
public string CountryId { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public string CreatedBy { get; private set; }
public DateTime Created { get; private set; }
public string ModifiedBy { get; private set; }
public DateTime? Modified { get; private set; }
internal static City GetCity(Fim.HR.JL.Dat.Components.City data)
{
City item = new City();
item.Fetch(data);
return item;
}
private City()
{ /* require use of factory method */ }
#region Data Access
private void Fetch(Fim.HR.JL.Dat.Components.City data)
{
bool cancel = false;
OnFetching(ref cancel);
if (cancel) return;
CityId = data.numCityId;
CountryId = data.strCountryId;
Name = data.strName;
Description = data.strDescription;
CreatedBy = data.strCreatedBy;
Created = data.datCreated;
ModifiedBy = data.strModifiedBy;
Modified = data.datModified;
OnFetched();
}
partial void OnFetching(ref bool cancel);
partial void OnFetched();
#endregion //Data Access
}
}
using System.Linq;
using Csla;
using Csla.Data;
using Csla.Security;
using Csla.Validation;
using Fim.HR.JL.Dat.Components;
namespace Fim.HR.JL.Bus.Components
{
public partial class City : Csla.ReadOnlyBase<City>
{
public static City GetCity(int? id)
{
if (id == null)
{
return null;
}
return DataPortal.Fetch<City>(new SingleCriteria<City, int?>(id));
}
private void DataPortal_Fetch(SingleCriteria<City, int?> criteria)
{
if (criteria.Value != null)
{
using (var mgr = ContextManager<Fim.HR.JL.Dat.Components.HRJLDataContext>
.GetManager(Database.HRJL, false))
{
var data = (from r in mgr.DataContext.Cities
where r.numCityId == criteria.Value
select r).SingleOrDefault();
if (data != null)
Fetch(data);
}//using
}
}
public Country Country
{
get
{
return Country.GetCountry(CountryId);
}
}
}
}
-----------------------------------------COUNTRY-------------------------------------
using System;
namespace Fim.HR.JL.Bus.Components
{
[Serializable()]
public partial class Country : Csla.ReadOnlyBase<Country>
{
public string CountryId { get; private set; }
public string RegionId { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public string CreatedBy { get; private set; }
public DateTime Created { get; private set; }
public string ModifiedBy { get; private set; }
public DateTime? Modified { get; private set; }
internal static Country GetCountry(Fim.HR.JL.Dat.Components.Country data)
{
Country item = new Country();
item.Fetch(data);
return item;
}
private Country()
{ /* require use of factory method */ }
#region Data Access
private void Fetch(Fim.HR.JL.Dat.Components.Country data)
{
bool cancel = false;
OnFetching(ref cancel);
if (cancel) return;
CountryId = data.strCountryId;
RegionId = data.strRegionId;
Name = data.strName;
Description = data.strDescription;
CreatedBy = data.strCreatedBy;
Created = data.datCreated;
ModifiedBy = data.strModifiedBy;
Modified = data.datModified;
OnFetched();
}
partial void OnFetching(ref bool cancel);
partial void OnFetched();
#endregion //Data Access
}
}
using System.Linq;
using Csla;
using Csla.Data;
using Csla.Security;
using Csla.Validation;
using Fim.HR.JL.Dat.Components;
namespace Fim.HR.JL.Bus.Components
{
public partial class Country : Csla.ReadOnlyBase<Country>
{
public static Country GetCountry(string id)
{
return DataPortal.Fetch<Country>(new SingleCriteria<Country, string>(id));
}
private void DataPortal_Fetch(SingleCriteria<Country, string> criteria)
{
if (!string.IsNullOrEmpty( criteria.Value) )
{
using (var mgr = ContextManager<Fim.HR.JL.Dat.Components.HRJLDataContext>
.GetManager(Database.HRJL, false))
{
var data = (from r in mgr.DataContext.Countries
where r.strCountryId == criteria.Value
select r).Single();
CountryId = data.strCountryId;
RegionId = data.strRegionId;
Name = data.strName;
Description = data.strDescription;
CreatedBy = data.strCreatedBy;
Created = data.datCreated;
ModifiedBy = data.strModifiedBy;
Modified = data.datModified;
}//using
}
}
}
}
---------------------------------REGION-----------------------------------------
using System;
namespace Fim.HR.JL.Bus.Components
{
[Serializable()]
public partial class Region : Csla.ReadOnlyBase<Region>
{
public string RegionId { get; private set; }
public string Name { get; private set; }
public string Description { get; private set; }
public string CreatedBy { get; private set; }
public DateTime Created { get; private set; }
public string ModifiedBy { get; private set; }
public DateTime? Modified { get; private set; }
internal static Region GetRegion(Fim.HR.JL.Dat.Components.Region data)
{
Region item = new Region();
item.Fetch(data);
return item;
}
private Region()
{ /* require use of factory method */ }
#region Data Access
private void Fetch(Fim.HR.JL.Dat.Components.Region data)
{
bool cancel = false;
OnFetching(ref cancel);
if (cancel) return;
RegionId = data.strRegionId;
Name = data.strName;
Description = data.strDescription;
CreatedBy = data.strCreatedBy;
Created = data.datCreated;
ModifiedBy = data.strModifiedBy;
Modified = data.datModified;
OnFetched();
}
partial void OnFetching(ref bool cancel);
partial void OnFetched();
#endregion //Data Access
}
}
To avoid loading the Country several times I implemented the following in the City class :
public Country Country{
get{
if (!countryLoaded){
country =
Country.GetCountry(CountryId);countryLoaded =
true;}
return country;}
}
Or can I use the fieldmanager for that? But pay attention City inherits from Csla.ReadOnlyBase.
City is a child of CityList :
using
System;using
System.Linq;using
Csla;using
Csla.Data;using
Fim.HR.JL.Dat.Components;namespace
Fim.HR.JL.Bus.Components{
[
Serializable()] public partial class CityList : Csla.ReadOnlyListBase<CityList, City>{
#region
Factory Methods private CityList(){
/* require use of factory method */ } public static CityList GetCityList(){
return DataPortal.Fetch<CityList>();}
#endregion
//Factory Methods#region
Data Access#region
Data Access - Fetch private void DataPortal_Fetch(){
bool cancel = false;OnFetching(
ref cancel); if (cancel) return; using (var mgr = ContextManager<Fim.HR.JL.Dat.Components.HRJLDataContext>.GetManager(
Database.HRJL,false)){
RaiseListChangedEvents =
false;IsReadOnly =
false; this.AddRange( from row in mgr.DataContext.Cities select City.GetCity(row));
IsReadOnly =
true;RaiseListChangedEvents =
true;}
//using
OnFetched();
}
partial void OnFetching(ref bool cancel); partial void OnFetched();#endregion
//Data Access - Fetch#endregion
//Data Access}
}
Hi,
I absolutly agree with robert_m
Ben
I believe it's good for encapsualtion and the lists are cached anyway so a round trip to the database only occurs the first time a list needs to be used, or when the list changes (if using the latest CSLA).
Yes...I mean that the NVL class will be seperate but you'd still have properties in the BO to access it as per the other posts here.
I tend to create a Use Case Controller object to handle the requirements. This is simply a normal Root BO which contains the Product BO as well as the NVLs for the use case. When I create a New instance of the controller object it fetches the Product and NVLs. I can add rules to the controller BO if it needs to verify data across more than one contained object. When I save the controller it doesn't usually save any data itself, it orchestrates the saving of the data in the contained BOs.
This keeps your Product object clean. Then it can be re-used in any other Use Case or UI without fear that it will contain a bunch of NVLs which are not needed in the other cases.
I would create a 2nd controller BO to contain a Product object if it required different NVLs (or other BOs) than the first Use Case.
Joe
A simpler solution may be using the Factory pattern combined with the Singleton pattern:
Public Class ProvidersNVL
Inherits NameValueListBase(Of System.String, System.String)
#Region " Singleton Pattern "
Protected Shared mList As ProvidersNVL
Public Shared Function GetList() As ProvidersNVL
If mList Is Nothing Then
mList = ActiveObjects.DataPortal.Fetch(Of ProvidersNVL)
End If
Return mList
End Function
Public Shared Sub InvalidateCache()
mList = Nothing
End Sub
#End Region ' Singleton Pattern
...
End Class
With this example the business layer and the UI layer can do the following every time the list is needed. By this way all objects can share the same information:
Dim list As ProvidersNVL
list = ProvidersNVL.GetList
In our application we have many business objects sharing many NameValueLists (for instances CountriesNVL, ProvidersNVL, CustomersNVL, …) and all of them are loaded on demand and cached. You may have an Invoice BO that may need the CustomersNVL to validate its CustomerID property and you may have a DataForm with a ComboBox that uses the same CustomersNVL. But you may have many scenarios where both objects (the Invoice and its DataForm) are used and no one of them needs the CustomersNVL so you can avoid loading every NameValueList that may be used in any one of them. We always try to follow the lazy loading philosophy.
Ben
Joe,
I think you came up with an interesting solution to this issue. You are seperating even more business logic from the UI by having your Use Case BO decide which NVLs and BOs to load and save. This makes things simple on the UI because only one object reference is required.
However, it doesn't seem like you are accomplishing this any different than if it were in the UI directly. Namely, you are still making round trips to the database for each NVL. Correct me if I am wrong.
It still seems like this pattern could be used to handle even more complexity than just loading NVLs. Let's test this. Let's say instead of having a simple NVL to run the dropdown, we have a Read-Only collection with 4 other values we are interested in saving with the main BO.
Going back to our product object, let's say we have a "Size" dropdown and each size changes the SKU of the product to something else. Furthermore, each size adds or subtracts an amount to the price. So in this example if the user selects the size they want from the dropdown, we will need to save the SizeID (primary key), the size description (for use on the invoice), the SKU Fragment (what part of the SKU will change to - this will require additional business logic), and a price adjustment field.
It seems like in this example, you could have your Use Case BO handle all of the logic of changing the SKU and the price as well as pulling the other two fields from the ReadOnlyList to populate in ShoppingCart object. Or do you think in this (more complex) case it would make more sense to encapsulate all of this business logic inside of the Product object by including the ReadOnlyList inside of the Product?
My problem is even more complex than this is, but I am just trying to see if this solution can be used to simplify some of the complexity.
-NightOwl888
"However, it doesn't seem like you are accomplishing this any different than if it were in the UI directly. Namely, you are still making round trips to the database for each NVL. Correct me if I am wrong."
The above statement is correct. The major differece is that the UI is "dumb" in one case and "too smart" in the other. I think we want a "dumb" UI so that when it comes time to build a new one we don't have to transfer a lot of logic from the old UI to the new one.
As far as round trips to the DB - I tend to want to get the values from the DB without caching. (They change a lot.) But there is nothing preventing you from caching these NVLs and then have the controller BO fetch them from the cache. It is a preference.
===================================================================
"...would make more sense to encapsulate all of this business logic inside of the Product object by including the ReadOnlyList inside of the Product?"
No. It would only make sense if the Product object *always* needs the ROC in every Use Case. You might re-use the same controller object because you have some use cases which overlap.
===================================================================
"It still seems like this pattern could be used to handle even more complexity than just loading NVLs."
It is. <g>
I load root BOs with complex hierarchies, ROCs, NVLs, etc. All the different BOs which are required to satisfy the use case.
===================================================================
When you have an NVL inside a Use Case Controller BO, you fetch it and bind the values to the screen. But you *unbind* the selected value into one of your other BOs (the FK.)
===================================================================
You sample with the ROC is more complex than an NVL. But the same ideas could apply. In this case you should decide if you really need an ROC at all or whether the Use Controller will fetch those 4 values instead. The controller BO could then bind them to the UI and let the user change all the values and then decide what to do with them later. Whereas if you just have an ROC the user can't really modify the values.
If you only want the user to select an ID from the dropdown and then you look up that row in the ROC and "transfer" all the data to your other BO - that seems reasonable to me.
The bottom line is that the Controller BO can centralize all the activity for this Use Case.
Joe
I use the following guideline:
If the NVL is the same regardless of context or business object data, then I implement a factory directly on the NVL.
If the NVL is different depending on the context or business object data, then I implement a property on the context/business object to return the appropriate NVL.
Consider a list of country codes. In many cases such a list will be the same at all times, and so should be globally and directly available.
list = CountryCodes.GetList()
But then consider a list of country codes where a product can be sold. Some products might have restrictions on export/import/etc. This is very dependent on the OrderLineItem object (as an example), and so the list should come from that object:
list = _order.LineItems[42].ValidCountryCodes
As per the earlier content of the thread, your Company object can be a root object.
But your Office and Department objects should exist within some other root object. You say the user is creating a "record", so let's call that a Record object.
The Record object should expose OfficeList and DepartmentList properties, which return appropriately filtered lists.
The Record object also has Company, Office and Department properties - presumably getting and setting the key values from the three NVL objects. When the Company property is set that would trigger reloading the OfficeList and DepartmentList NVL properties, and revalidation of the Office and Department properties (because they might no longer be valid at all now).
Rocky,
The way I have done this in my project is to have three NVL classes, CompanyNVL, CompanyOfficeNVL and OfficeDepartmentNVL. I get these NVL with factory methods e.g.
Dim mCompanyList As CompanyNVL = CompanyNVL.Load()
Dim mOfficeList As CompanyOfficeNVL = CompanyOfficeNVL.Load(CompanyID)
Dim mDepartmentList As OfficeDepartmentNVL = OfficeDepartmentNVL.Load(OfficeID)
Is this correct by design?
Tahir
Yes, that’s exactly what I do.
But the “Record” object in the example needs to
validate the Office and Department properties, so it needs access to the
properly filtered lists, so it must already have a containment relationship with
those objects, so it is reasonable for it to provide navigation to those
pre-filtered objects as well.
So I tend to make those particular factories Friend/internal,
thus forcing the UI developer to go through my “Record” object –
ensuring they get the correct filtering for each specific Record.
Rocky
From: tna55
[mailto:cslanet@lhotka.net]
Sent: Thursday, February 21, 2008 7:13 AM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] Should NVL objects for dropdowns be inside
Business Objects?
Rocky,
The way I have done this in my project is to have three NVL classes,
CompanyNVL, CompanyOfficeNVL and OfficeDepartmentNVL. I get these NVL with
factory methods e.g.
Dim mCompanyList As CompanyNVL = CompanyNVL.Load()
Dim mOfficeList As CompanyOfficeNVL = CompanyOfficeNVL.Load(CompanyID)
Dim mDepartmentList As OfficeDepartmentNVL =
OfficeDepartmentNVL.Load(OfficeID)
Is this correct by design?
Tahir
Copyright (c) Marimer LLC