HUGE difference in performance using CSLA over Datatable -- CSLA is SLOW!!!

HUGE difference in performance using CSLA over Datatable -- CSLA is SLOW!!!

Old forum URL: forums.lhotka.net/forums/t/4293.aspx


BillyM posted on Monday, February 04, 2008

I am seeing a HUGE performance hit in populating a large list via WCF using CSLA as opposed to simply using a DataSet and am assuming we are doing something fundamentally wrong.

 

Returning 5000 rows reuslts in:

The problem appears to be in the DataPortal.Fetch method, which is averaging approximately 15 ms each.

 

CODE SNIPPETS

 

Stored Procedure Call:

ALTER    procedure [dbo].[sp_GetCustomer] As

Select top 5000 customer_id,name_first,name_middle,name_last, phone_home from customer

 

I) DataTable ACCESS (136 ms):

 

public DataTable GetAllCustomers(string connectionString)

{

    SqlParameter[] parameters = { };

    return = SqlAccessor.ExecuteDataSet(connectionString, SqlAccessor.SqlCommandBuilder(new SqlCommand("sp_GetCustomer"), parameters), CommandType.StoredProcedure, "").Tables[0]; 

}

 

II) CSLA ACCESS (76212 ms):

 

CustomerList: (Performance hit is in the "while" loop processing 5000 records – it is KILLING us)

 

public class CustomerList : ReadOnlyListBase<CustomerList, Customer> 

 

private void Fetch()

 {

      this.RaiseListChangedEvents = false;

      using (SqlConnection cn = new SqlConnection(Database.ConnectionString))

      {

          cn.Open();

          using (SqlCommand cm = cn.CreateCommand())

          {

              cm.CommandType = CommandType.StoredProcedure;

              cm.CommandText = "sp_GetCustomer";

              using (SafeDataReader dr = new SafeDataReader(cm.ExecuteReader()))

              {       

                  IsReadOnly = false;       

                  while (dr.Read())

                  {

                       Customer info = Customer.GetCustomer(dr.GetDecimal("customer_id"));

                       this.Add(info);

                  }

                  IsReadOnly = true;

            }

      }

      this.RaiseListChangedEvents = true;

}

 

CUSTOMER: (Averages approx 15 ms per access)

 

public class Customer : BusinessBase<Customer>

 

public static Customer GetCustomer(decimal customer_id)

{

    return cust = DataPortal.Fetch<Customer>(new Criteria(customer_id));

}

 

mr_lasseter replied on Monday, February 04, 2008

Normally when you create a ReadOnlyList you create it of ReadOnlyObjects, which you aren't doing in this case.  I am not sure in what scenario you would actually do this, but your real problem is you are hitting the database 5001 times in the CSLA code.  From what I can see the stored procedure already pulls back all the information you need to populate the Customer.  So you could create a new method on the Customer Class as such:

public static Customer LoadCustomer(SafeDataReader dr)
{
    _customerId = dr.GetInt32("customer_id);
    _firstName = dr.GetString("name_first");
    //so on
}

Once you do this you should find that the performance of CSLA is actually faster than the DataSet/DataTable.

Hope this helps,
Mike

vdhant replied on Monday, February 04, 2008

I agree with the above. Its the fact that you are doing the one main database access and then 5000 more (one for each item) and you are going through the dataprotal 5001 times. As said above you should have the one SQL statement which returns all data that is required and then pass the datareader through to the load method (or what ever method you have). Once you so this you will see much better performance.

Anthony

skagen00 replied on Monday, February 04, 2008

I just wanted to add to this thread to add agreement to the prior two posters as well as refer to a thread where there is some performance discussion.

http://forums.lhotka.net/forums/thread/16966.aspx

ozitraveler also cited some statistics in this thread:

http://forums.lhotka.net/forums/thread/4044.aspx

Truly, the first post of this thread is, as the other two posters above me stated, not comparing apples to apples. The DataTable in the GetAllCustomers method seems to indicate that the sp_GetCustomer stored procedure will pull down all customers with the right parameters.

To adequately modify the Csla case, I would change the items in red to this:

                  while (dr.Read())

                  {

                       Customer info = Customer.GetCustomer(dr);

                       this.Add(info);

                  }

 

The factory method GetCustomer would contain an internal overload that takes a datareader and loads the customer data from the datareader.

I would encourage the author to make these changes and then cite the new statistics. If it's still slow, then I'd myself be interested in running a test of my own. But I would highly suspect that the author will find markedly different results when comparing apples to apples.

Chris

 

 

JonnyBee replied on Tuesday, February 05, 2008

Hi,

First of all - you are using an Editable object in a ReadOnlyList. You should be using a ReadOnlyBase object in a ReadOnlyList (and thus you should set the private variables in the readonly object and have a very good performance).

Next thing to check:
In your business object - are you using the standard Set/Get methods? Specifically the CanWriteProperty(true). This method uses reflection and can severly slow down your application. You should change to CanWriteProperty("propertyname", true) IF you need this type of protection. The same goes for CanReadProperty and PropertyHasChanged methods also.

/jonnybee

BillyM replied on Tuesday, February 05, 2008

Thanks everyone. The team made the following changes:

New benchmarks are:

CSLA

DataTable

ajj3085 replied on Tuesday, February 05, 2008

You didn't mention it... but in your Customer constructor, are you setting the fields directly or through the property code?  You should be setting them directly via the fields.

BillyM replied on Tuesday, February 05, 2008

We are setting the fields.

lawrencek replied on Tuesday, February 05, 2008

you can get a small performance increase by using the int overloads of the Get methods on the data reader 

RockfordLhotka replied on Wednesday, February 06, 2008

Also make sure you are using 2.1.4 or preferably 3.0.3+. If you are using 2.0.x you are missing out on some important performance enhancements that would impact this scenario.

It is also important to be aware of what code is running during the load process. If you are calling ValidationRules.CheckRules() in each object's Fetch(), for example, then you are triggering a bunch of work - which may be beneficial, but takes time.

It is always important to remember that performance isn't the only metric - beneficial behaviors are sometimes worth some processing time. But it is also important to be aware of which behaviors are being triggered and to not trigger those you don't need.

Another thing to check is to make sure that your collection object turned off its eventing during the load process - no sense wasting time processing/raising events that go nowhere, and that you wouldn't want even if they did go somewhere.

webjedi replied on Wednesday, February 06, 2008

RockfordLhotka:

It is also important to be aware of what code is running during the load process. If you are calling ValidationRules.CheckRules() in each object's Fetch(), for example, then you are triggering a bunch of work - which may be beneficial, but takes time.

It is always important to remember that performance isn't the only metric - beneficial behaviors are sometimes worth some processing time. But it is also important to be aware of which behaviors are being triggered and to not trigger those you don't need.

And to draw a dotted line to a related matter, if you are just pulling the dataset and then doing some validation later you may be only deferring your performance 'hit'.  I mean if you are doing all the validation using CSLA the process might be taking ~800ms, but doing everything, where if you are doing a load with a straight dataset it might be taking ~500ms but later on when you do your processing/validation it might be adding more time.

Ryan

BillyM replied on Wednesday, February 06, 2008

Rocky... We are using 3.0.2... will that be a problem?

RockfordLhotka replied on Wednesday, February 06, 2008

Should be fine – 2.1 was where the big perf changes happened that would affect this scenario.

 

Rocky

 

From: BillyM [mailto:cslanet@lhotka.net]
Sent: Wednesday, February 06, 2008 2:47 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] HUGE difference in performance using CSLA over Datatable -- CSLA is SLOW!!!

 

Rocky... We are using 3.0.2... will that be a problem?

rsbaker0 replied on Thursday, February 07, 2008

Just for comparison sake, we are using CSLA bolted over an ORMapper (mainly because it provides a database-independent DAL), and we can retrieve 2000+ BusinessBase objects / second even from a lowly workstation database.

No, this isn't as fast as your data table, but it's much faster than the technology we are porting from.

We really didn't notice any difference in this performance when we added CSLA into the mix (originally we talked directly to the mapper).

(NHibernate, incidentally, was a good deal faster than the mapper we use - 8,000 + objects/second, but only because it generates fast property access code at run time. If this feature was turned off, it was about the same).

Maqster replied on Friday, February 08, 2008

Hi rsbaker0, I'm interested in knowing what mapper you are using?

/Maqster

rsbaker0 replied on Tuesday, February 12, 2008

Maqster:
Hi rsbaker0, I'm interested in knowing what mapper you are using?

/Maqster

We're using the Wilson ORMapper.

We looked at several alternatives because we have some specific requirements. We were migrating a legacy application in which users have the capability to modify both the size of existing fields (both numeric and character) as well as add additional custom fields and tables, which was very limiting in terms of what would work. NHibernate can do this "out of the box", but figuring out how to dynamically modify it's "session factory" at runtime to recognize the additional fields was *very* daunting. Wilson's mapper is small, acceptably fast, nicely written, and easy to modify. It also generates very nice, parameterized SQL.

As to the thread topic, Wilson implements an IObjectHelper interface that sets the fields when loading objects instead of using the properties (which cause all sorts of events and virtual method hits), so we noticed literally no change in loading a CSLA object versus a native mapped object. This as true even though we basically have to "clone" each object as it is loaded as Wilson's mapper doesn't have the capability to populate an object you have already created (which is what DataPortal_Fetch wants to do).

Copyright (c) Marimer LLC