RecentComments

Comment RSS

Domain Driven Design, Business Objects, Datasets, NHibernate and friend Jack (Part 2/2)

by Ioannis 12. February 2009 19:59

 

In the previous post, we have discussed some issues that emerged from a conversation with my friend Jack. In this post we will explore a little bit his following statement:

Jack: Well(…) I have created a BO (a simple one, mapped to a single table in the DB) and created a very simple DataLayer using NHibernate to retrieve a list of 1500 BOs and display them in a DataGrid. It took NHibernate 6 sec to retrieve the data! Can you believe it? (…) I have tried the same thing with a DataSet and it took only some msec (…)  I tried to find a solution to this in the internet for 3 hours and found nothing. NHibernate is poorly documented too… Nothing beats DataSets.

This needs proof. This needs code. So I have implemented a small application as my testbench to perform my measurement with NHibernate. The TestBench is as follows:

We create a small DB in MS SQL with a single table and 20000 records:

As you can see the table consists of a Primary Key (ID – Bigint) and 5 columns with strings (String1..5) of type nvarchar(50).

Now we go to Visual Studio, create a new application and implement the Business Object as follows (this is what Jack did):

 

That is we provide a Property and a Field for every column in the Table. The FirePropertyChangedNotification(value) method is at the parent GenericBO class and is implemented as follows:

Which is the typical BO’s support of the INotifyPropertyChanged interface needed for updating the GUI correctly in data binding scenarios.

The next thing was to set up NHibernate and provide the mapping file between the table and the BO:

Now it was time to perform some tests. I decided to test several ways of retrieving the list of TestClass BOs both with NHibernate Session and SessionStateless. So the tests are as follows (all tests return List<TestClass> unless explicitly specified):

 

I got the following times (for test 3 to work I needed to specify a property ID named as the column in the Table in TestClass whose accessors are accessing the field _primaryKey) :

  Test 1 Test 2 Test 3 Test 4
1st Time 26,078sec 23,927 sec 23,113 sec 23,735 sec
2nd Time 0,117 sec 0,098 sec 23,050 sec 0,106 sec
Times include time to display in WPF DataGrid

The results are disappointing. Of course the second time, since there is no change in the Cache the times are very low (in Test3 the times are the same for the first and second time since we are performing a plain mapping with a transformer) but what happens in the first Time? Will we have better lack if we perform the same tests in a StateLess session?

  Test 1 Test 2 Test 3 Test 4
1st Time 25,573sec 24,890sec 24,531sec N/A
2nd Time 25.041 sec 25,029 sec 23,427sec N/A
Times include time to display in WPF DataGrid

Now we are worse off. First and Second time are huge due to the absence of the cache. Was Jack right after all? Before jumping into conclusions let’s enable logging for NHibernate and see the log:

Retrieval starts at:

2009-02-11 21:41:05,268 [10] DEBUG NHibernate.Loader.Loader - processing result set
2009-02-11 21:41:05,268 [10] DEBUG NHibernate.Loader.Loader - result set row: 0
2009-02-11 21:41:05,274 [10] DEBUG NHibernate.Loader.Loader - result row: EntityKey[ORMTests.TestClass#1]
2009-02-11 21:41:05,279 [10] DEBUG NHibernate.Loader.Loader - Initializing object from DataReader: [ORMTests.TestClass#1]
2009-02-11 21:41:05,285 [10] DEBUG NHibernate.Persister.Entity.AbstractEntityPersister - Hydrating entity: [ORMTests.TestClass#1]
 

First phase ends at:

2009-02-11 21:41:11,949 [10] DEBUG NHibernate.Loader.Loader - result set row: 19999
2009-02-11 21:41:11,949 [10] DEBUG NHibernate.Loader.Loader - result row: EntityKey[ORMTests.TestClass#20000]
2009-02-11 21:41:11,949 [10] DEBUG NHibernate.Loader.Loader - Initializing object from DataReader: [ORMTests.TestClass#20000]
2009-02-11 21:41:11,949 [10] DEBUG NHibernate.Persister.Entity.AbstractEntityPersister - Hydrating entity: [ORMTests.TestClass#20000]
2009-02-11 21:41:11,949 [10] DEBUG NHibernate.Loader.Loader - done processing result set (20000 rows)

First phase takes 6 seconds !

Second phase starts at:

2009-02-11 21:41:11,957 [10] DEBUG NHibernate.Engine.TwoPhaseLoad - resolving associations for [ORMTests.TestClass#1]
2009-02-11 21:41:11,962 [10] DEBUG NHibernate.Engine.TwoPhaseLoad - done materializing entity [ORMTests.TestClass#1]

Second phase ends at:

2009-02-11 21:41:32,601 [10] DEBUG NHibernate.Engine.TwoPhaseLoad - resolving associations for [ORMTests.TestClass#20000]
2009-02-11 21:41:32,602 [10] DEBUG NHibernate.Engine.TwoPhaseLoad - done materializing entity [ORMTests.TestClass#20000]

Second phase takes 21 seconds !

With a little research it seems that in the second phase NHibernate performs the mapping of the raw values to the Business Objects. To verify that we run Test 3 again but this time we do not map the results to BOs: 

 

Now the time has decreased to : 1 sec !

There must be a delay when mapping the data to the BOs. So I have manually added the code to map the data to the TestClass Business Objects just after getting the List<object> ListReturned as follows: 

 

The retrieval time returned to 15,227sec which is an improvement from 23sec but still not acceptable.

So the problem is not with NHibernate but rather it is in the way we have implemented the BO. And the first suspicious place where this may be happening is when calling the FirePropertyChangedNotification(…). So we first remove from the function the automatic retrieval of the Property’s name which may cause the delay:

(StateLess Session)

  Test 1 Test 2 Test 3 Test 4
1st Time 8,855sec 8,827sec 4,651sec N/A
2nd Time 8,638sec 8,815sec 4,170sec N/A
Times include time to display in WPF DataGrid

We have 50% increase in performance. How about removing the method call completely and putting its behavior directly in the properties?

(StateLess Session)

  Test 1 Test 2 Test 3 Test 4
1st Time 0,813sec 0,510sec 0,331sec N/A
2nd Time 0,490sec 0,553sec 0,320sec N/A
Times include time to display in WPF DataGrid

Which is now acceptable. To make sure we try to load the same data using a DataSet which gives approximately 0,301sec.

The last thing to do is perform the comparison between Test2 and DataSets for increasing table size:

   

Apparently there is a difference in DataSets but now this difference is justified since NHibernate is an ORM tool which is more than a simple mapping tool which creates the DataSets.

As a conclusion, Jack was again wrong. Wrong, because it was his implementation that lead to this big stall in performance and not NHibernate itself. Moreover, when he was searching the internet for a solution it was normal not to be able to find one since the issue was caused by his way of implementing things.

My effort is not to advertise NHibernate. I am sure that the same would emerge with EDM or any other ORM tool. The point of this 2 post discussion is three-fold:

  • First to show a way of thinking which in my opinion can lead to overstatements.
  • Second to provide a general overview of DDD, ORM, DataSets and Business Objects.
  • Third to show some performance analysis results when using NHibernate and a way to resolve issues about those.

 

kick it on DotNetKicks.com

Tags:

.NET | Databases

Domain Driven Design, Business Objects, Datasets, NHibernate and friend Jack (Part 1/2)

by Ioannis 12. February 2009 19:40

 

A few days ago, I was having some drinks with a friend (let’s call him Jack) in a bar discussing about enterprise application development and the migration from DataSets with VS to Business Objects with NHibernate. I will try to reproduce the conversation here:

Jack:  Oh man NHibernate really sucks! It is very slow compared to DataSets. The things about Business Objects you were saying are all theoretical mambo-jumbo. Datasets rock.

Me: Why do you say that? What did you do?

Jack: Well, I found some time in the weekend and have followed the DDD paradigm instead of using DataSets. I have created a BO (a simple one, mapped to a single table in the DB) and created a very simple DataLayer using NHibernate to retrieve a list of 1500 BOs and display them in a DataGrid. It took NHibernate 6 sec to retrieve the data! Can you believe it? (…) I have tried the same thing with a DataSet and it took only some msec (…)  I tried to find a solution to this in the internet for 3 hours and found nothing. NHibernate is poorly documented too… Nothing beats DataSets.

There are a lot of things to discuss in Jack’s statement.

First of all, it took just a 1 hour of playing with NHibernate for Jack to claim that the NHibernate technology sucks due to inexplicable performance issues in such a small example. It took him another 3 hours to claim that the documentation for NHibernate is insufficient. And this whole experience was enough for him to judge... But since this issue has nothing to do with programming, we will leave it to the philosophy blog.

Another issue worth discussing here is the fact that Jack is comparing NHibernate with DataSets and relates the first with Business Objects and then says that he followed the DDD programming paradigm. A lot of terms whose connection is not clear in his mind. How do they relate? This is about philosophy again. But this time it is about philosophy of designing programs and I will discuss it here…

Enterprise applications share a general set of requirements. There are some data that need to be deleted, inserted, updated, selected and filtered in various ways. Those data are stored (persisted) in a database. There are several rules (depending on the application) that define how those data are entered, processed, validated and sometimes automatically generated. Finally there is the graphical user interface that provides the means of playing with those data.

The keyword here is “The  Data”. Every “logic” in the application (Business, GUI, Data access) needs “The Data”. “The Data” are retrieved from the database, processed through classes in the business logic and presented through events in the GUI. So how are “The Data” organized? And how are “The Data” persisted? It turns out that this has a lot to do with how we design the application in our minds in the first place. Let us call “The Data” as a set of carefully designed classes also known as “Business Objects”.

The common ground for everyone and for a long time was the database. The relational database with the rules it implied (Tables, Views, Primary and Foreign Keys) provided a starting point on organizing “The Data” and as a consequence our BOs.

To make this point more clear imagine the following simple scenario where we need to implement an application involving Clients, Products and Purchases where a client buys a number of products which are written on an Invoice with a unique Invoice Number. Note that a lot of us, even before hearing anything about the application’s requirements, we have created a conceptual Database diagram in our mind that looks like the one below: 

So “The Data” are conceptually organized in three tables and associations between them. All my application will be using select like statements operating on relations on table rows. Therefore my Business Objects will be mainly sets of DataTable, DataRow and Relation classes mimicking the database. In other words they will be DataSets. Yes DataSets are Business Objects since they are a way to organize “The Data” of the business.

But is this formulation of data convenient for my application (or Domain)? Note that up to now we have not said anything about the application’s requirements.

What if the main requirement for the application (ie the Domain) is to show the current Invoices or operate on Invoices?

With the DataSet approach we would probably fill a Purchases DataTable with the DataRows that have a specific InvoiceNo and operate on it.

Wouldn’t it be more convenient if we had an “Invoice” as “The Data”? Do we need to change the Database?  The answer is no. The key point here is to decouple the application’s organization of “The Data” from the limitations the relational database imposes. We want to use Invoices in our business logic therefore we will create an “Invoice” Business Object:


 

There is no doubt that organizing our data like that has a lot of benefits. Actually, we have roughly followed the Domain Driven Design paradigm where we have implemented our Business Objects according to entities of the actual domain and the application’s requirements. There was nothing evil with our Datasets and the tabular like business objects. They just weren’t suited for the requirements of the particular application (the domain). In another application or as part of a domain datasets may be the most suitable choice (try accounting journal entries and you will see what I mean).

No matter what our BOs are (Datasets or other) there is always the need of mapping them to the Relational Database. Note here that the mapping is not a simple one to one connection of table fields to BO properties. Most of the time it requires more than that. For example creating a new Invoice BO in the previous example and adding a Client and a Product into it is persisted by adding a new record in the Purchases table with the appropriate Client and Product keys.

Here comes the role of ORM tools such as NHibernate. Of course along with the previous functionality those tools offer a lot more such as session management, concurrency control etc. So ORM tool provide methods for implementing the Data Access layer ASAP. So why do people pose the question Datasets vs Business Objects? Or why did Jack said that he prefers DataSets over NHibernate? That is because Visual Studio provides an automated way of creating the DataSets for you if you have the database ready. It is as if Visual Studio is creating your ORM tool that will handle your DataSets to DB communication. And it happens in a few seconds.

ORM tools such as NHibernate favor starting building your application from your Business Objects that fit your domain. Then they let you define how your BOs will be persisted (define the DB Schema) and request from you to provide a mapping between the two. They can then give you the goodies for manipulating the data. So for someone who already has the Database ready, this seems like a big overkill. But it is not always such a big deal since ORM tools usually provide third party tools that can construct some BOs automatically from tables of the database.

So here is the basis of the actual debate:

The verdict?

  • If the application is small scale and you don’t have the time, use DataSets and go have fun (unless fun is discussing about programming).
  • If the application is not small scale then see if the DataSet tabular logic fits in your Domain and if it does use it.
  • For the rest be custom and use ORM tools (by the way EDM is Microsoft’s suggestion so if you are into Microsoft go for it).

So we have actually questioned Jack’s personality and have shown that he has used terms without really knowing what they meant. In the next post we will prove that Jack was also wrong about the performance issues.

“Sorry Jack”

kick it on DotNetKicks.com

Tags:

.NET | Databases

Masked TextBox in WPF (and Keyboard in WPF)

by Ioannis 4. February 2009 18:26

Last week, I was wondering whether it is easy to implement a Masked TextBox in WPF since WPF does not have any by default. It turns out that this quest is an interesting one and consists of discovering the MaskedTextProvider .NET class and finding out how to control the Keyboard and especially the Insert Key in WPF. So we will start with the following small example:

We have a simple TextBox Control that we want to mask and a simple Button Control that serves as an alternative place for the focus, in order to test the behavior of the TextBox when it looses it. We want to implement a Masked TextBox, therefore we create a new class named MyMaskedTextBox which inherits from TextBox:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Input;

namespace MaskedTextBox
{

    class MyMaskedTextBox:TextBox
    {

    }
}

All the mentioned assemblies will be needed so leave them there for now.

The System.ComponentModel.MaskedTextProvider class basically works as follows: We provide a mask (the same mask we used to apply in the WinForms MaskedTextBox) at the constructor of the object. We then submit characters or strings at specific positions of the mask and ask the provider whether they are ok to be there. We can also use the internal MaskTextProvider's string that stores the submitted charaters, in order to see whether the mask has been completed.

So, in our exaple, we create a public property for the developper to supply the mask and instantiate a MaskedTextProvider in its setter (that is whenever the user sets the mask, a new provider is instantiated):

        private MaskedTextProvider _mprovider=null;
        public string Mask
        {
            get
            {
                if (_mprovider != nullreturn _mprovider.Mask;
                else return "";
            }
            set
            {
                _mprovider = new MaskedTextProvider(value);
                this.Text = _mprovider.ToDisplayString();
            }
         }

We will simplify our lives a lot if we apply some constraints:

  • The Delete and BackSpace keys should be disabled when within the TextBox.
  • The Insert Key should be always off (always replace the previous value).
  • Space is speically handled  by the MaskedTextProvider and therefore we shoud give the user the ability to choose whether he/she wants this functionality.
  • We do not want to support Text selection within the TextBox.

To achieve the previous requirements, we add another property that holds the user's preference wrt the Space key and override the OnPreviewKeyDown event as follows:

        private bool _ignoreSpace = true;
        public bool IgnoreSpace
        {
            get { return _ignoreSpace; }
            set { _ignoreSpace = value; }
        }

        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            if (this.SelectionLength > 1)
            {
                this.SelectionLength = 0;
                e.Handled = true;
            }
            if (e.Key==Key.Insert || 
                e.Key == Key.Delete || 
                e.Key==Key.Back || 
               (e.Key==Key.Space && _ignoreSpace))
            {
                e.Handled = true;
            }
            base.OnPreviewKeyDown(e);
         }

This basically says, that when a key is down and something is selected then just unselect it and ignore the key. Also, if the key is one of those mentioned in the list then ignore them also. But how do we make sure that Insert will be always off? (Replace Mode). Well, at first, whenever a TextBox Control gets the focus for the first time the Insert is always ON. So the first time the TextBox gets the focus, we need to set the Insert to OFF. Actually we want to simulate the Key-Press of the Insert Key. But we don't have the Windows Forms' SendKeys method anymore. Well, the equivalent method that does exactly that -simulates the pressing of a key- in WPF is as follows:

         private void PressKey(Key key)
        {
            KeyEventArgs eInsertBack = new KeyEventArgs(Keyboard.PrimaryDevice, Keyboard.PrimaryDevice.ActiveSource, 0, key);
            eInsertBack.RoutedEvent = KeyDownEvent;
            InputManager.Current.ProcessInput(eInsertBack);
         }

We use a Boolean property which is false upon initialization of the TextBox and becomes true after the first time the TextBox has got the focus. So we check this property and if it is false we "press" the insert key. In consecutive times that the TextBox will get the focus the Insert won't be pressed again:

        private bool _InsertIsON = false;
        protected override void OnGotFocus(RoutedEventArgs e)
        {
            base.OnGotFocus(e);
            if (!_InsertIsON)
            {
                PressKey(Key.Insert);
                _InsertIsON = true;
            }
         }

Having done that, we can now work with the mask. We want to achieve two things: First we want to Preview the user's input and set a property to true or false depending on whether the input will be accepted or not (this property may come handy to the developer who uses our control). Second we want to actually allow/prevent the insertion of the new character in the TextBox based on the mask. 

So, before the insertion of any text, we inform the developer (if he/she likes) that the inserted text will be valid/invalid. Therefore we add the following:

        private bool _NewTextIsOk = false;
        public bool NewTextIsOk
        {
            get { return _NewTextIsOk; }
            set { _NewTextIsOk = value; }
        }
        protected override void OnPreviewTextInput(TextCompositionEventArgs e)
        {
             System.ComponentModel.MaskedTextResultHint hint;
             int TestPosition;

             if (e.Text.Length == 1)
                 this._NewTextIsOk = _mprovider.VerifyChar(e.Text[0], this.CaretIndex, out hint);
             else
                 this._NewTextIsOk = _mprovider.VerifyString(e.Text, out TestPosition, out hint);
             
            base.OnPreviewTextInput(e);
         }

This says that according to the length of the submitted text (1 or greater than 1) call the provider to check if it is ok for this text to be inserted at the current position and set the property accordingly. The current position is given by the property this.CaretIndex.

When the text is about to be inserted:

        protected override void OnTextInput(System.Windows.Input.TextCompositionEventArgs e)
        {
            string PreviousText = this.Text;
            if (NewTextIsOk)
            {
                base.OnTextInput(e);
                if (_mprovider.VerifyString(this.Text) == falsethis.Text = PreviousText;
                while (!_mprovider.IsEditPosition(this.CaretIndex) && _mprovider.Length>this.CaretIndex) this.CaretIndex++;

            }
            else
                e.Handled = true;
         }

We have another verification which seems redundant. This verification occurs AFTER the text is inserted and may come handy if in another implementation we leave the Insert Key to ON and we want to make sure that the new shifted text in the TextBox is still valid. We also move the caret until we reach the end of the mask or the caret position is in an editable character.

Finally we may want to prevent the control from losing the focus until the mask is full with valid data. So:

        public bool StayInFocusUntilValid
        {
            get { return _stayInFocusUntilValid; }
            set { _stayInFocusUntilValid = value; }
        }
        protected override void OnPreviewLostKeyboardFocus(KeyboardFocusChangedEventArgs e)
        {
            if (StayInFocusUntilValid)
            {
                _mprovider.Clear();
                _mprovider.Add(this.Text);
                if (!_mprovider.MaskFull) e.Handled = true;
            }
            
            base.OnPreviewLostKeyboardFocus(e);
         }

Having done all those we can now use the new control as follows:

<Window x:Class="MaskedTextBox.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:local="clr-namespace:MaskedTextBox"

Title="Window1" Height="126" Width="256">

<StackPanel Orientation="Horizontal" Width="228">

<local:MyMaskedTextBox x:Name="MaskedDemo" Mask="(LLL)00/00/0000" StayInFocusUntilValid="True" IgnoreSpace="True" Width="118" Height="26" Margin="20"/>

<Button Content="OK" Height="24" Width="50"/>

</StackPanel>

</Window>


And the result is:

You can download the demo project here: MaskedTextBox.zip (53,22 kb)

kick it on DotNetKicks.com

Tags:

WPF

Powered by BlogEngine.NET 1.5.0.7

Programming Blogs - BlogCatalog Blog Directory Add to Technorati Favorites

MVP Award

Ioannis Panagopoulos





This blog is using BlogEngine.Net and is hosted in the hoster below. I have not experienced any problems installing BlogEngine.Net in the host and I am satisfied with the host's response times. Therefore I recommend it.


DiscountASP Add