We are working on an client (WPF, WCF) app that contains a significant amount of data, and performance is of utmost concern. In an effort to optimize the performance of some screens, I decided to experiment with flattening the object model a bit, and designed some classes where an outermost ReadOnlyListBase is the only CSLA-derived entity in the hierarchy. I did this for the benefits of having it consistent with all of our other objects, in terms of instantiation and keeping the configuration for it the same.
But, I wanted to know if there was any significant reason why this would be a bad idea. I scanned the forums and the books, and didn't see anything clearly that would suggest it is not good.
Here is an example of the design:
DailyScheduleList : ReadOnlyListBase(DailyScheduleList, DailyScheduleInfo)
DailyScheduleInfo : List<SlotInfo>
SlotInfo : List<SliceInfo>
SliceInfo
The data for the app is retrieved by a date range, typically a month, and the data may vary significantly by day, and each day contains "lists" of slots which each contain lists of slices. It does appear that this method is faster than the way I initially designed it, which was having the lists contain an object that derived from ReadOnlyBase which in turn had a property of type ReadOnlyListBase to contain the next list, though I haven't benchmarked it.
Any thoughts would be appreciated.
Thank you for the feedback. In this case, you are correct that per-propety access checks are unnecessary.
I went ahead and decided to write a test to see if I was indeed making "evil" optimizations. I wrote a test using pure-CSLA and my bastardized CSLA optimization. I already described the bastardized classes, so here are the pure-CSLA:
CslaDailyScheduleList : ReadOnlyListBase<CslaDailyScheduleList, CslaDailyScheduleInfo>
CslaDailyScheduleInfo : ReadOnlyBase<CslaDailyScheduleInfo>
(This class has a Slots property of the following type)
CslaSlotList : ReadOnlyListBase<CslaSlotList, CslaSlotInfo>
CslaSlotInfo : ReadOnlyBase<CslaSlotInfo>
(This class has a Slices property of the following type)
CslaSliceList : ReadOnlyListBase<CslaSliceList, CslaSliceInfo>
CslaSliceInfo : ReadOnlyBase<CslaSliceInfo>
To mimic our general data in this particular view, which is a calendar view, I wrote a loop that loaded both sets identically, with 31 objects in the outer list, and each one of those having 10 slots, with 3 slices.
I ran tests using both a local and remote DataPortal. What I found seems to indicate that in this case my optimization were indeed about 10X faster. The test itself is below.
Local data portal:
Pure/Bastardized: 130 ms/14 ms
Remote data portal:
Pure/Bastardized: 4972 ms/594 ms
So, clearly, there is a dramatic speed differential. Our app is dealing with inventory and schedules, and many screens are dealing with groups of read-only data by day (that itself may have nested collections), so I'm leaning towards starting to use this more across the board. If anyone else has any thoughts on this, I'd love to hear how you handled situations where you had large quantities of read-only data and wanted to optimize performance in a CSLA-centric design. I love CSLA, don't get me wrong. We are using it throughout the app. But one screen in particular was quite sluggish, and this change sped it up noticeably.
[
TestMethod] public void CompareCslaVsNonCslaCollections(){
GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); int start; int stop;start =
Environment.TickCount; CslaDailyList list = CslaDailyList.GetList();stop =
Environment.TickCount; Trace.WriteLine("Elapsed time with CSLA: " + (stop - start).ToString()); GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();start =
Environment.TickCount; NonCslaDailyList nonlist = NonCslaDailyList.GetList();stop =
Environment.TickCount; Trace.WriteLine("Elapsed time with non-CSLA: " + (stop - start).ToString());}
The internal factory methods are vanilla. No DB access at all, and the only code for loading the objects is in the DP_Fetch of the outermost list. Here they are:
Csla: (The EnableLoading method is a method I added to set IsReadOnly and RaiseListChangedEvents accordingly...since I was loading external to the entity).
private
void DataPortal_Fetch(){
this.IsReadOnly = false; this.RaiseListChangedEvents = false; DateTime tmpDate = DateTime.Today; for (int i = 1; i <= 31; i++){
CslaDailyInfo dailyInfo = CslaDailyInfo.Get();dailyInfo.Date = tmpDate;
dailyInfo.Id = i;
dailyInfo.Slots.EnableLoading(
true); this.Add(dailyInfo); // Add 10 slots a day. for (int j = 1; j <= 10; j++){
CslaSlotInfo slot = CslaSlotInfo.Get();slot.ScheduleId = i;
slot.Ordinal = j;
slot.Slices.EnableLoading(
true);dailyInfo.Slots.Add(slot);
// Add 3 slices a day. for (int k = 0; k < 3; k++){
CslaSliceInfo slice = CslaSliceInfo.Get();slice.Id = k;
slice.AdvertiserId = 100 + k;
slice.AdvertiserName =
"Advertiser " + k.ToString();slice.BookingId = k;
slice.BookingName =
"Booking " + k.ToString();slot.Slices.Add(slice);
}
slot.Slices.EnableLoading(
false);}
dailyInfo.Slots.EnableLoading(
false);tmpDate = tmpDate.AddDays(1);
}
this.IsReadOnly = true; this.RaiseListChangedEvents = true;}
Non-CSLA:
private
void DataPortal_Fetch(){
this.IsReadOnly = false; this.RaiseListChangedEvents = false; DateTime tmpDate = DateTime.Today; for (int i = 1; i <= 31; i++){
NonCslaDailyInfo dailyInfo = new NonCslaDailyInfo();dailyInfo.Date = tmpDate;
dailyInfo.Id = i;
this.Add(dailyInfo); // Add 10 slots a day. for (int j = 1; j <= 10; j++){
SlotInfo slot = new SlotInfo();slot.ScheduleId = i;
slot.Ordinal = j;
dailyInfo.Add(slot);
// Add 3 slices a day. for (int k = 0; k < 3; k++){
SliceInfo slice = new SliceInfo();slice.Id = k;
slice.AdvertiserId = 100 + k;
slice.AdvertiserName =
"Advertiser " + k.ToString();slice.BookingId = k;
slice.BookingName =
"Booking " + k.ToString();slot.Add(slice);
}
}
tmpDate = tmpDate.AddDays(1);
}
this.IsReadOnly = true; this.RaiseListChangedEvents = true;}
I am quite familiar with loading all objects in a CSLA hierarchy with one call. We do that exclusively. As I said, this call used for the test does not hit the database. In this project we have objects that contain up to 5 levels deep of nesting that are populated via a single call to the DB, and the resultsets from the data reader is passed to the objects that need it.
My suspicion is that the (in this case, unnecessary) overhead comes from all of the additional work CSLA does when it is dealing with its classes. I see code being called in the ReadOnlyBase constructors, and assume more work is also being done when serialization occurs. Bypassing this by using lightweight DTO objects wrapped in an outer CSLA entity seems to be significantly faster for this case.
Hmmm...I just went looking for that thread but couldn't find it. I did see it though as addressed in the change log of 3.5.
We do not have any rules for any of the types in this read-only hierarcy, so that may indeed be it. Thanks again for your feedback.
Copyright (c) Marimer LLC