2013 - Development Simply Put

A blog simplifies main concepts in IT development and provides tips, hints, advices and some re-usable code. "If you can't explain it simply, you don't understand it well enough" -Albert Einstein

  • Development Simply Put

    If you can't explain it simply, you don't understand it well enough.

    Read More
  • Integrant

    Based in the U.S. with offshore development centers in Jordan and Egypt, Integrant builds quality custom software since 1992. Our staff becomes an extension of your team to eliminate the risks of software development outsourcing and our processes...

    Read More
  • ITWorx

    ITWorx is a global software professional services organization. Headquartered in Egypt, the company offers Portals, Business Intelligence, Enterprise Application Integration and Application Development Outsourcing services to Global 2000 companies.

    Read More
  • Information Technology Institute

    ITI is a leading national institute established in 1993 by the Information and Decision Support Centre.

    Read More

2013-12-27

ASP.NET Viewstate And Controlstate Performance Enhancements - Saving Viewstate And Controlstate On Server

ASP.NET Viewstate & Controlstate

Viewstate and Controlstate are used in ASP.NET pages to keep states of pages and all controls on them between postbacks. ASP.NET framework saves these states in a container to be used throughout postbacks to maintain these states.

Also, you can explicitly add any info you need to keep between postbacks inside the page viewstate. This may seem urging but you must know that this doesn't come free of charge. Yes viewstate and controlstate are very useful and powerful but they have to be used wisely otherwise you will greatly affect your system performance badly and in an unpleasant way.

The viewstate and controlstate are both saved by the server and then retrieved to keep your page and controls state. The default behavior is that these states are saved into a hidden field on the page so that at postbacks the server will be able to read these states back from the hidden field and retrieve the sates prior the postbacks. Is this good?

This article is not meant to be a full guide about viewstate and controlstate, if you need a full guide you can have a look on the references at the end of this article. So, what is this article about?

This article will focus on how to overcome the drawbacks of heavy viewstate and controlstate and enhance the system performance by saving these states on server rather than sending them back and forth between client and server throughout postbacks.

Analysis
To know how viewstate and controlstate work, you need to know some points in brief:
  1. HTTP is stateless which means that it doesn't support by itself saving the states of requests and responses. That is why each web development platform should handle the states by its own way and system when needed
  2. For ASP.NET, when a request is initiated, the server process the request and builds the whole page and sends it back to the client. At this moment, the server forgets about the whole page object and all info related to the request. This is what is meant by stateless
  3. ASP.NET has its own way of saving the page states. It exposes some methods/events by which you can control how the page state will be saved and then retrieved, but if you didn't override these methods and provide your own implementation, there is always a default behavior which ASP.NET will use to save the states
  4. The default behavior for ASP.NET to save page states is saving/loading them into/from a hidden field on the page
  5. The states the server tends to save are the states of all the page controls before being sent back to the client. At the successive requests, the server can now retrieve these sates to know how the page looked like before the system user applied some changes on it at the client side 
But why the hassle?
I asked myself before why at every request the server needs to know the states on which the page was before the last response, does it really matter? As far as I know when a request is performed the form will be submitted to the server and the server will have all the info required to re-create and re-populate the form fields in the response, so for God's sake why????

Misunderstood about viewstate
Some developers think that viewstate is used to keep values and states of page controls so that the server is able to populate these values and states after postbacks. This is wrong. Believe me even if you disabled the viewstate on a page and its controls the values you entered in the controls will still exist after postbacks. You don't believe me, try it yourself.

Create a web application, add a page and disable viewstate on it, add a server textbox control and make it run at server, add a server button control and make it run at server. Now start the application and enter some text inside the textbox and click on the button. A postback will be performed and the textbox will be populated with the text you enterd yourself before the postback. How? this happened because when you clicked the button the whole form is submitted including the text you entered inside the textbox. So, the server didn't need anything to know the value you entered as it was already sent to it with the request. That's why I told you before viewstate is not responsible for keeping controls values and states.

Believe it or not, one of the main purposes of the viewstate is to track changes made on a page controls. Why keep track of changes? to be able to properly fire events like "ontextchanged" which are based on tracking changes made on a control to properly apply your custom code for handling such situations. Still not convinced? If yes, try the following example.

The proof
Try this:
  1. Create a web application
  2. Add a page and enable viewstate on it
  3. Add the following markup inside the form tag
    <asp:TextBox ID="box" runat="server" Text="" EnableViewState="true" ontextchanged="box_TextChanged"></asp:TextBox>
    <asp:Button ID="btn" runat="server" Text="Do Postback" onclick="btn_Click" />
    
  4. Write this code on the code behind inside the page class
    protected void btn_Click(object sender, EventArgs e)
    {
    }
    
    protected void box_TextChanged(object sender, EventArgs e)
    {
    }
    
  5. Put a breakpoint on the "box_TextChanged" event
  6. Run the application in debug mode
  7. Write "Test Test" in the textbox
  8. Click "Do Postback" button
  9. You will reach the breakpoint, hit F5 to return to client-side
  10. Delete "Test Test" from the textbox and leave it empty
  11. Click "Do Postback" button
  12. You will reach the breakpoint, hit F5 to return to client-side
  13. Stop debugging and disable the viewstate on the page
  14. Disable the viewstate on the textbox so that the markup will be as follows
    <asp:TextBox ID="box" runat="server" Text="" EnableViewState="false" ontextchanged="box_TextChanged"></asp:TextBox>
    <asp:Button ID="btn" runat="server" Text="Do Postback" onclick="btn_Click" />
    
  15. Put a breakpoint on the "box_TextChanged" event
  16. Run the application in debug mode
  17. Write "Test Test" in the textbox
  18. Click "Do Postback" button
  19. You will reach the breakpoint, hit F5 to return to client-side
  20. Notice that the textbox text is "Test Test", even without viewstate!!!
  21. Delete "Test Test" from the textbox and leave it empty
  22. Click "Do Postback" button
  23. You will not reach the breakpoint, even when the text is changed from "Test Test" to ""!!!
Confused? You have the right to. Here is what happened:
  1. When you created the textbox using the markup, the default value of the textbox is empty or ""
  2. At the first load of the page, the server loaded the textbox with its default value which was set into the markup which was "" in our case
  3. Since this was the first page load, the server already knew that whatever viewstate was enabled or disabled it would not matter as the page was in its default state
  4. At client-side, when you entered "Test Test" inside the textbox then followed by postback the server created the whole page and its controls from scratch
  5. So, the first step was to create the textbox and pre-populate it with its default value which is "" in our case
  6. At this point the viewstate may have played a role, so:
    1. When viewstate was enabled:
      1. The server checked if any viewstate was saved from before
      2. In this case, no viewstate was saved because the previous load was the first page load as we stated above in step #3
      3. So, the textbox text was not changed and it stayed ""
    2. When viewstate was disabled:
      1. The textbox text was not changed and it stayed ""
  7. Server loaded the new textbox text from the submitted form, so in our case it was found to be "Test Test"
  8. Server set the textbox text to the value retrieved in the previous step which is "Test Test"
  9. Server compared the textbox value from step #6 and #8 and figured out that the value has changed from "" to "Test Test", so the server fired the "box_TextChanged" event
  10. Before rendering the page, the server had something to do:
    1. When viewstate was enabled:
      1. The server saved the viewsate of the page controls, so the textbox state was saved and the saved value of the textbox was "Test Test"
    2. When viewstate was disabled:
      1. No state was saved
  11. Back again at client side, when you cleared the textbox text and performed a postback, the server created the whole page and its controls from scratch
  12. So, the first step was to create the textbox and pre-populate it with its default value (from the markup) which is "" in our case
  13. At this point the viewstate may have played a role, so:
    1. When viewstate was enabled:
      1. The server checked if any viewstate was saved from before
      2. In this case, viewstate was found and the saved texbox value was "Test Test"
      3. So, the server re-populated the textbox with its previous value which was saved in the viewstate, in our case, "Test Test""
    2. When viewstate was disabled:
      1. The textbox text was not changed and it stayed ""
  14. Server loaded the new textbox text from the submitted form, so in our case it was found to be ""
  15. Server set the textbox text to the value retrieved in the previous step which is ""
  16. Server compared the textbox value from step #13 and #15 to check if any changes had been applied on the textbox, so:
    1. When viewstate was enabled:
      1. A change had been applied from "Test Test" to ""
      2. The server fired the "box_TextChanged" event
    2. When viewstate was disabled:
      1. No change had been applied as both values are ""
      2. The server didn't fire the "box_TextChanged" event
That's it, I think you now got it right, right?

The last thing to mention here is

ASP.NET Page Lifecycle

Why to save viewstate and controlstate on server?
Now after we have understood what viewstate and controlstate are about, let's discuss something. We said before that ASP.NET has a default way or approach to save and load viewstate and controlstate if not other approach is set by the system developer. This default approach is saving and loading states into and from a hidden field on the page. Is this good? may be it is good for some cases but if your page controls are complex or many or you are stuffing too many objects into the viewstate, this will make the viewstate and controlstate large in size and in this case the hidden field will take too much size on the page. This will eventually cause the response size to be large. I think this is enough for a reason on why to try to find another approach for saving and loading states.

How to save viewstate and controlstate on server?
To control the way ASP.NET will save and load your page states, you need to override two events on the page class but before going deep into code let's highlight some points first.

The whole idea here is to save the states on a text file on the server. This way the server will not have to send the states back and forth between server and client which makes the request and response sizes smaller and the whole application performance better.

So, for this approach to work as it should, a state file should be created for every user so that users will not share states. This could be handled using session ids as the file names or something like that. This will work because we know that session ids are unique for all users and it is impossible for two users to have the same session id.

Problem
This is good but there is a problem with this approach. We said that session ids are unique for all users and that every user will have his own unique session id, but, for the same user, if he opens more than one page of the application on more than one tab, all these pages and tabs will share the same session id. So, now we have to differentiate between the states of pages even for the same user because we don't want to load the states of page A to page B.

Solution
We have to define an id for each page a user opens, so that the combination of this id with the user session id form a unique page id. This combined id will be used as the state file id. To do this, we will generate a unique page id at the page first load and save this id on a hidden field on the page.

Building the whole solution
As we said before, there are two events to override on the page class:
  1. The "SavePageStateToPersistenceMedium" event which is fired when the server saves the page states before the page is rendered
  2. The "LoadPageStateFromPersistenceMedium" event which is fired when the server loads the saved states from the previous response
So, we will create our own class derived from the "Page" class and customize it to look as in the code below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI;
using DevelopmentSimplyPut;
using System.IO;
using System.Web.UI.WebControls;

namespace DevelopmentSimplyPut.CustomStatePreservePages
{
    public class InFileStatePreservePage : Page
    {
        private string pageId;
        public string PageId
        {
            get
            {
                string result = "";

                if (!string.IsNullOrEmpty(pageId))
                {
                    result = pageId;
                }
                else
                {
                    result = Request.Form["hdnPageId"];
                }

                return result;
            }
        }

        public string StatePreserveFilesFolderPath
        {
            get
            {
                return Path.Combine(Request.PhysicalApplicationPath, Constants.StatePreserveFilesFolderName);
            }
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if(!IsPostBack)
            {
                pageId = Session.SessionID.ToString() + Guid.NewGuid().ToString();
                Page.ClientScript.RegisterHiddenField("hdnPageId", pageId);
            }
        }

        protected override object LoadPageStateFromPersistenceMedium()
        {
            if (Page.Session != null)
            {
                if (Page.IsPostBack)
                {
                    string filePath = Session[PageId].ToString();

                    if (!string.IsNullOrEmpty(filePath))
                    {
                        if (!File.Exists(filePath))
                        {
                            return null;
                        }
                        else
                        {
                            StreamReader sr = File.OpenText(filePath);
                            string viewStateString = sr.ReadToEnd();
                            sr.Close();

                            try
                            {
                                File.Delete(filePath);
                            }
                            catch
                            {

                            }

                            LosFormatter los = new LosFormatter();
                            return los.Deserialize(viewStateString);
                        }
                    }
                    else
                    {
                        return null;
                    }
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        protected override void SavePageStateToPersistenceMedium(object state)
        {
            if (state != null)
            {
                if (Page.Session != null)
                {
                    if (!Directory.Exists(StatePreserveFilesFolderPath))
                    {
                        Directory.CreateDirectory(StatePreserveFilesFolderPath);
                    }

                    string fileName = Session.SessionID.ToString() + "-" + DateTime.Now.Ticks.ToString() + ".vs";
                    string filePath = Path.Combine(StatePreserveFilesFolderPath, fileName);

                    Session[PageId] = filePath;

                    LosFormatter los = new LosFormatter();
                    StringWriter sw = new StringWriter();
                    los.Serialize(sw, state);

                    StreamWriter w = File.CreateText(filePath);
                    w.Write(sw.ToString());
                    w.Close();
                    sw.Close();
                }
            }
        }
    }
}

And now any page you create in the system should inherit from the "InFileStatePreservePage" class and in the "Page_Load" event call the base first as in the code below.
public partial class MyPage : InFileStatePreservePage
{
    protected void Page_Load(object sender, EventArgs e)
    {
        base.Page_Load(sender, e);
    }
}

Deleting the abandoned state files
To delete the remaining state files, you need to make sure that the files you are going to delete are the outdated files only. To do that you should delete only the files that are not modified for a period greater than the session timeout period. So, to do that you can add the code below to your Global.asax file.
void Application_Start(object sender, EventArgs e) 
{
 string stateFilesDirectory = System.IO.Path.Combine(Server.MapPath("~"), DevelopmentSimplyPut.Constants.StatePreserveFilesFolderName);
 Application["stateFilesDirectory"] = stateFilesDirectory;
 
 if (!string.IsNullOrEmpty(stateFilesDirectory) && System.IO.Directory.Exists(stateFilesDirectory))
 {
  string[] files = System.IO.Directory.GetFiles(stateFilesDirectory);
  foreach (string file in files)
  {
   System.IO.FileInfo fi = new System.IO.FileInfo(file);
   fi.Delete();
  }
 }
}

void Application_End(object sender, EventArgs e) 
{
 string stateFilesDirectory = Application["stateFilesDirectory"].ToString();

 if (!string.IsNullOrEmpty(stateFilesDirectory) && System.IO.Directory.Exists(stateFilesDirectory))
 {
  string[] files = System.IO.Directory.GetFiles(stateFilesDirectory);
  foreach (string file in files)
  {
   System.IO.FileInfo fi = new System.IO.FileInfo(file);
   fi.Delete();
  }
 }
}

void Session_Start(object sender, EventArgs e) 
{
 string stateFilesDirectory = Application["stateFilesDirectory"].ToString();

 if (!string.IsNullOrEmpty(stateFilesDirectory) && System.IO.Directory.Exists(stateFilesDirectory))
 {
  string[] files = System.IO.Directory.GetFiles(stateFilesDirectory);
  int timeoutInMinutes = Session.Timeout;
  int bufferMinutes = 5;
  foreach (string file in files)
  {
   System.IO.FileInfo fi = new System.IO.FileInfo(file);
   if (fi.LastAccessTime < DateTime.Now.AddMinutes((-1 * (timeoutInMinutes + bufferMinutes))))
   {
    fi.Delete();
   }
  }
 }
}

void Session_End(object sender, EventArgs e)
{
 string stateFilesDirectory = Application["stateFilesDirectory"].ToString();
 
 if (!string.IsNullOrEmpty(stateFilesDirectory) && System.IO.Directory.Exists(stateFilesDirectory))
 {
  string[] files = System.IO.Directory.GetFiles(stateFilesDirectory);
  int timeoutInMinutes = Session.Timeout;
  int bufferMinutes = 5;
  foreach (string file in files)
  {
   System.IO.FileInfo fi = new System.IO.FileInfo(file);
   if (fi.LastAccessTime < DateTime.Now.AddMinutes((-1 * (timeoutInMinutes + bufferMinutes))))
   {
    fi.Delete();
   }
  }
 }
}

Important prerequisite
For this solution to work well you need to set your application session timeout mode to Inproc. Otherwise, the session "Session_Start" and "Session_End" events will not fire and in this case the only time you will be clearing the abandoned state files will be at "Application_Start" and "Application_End" events which is a way too late and may cause the server to have low disk space. So, to do so you have to set your application web.config file as below.
<configuration>
    <system.web>
     <sessionState cookieless="UseCookies" mode="InProc" timeout="60"/>
    </system.web>
</configuration>


That's it, hope you can find this helpful someday. For further reading you can check the resources below.
Good luck.


References
  1. TRULY Understanding ViewState - Infinities Loop 
  2. ViewState in SQL
  3. ViewState Compression - CodeProject
  4. Understanding ASP.NET View State
  5. Flesk.NET Components - Viewstate Optimizer
  6. ViewState: Various ways to reduce performance overhead - CodeProject
  7. Keep ASP.NET ViewState out of ASPX Page for Performance Improvement - CodeProject
  8. Control State vs. View State Example

2013-12-22

How To Avoid Problems Caused By Clients' Browser Cached Resource Files (JS, CSS, ....) With Every New Build


Javascript & Css

Browsers like IE, Firefox, Chrome and others have their own way to decide if a file should be cached or not. If a file link (URL) is requested more than a certain number of times the browser decides to cache this file to avoid repeated requests and their relevant responses. So, after a file is cached by the browser and a new request is performed for this file, the browser responses with the cached version of the file instead of retrieving the file for the server.

But how does the browser know if we are requesting the same file? The browser knows that by comparing the URL of the requested file to the URL of the file it had already cached before. This means that any slight change on the requested file URL will be recognized by the browser as a completely new file.

So, what is the problem?
The problem is that sometimes between builds there are some changes applied on the application javascript and style files. Although the new code is sent to the client, they get javascript errors and some of the styles are messy. Why? this is due to the client's browser caching of the javascript and styles files. Although we replaced the old files with the new ones but the client's browser is still using the old cached ones because the URLs are still the same.

What is the solution?
There are many approaches to take to fix this issue but they are not all proper ones. Let's check some of these solutions.

Some of the solutions are:
  1. Ask the client to ask all of his system users to clear the browser cache
  2. Ask the client to ask all of his system users to disable browser caching
  3. For each build rename JS and CSS file names
  4. For each build add a dummy query string to all resources URLs
Now, let's see. I think we will all agree that the first two options are not practical at all. For the third option, it will work for sure but this is not acceptable as changing the files names will require changing all references to these files in all application pages and code which is dangerous and not acceptable by any means in terms of code maintainability.

This leaves us with the fourth option which seems like the third one but believe me they are not completely the same. For sure I don't mean to do it in a manual form like browsing through the whole code and changing the extra dummy query string for all resources URLs, there is a more generic and respectable way to do it without even caring about re-visiting the URLs for each new build.

The solution is to implement a server control to be used to register the resources instead of using the regular script and link tags. This control will be responsible for generating the URLs with the dummy query strings and making sure these query strings are not changed unless a new build is deployed.

Now, let's see some code.

Server Control:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Globalization;
using System.Web.UI.WebControls;

namespace DevelopmentSimplyPut.CustomWebControls
{
    public class VersionedResourceRegisterer : WebControl, INamingContainer
    {
        JsTags js;
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public JsTags JS
        {
            get
            {
                return js;
            }
        }

        CssTags css;
        [PersistenceMode(PersistenceMode.InnerProperty)]
        public CssTags CSS
        {
            get
            {
                return css;
            }
        }

        public VersionedResourceRegisterer()
        {
            js = new JsTags();
            css = new CssTags();
        }

        protected override void Render(System.Web.UI.HtmlTextWriter output)
        {
            string fullTag = "";
            string version = AppConstants.Version;

            if (null != JS && JS.Count > 0)
            {
                foreach (Tag js in JS)
                {
                    string path = js.path;
                    path = GetAbsolutePath(path);

                    if (!string.IsNullOrEmpty(path))
                    {
                        fullTag += string.Format(CultureInfo.InvariantCulture, "<script src=\"{0}?v={1}\" type=\"text/javascript\"></script>", path, version); 
                    }
                }
            }

            if (null != CSS && CSS.Count > 0)
            {
                foreach (Tag css in CSS)
                {
                    string path = css.path;
                    path = GetAbsolutePath(path);

                    if (!string.IsNullOrEmpty(path))
                    {
                        fullTag += string.Format(CultureInfo.InvariantCulture, "<link href=\"{0}?v={1}\" type=\"text/css\" rel=\"stylesheet\" />", path, version);
                    }
                }
            }

            output.Write(fullTag);
        }

        private string GetAbsolutePath(string path)
        {
            string result = path;

            if(!string.IsNullOrEmpty(path))
            {
                if (!path.Contains("://"))
                {
                    if (path.StartsWith("~"))
                    {
                        HttpRequest req = HttpContext.Current.Request;
                        string applicationPath = req.Url.Scheme + "://" + req.Url.Authority + req.ApplicationPath;

                        if(!applicationPath.EndsWith("/"))
                        {
                            applicationPath += "/";
                        }

                        path = path.Replace("~", "").Replace("//", "/");

                        if (path.StartsWith("/"))
                        {
                            if (path.Length > 1)
                            {
                                path = path.Substring(1, path.Length - 1);
                            }
                            else
                            {
                                path = "";
                            }
                        }

                        result = applicationPath + path;
                    }
                }
            }

            return result;
        }
    }

    public class Tag
    {
        public string path { set; get; }
    }

    public class JsTags : List<Tag>
    {
    }

    public class CssTags : List<Tag>
    {
    }
}

Version Generation:
public static class AppConstants
{
    private static string version;
    public static string Version
    {
        get
        {
            return version;
        }
    }

    static AppConstants()
    {
        version = (Guid.NewGuid()).ToString().HtmlEncode();
    }
}
As you see the AppConstants class is a static class and inside its static constructor the version is generated once. This means that with each IIS reset a new version will be generated and accordingly with each build we get a new version.

Using Control On Pages:
<ucVersionedResourceRegisterer:VersionedResourceRegisterer runat="server">
 <JS>
  <ucVersionedResourceRegisterer:Tag path="Scripts/jquery-1.10.2.min.js" />
  <ucVersionedResourceRegisterer:Tag path="Scripts/jquery-migrate-1.2.1.min.js" />
  <ucVersionedResourceRegisterer:Tag path="Scripts/jquery.alerts.min.js" />
 </JS>
 <CSS>
  <ucVersionedResourceRegisterer:Tag path="Styles/jquery.alerts.css" />
 </CSS>
</ucVersionedResourceRegisterer:VersionedResourceRegisterer>

Finally, this is not the only advantage of using the server control as you can always use it to gain more control on your resource files. One of the tasks in which I made use of this control is applying automatic minification and bundling of my resource files to enhance my application performance.

That's it. Hope you find this post helpful someday.
Good luck.


2013-12-21

Application To Generate Combined Images Of All Image-Categories Possible Combinations

[Update] The application is now updated to avoid "out of memory" exceptions.


On my last project I was working on a tree control which represents system business objects into hierarchical form. Each object has a workflow to go through and for each step in this workflow the object state changes.

So, one of the requirements was to add images beside each tree node for the system user to know the status of each tree node easily with the need to open a properties window or something like that.

This is easy but we have some points to clarify first:
  • Each system objects has three categories of status
    1. Cat01: is so business related so I will not go into details about it but let's just say that this type of object status should have one of 25 possibilities
    2. Cat02: is related to the type of change applied on the system object (added, edited, deleted, unchanged)
    3. Cat03: is also so business related but it is mainly about object change approval status. This object status should have one of 7 possibilities
  • For each image tag added on the page there will be a request to the server and for sure its related response

So, after some thinking we came up with 2 approaches to choose from:
  1. Add three image tags beside each tree node and fill these tags with appropriate images according to the status of each object
  2. Add one image tag beside each tree node and fill this tag with only one image which is all the object 3 status images combined into one image. The specific image name will be built by concatenating the keywords for each category status. This way we don't have to switch case or detect each combination from real object status, just concatenate the keywords and voila we have the right image name

After doing the math, and it was so simple, we decided to go with the second approach to eliminate the extra requests the would be sent for each tree node to get all the three status images, so instead of 3 requests it would be only 1.

So, now comes the hard part. We need to generate the combined images. We could have wrote some code to generate the combined image of each tree node on the fly at run-time but we thought that this would not be the best decision as the status images are some static images which we already have at design time. So, there was no reason to go with that much run-time processing for each node when we could already generate all the possible images combinations we need.

That's why we decided to generate all the possible combinations that we could have for each system object status. Someone said that this would be too much images but we replied that it is not a problem as these images will be static and at the end of the days we are talking about a few mega bytes of static hard desk space not RAM.

We thought that doing the combination thing manually would be a shame and actually impossible as we have 25 x 4 x 7 = 700 combinations which means 700 images. Doing all of this work manually is so bad by all means especially when at some point the client decides to replace an image with another one or add a new image.

That's why I wrote a simple windows application which does all the hard work. You just give it each category images and in just seconds you get all your images.

Hint: the code related to generating all the possible combinations is built using the "Possibilities Cube" library I had posted before. If you are interested to read about it you can find it on Possibilities Cube Library - A Library Smart Enough To Calculate All Possibilities With Logical Conditions

Now, let's see some screenshots for the windows application.


Images for the first category (Cat01)

Images for the second category (Cat02)

Images for the third category (Cat03)

Adding images for each category on the application

Finally, the generated images

As you can see it is so easy to use the application and you can apply any modifications on the code to go with your specific business needs. Currently the code is set to export images into png format but you can change it as you wish in the code.

I didn't put much care into the application UI, graphics and so on as it is only for indoor usage to serve a certain need not to be a standalone product or anything like that so don't be turned off by the UI as at the end of the day it may help you save much time and effort.

Finally, you can download the application from here


Wish you will find this useful.
Good luck.


2013-12-20

Forcing ASP.NET Control To Go Through Its Full Life Cycle On Static Methods

A few days ago I needed to use a custom control which I made as a template to generate some HTML and return it as a response for an Ajax call, but, I faced a problem which is that my WebMethod is a static method which cannot access the control instance i dropped on the same page including the WebMethosd on code behind.

To make it more clear, here was the situation. I was working on an ASP.NET tree control which represents some system objects into hierarchical form. When testing the tree on few objects everything went fine, but with increasing the number of objects the tree became heavier and its response was not that appealing.

This made me think to take another approach which is using Ajax calls to hit the database and get the only objects I need per situation and finally attach/bind/update the tree with these objects. This will save me costy round trips back and forth to the server through postbacks and for sure the heavy load of the tree HTML with each request and response.

First, the idea was ideal but then came the difficulties. The tree control I was using is a third party control. So, I didn't actually have the code and pattern on which the nodes were built, so this made me think, how would I write the full HTML of the new nodes I got from the Ajax calls?

After short thinking, an idea hit me...... why don't I use the same tree control to draw me the single nodes I need, so whenever an Ajax call is performed a new instance of the tree control on another page will return me the HTML of the new nodes and then I will just update my main tree control with the new HTML. Luckily, I found that the tree control I was using had a method which would return me the generated HTML by the tree, so I cheered up and said out loud "Found it".

For the second time, this was a promising idea but then came the troubles. I had already implemented a static WebMethod on a page to call by Ajax. This WebMethod should be responsible for getting me the new HTML. So, I created an instance of the tree control inside this method, bound the tree to the new object, called the tree method which returns the generated HTML, but my code blown up with an exception.

After debugging I found that the tree method I was calling to get the generated HTML thrown some serious exceptions as the tree control instance I created inside my WebMethod is not actually fully loaded because the control didn't go through its whole life cycle.

This made me feel bad as the other alternatives are nightmares. So, I tried to search for any way by which I can force a control to go through its life cycle inside a static method. Finally, I found the solution and that is what I am going to show you here.

First, you have to know that the "Page" class has some validation which forces you to add a "Form" tag or you will get a serious exception. Since we will need on our solution to add the control we wish to load inside a static method on a page instance, then it would be nice to know this first.

To create a Page and bypass the Form tag existence we need to override the "VerifyRenderingInServerForm" method on the "Page" class. So, we will create a new class inherited from the "Page" class and override the "VerifyRenderingInServerForm" method as shown below.
public class PageWithoutForm : Page
{
    public override void VerifyRenderingInServerForm(Control control)
    {
    }
}

Now, we get to the last part which is forcing the control to go through its life cycle. This can be done as in the code below.
[WebMethod]
public static string LoadControlInStaticMethod()
{
 CustomControl ctrl = new CustomControl();

 var page = new PageWithoutForm();
 page.Controls.Add(ctrl);
 StringWriter writer = new StringWriter();
 HttpContext.Current.Server.Execute(page, writer, false);
 
 ctrl.CustomProperty = "SomeValue";
 ctrl.FireSomeAction();
}

If you have your control in "ascx" form, you can do the same as in the code above but with just updating the line where you defined the control instance as in the code below.
[WebMethod]
public static string LoadControlInStaticMethod()
{
 CustomControl ctrl = (CustomControl)LoadControl("~/SomeFolder/CustomControl.ascx");

 var page = new PageWithoutForm();
 page.Controls.Add(ctrl);
 StringWriter writer = new StringWriter();
 HttpContext.Current.Server.Execute(page, writer, false);
 
 ctrl.CustomProperty = "SomeValue";
 ctrl.FireSomeAction();
}


That is it all. I hope this will help someone one day.
Good luck.


2013-08-07

Possibilities Cube Library - A Library Smart Enough To Calculate All Possibilities With Logical Conditions



Possibilities Cube Library - A Library Smart Enough To Calculate All Possibilities With Logical Conditions

Let's imagine that as a big advertisement campaign Vodafone, Samsung & Nokia co-arranged a lottery. The winner will get two mobile phones in addition to two SIM cards with special numbers.

It is obvious that the SIM cards will be provided by Vodafone while the mobile phones manufacturer will be decided by toss, so the winner may get two phones from Samsung or Nokia or both. Also, the phones specifications will be somehow restricted as in the image below.
 

Possibilities Cube Library - A Library Smart Enough To Calculate All Possibilities With Logical Conditions


So, if we try to guess all the possible combinations of any of the phones, we can work it out and get the results as in the image below.

Possibilities Cube Library - A Library Smart Enough To Calculate All Possibilities With Logical Conditions

This was somehow easy as the possibilities are not that large. But what about guessing all the possible combinations of the two phones at the same time?

Possibilities Cube Library - A Library Smart Enough To Calculate All Possibilities With Logical Conditions

This time it is not that easy due to the large number of possibilities. As we can see there are 64 possibilities and this is because each phone can be one of 8 phone combinations and we have 2 phones, then 8 * 8 = 64

What if I told you that we need to re-visit the phones combinations are there is something not logical. We know that Samsung doesn't produce phones with Symbian as OS. So, we need to cancel the phone combination which includes both Samsung and Symbian.

Also, if the lottery managers said that the two mobile phones can't be identical or exactly the same which means that at least one phone specification should be differ between both phones.

All these logical restrictions should be included in our calculations to finally get all possible combinations we need.

If we try to visualize the whole thing, we can see it into the image below.

Possibilities Cube Library - A Library Smart Enough To Calculate All Possibilities With Logical Conditions


Now, how about if I tell you that there is a library which you can use to calculate such calculations and you can define your own logical restrictions to filter out all logically refused combinations, will you like to use this library?

Here come the PossibilitiesCube library.


PossibilitiesCube.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DevelopmentSimplyPut.CommonUtilities
{
    public class PossibilitiesCube
    {
        #region Properties
        public Func<Int64[], bool> AttributesCombinationValidator { set; get; }
        public Func<Int64[], bool> InstancesCombinationValidator { set; get; }
        public Func<Int64[], Int64[,], bool> FinalCombinationValidator { set; get; }
        public Int64 InstancesCombinationsMaxRowIndex
        {
            get
            {
                return instancesCombinations.GetLength(0) - 1;
            }
        }
        public Int64 InstancesCombinationsMaxColumnIndex
        {
            get
            {
                return instancesCombinations.GetLength(1) - 1;
            }
        }
        public Int64 AttributesCombinationsMaxRowIndex
        {
            get
            {
                return attributesCombinations.GetLength(0) - 1;
            }
        }
        public Int64 AttributesCombinationsMaxColumnIndex
        {
            get
            {
                return attributesCombinations.GetLength(1) - 1;
            }
        }

        bool combinationsExist;
        public bool CombinationsExist
        {
            get
            {
                return combinationsExist;
            }
        }
        #endregion Properties

        #region Fields
        Int64 numberOfInstances;
        Int64 instancesCombinationsMaxRowIndex;
        Int64 instancesCombinationsMaxColumnIndex;
        Int64 attributesCombinationsMaxRowIndex;
        Int64 attributesCombinationsMaxColumnIndex;
        Int64[,] attributesCombinations;
        Int64[,] instancesCombinations;
        Int64[] attributesPoolsSizes;
        #endregion

        #region Indexers
        public Int64 this[Int64 instancesCombinationIndex, Int64 instanceIndex, Int64 attributeIndex]
        {
            get
            {
                return GetAttributesCombination(instancesCombinations[instancesCombinationIndex, instanceIndex])[attributeIndex];
            }
        }
        public Int64[] this[Int64 instancesCombinationIndex, Int64 instanceIndex]
        {
            get
            {
                return GetAttributesCombination(instancesCombinations[instancesCombinationIndex, instanceIndex]);
            }
        }
        public Int64[] this[Int64 instancesCombinationIndex]
        {
            get
            {
                Int64[] result = new Int64[instancesCombinations.GetLength(1)];
                for (Int64 i = 0; i <= instancesCombinations.GetLength(1); i++)
                {
                    result[i] = instancesCombinations[instancesCombinationIndex, i];
                }
                return result;
            }
        }
        #endregion

        #region Constructors
        public PossibilitiesCube(Int64 _numberOfInstances, params Int64[] _attributesPoolsSizes)
        {
            if (_numberOfInstances <= 0)
            {
                throw new Exception("NumberOfInstancesPerPossibility must be +ve and greater than 0.");
            }

            numberOfInstances = _numberOfInstances;
            attributesPoolsSizes = _attributesPoolsSizes;

            attributesCombinationsMaxRowIndex = 1;
            foreach (Int64 size in _attributesPoolsSizes)
            {
                attributesCombinationsMaxRowIndex *= size;
            }
            
            attributesCombinationsMaxRowIndex--;
            attributesCombinationsMaxColumnIndex = _attributesPoolsSizes.Length - 1;
        }
        #endregion Constructors

        #region Methods
        public Int64[] GetAttributesCombination(Int64 index)
        {
            Int64[] result = new Int64[attributesCombinations.GetLength(1)];

            for (Int64 i = 0; i < attributesCombinations.GetLength(1); i++)
            {
                result[i] = attributesCombinations[index, i];
            }

            return result;
        }
        private void GetPossibilities()
        {
            Int64[,] result = new Int64[instancesCombinationsMaxRowIndex + 1, instancesCombinationsMaxColumnIndex + 1];
            Int64 numberOfFilteredOutPossibilities = 0;

            for (Int64 i = 0; i <= instancesCombinationsMaxRowIndex; i++)
            {
                Int64[] rowResults = GetPossiblityByIndex(i, instancesCombinationsMaxRowIndex, instancesCombinationsMaxColumnIndex, InstancesCombinationValidator, OperationMode.Instances);

                if (rowResults[0] == -1)
                {
                    numberOfFilteredOutPossibilities++;
                }
                else if(null != FinalCombinationValidator)
                {
                    if(!FinalCombinationValidator(rowResults, attributesCombinations))
                    {
                        rowResults[0] = -1;
                        numberOfFilteredOutPossibilities++;
                    }
                }

                for (Int64 k = 0; k < rowResults.Length; k++)
                {
                    result[i, k] = rowResults[k];
                }
            }

            Int64[,] finalResult;
            Int64 actualNumberOfPossibilities = instancesCombinationsMaxRowIndex + 1 - numberOfFilteredOutPossibilities;

            if (actualNumberOfPossibilities > 0)
            {
                finalResult = new Int64[actualNumberOfPossibilities, instancesCombinationsMaxColumnIndex + 1];

                Int64 actualRowIndex = 0;
                for (Int64 i = 0; i < instancesCombinationsMaxRowIndex + 1; i++)
                {
                    if (result[i, 0] != -1)
                    {
                        for (Int64 k = 0; k < instancesCombinationsMaxColumnIndex + 1; k++)
                        {
                            finalResult[actualRowIndex, k] = result[i, k];
                        }

                        actualRowIndex++;
                    }
                }

                combinationsExist = true;
            }
            else
            {
                finalResult = new Int64[1, instancesCombinationsMaxColumnIndex + 1];
                for (Int64 k = 0; k < instancesCombinationsMaxColumnIndex + 1; k++)
                {
                    finalResult[0, k] = -1;
                }

                combinationsExist = false;
            }

            instancesCombinations = finalResult;
        }
        public void BuildPossibilitiesMatrix()
        {
            Int64[,] result = new Int64[attributesCombinationsMaxRowIndex + 1, attributesCombinationsMaxColumnIndex + 1];
            Int64 numberOfFilteredOutPossibilities = 0;

            for (Int64 i = 0; i <= attributesCombinationsMaxRowIndex; i++)
            {
                Int64[] rowResults = GetPossiblityByIndex(i, attributesCombinationsMaxRowIndex, attributesCombinationsMaxColumnIndex, AttributesCombinationValidator, OperationMode.Attributes);

                if (rowResults[0] == -1)
                {
                    numberOfFilteredOutPossibilities++;
                }

                for (Int64 k = 0; k < rowResults.Length; k++)
                {
                    result[i, k] = rowResults[k];
                }
            }

            Int64[,] finalResult;
            Int64 actualNumberOfPossibilities = attributesCombinationsMaxRowIndex + 1 - numberOfFilteredOutPossibilities;

            if (actualNumberOfPossibilities > 0)
            {
                finalResult = new Int64[actualNumberOfPossibilities, attributesCombinationsMaxColumnIndex + 1];

                Int64 actualRowIndex = 0;
                for (Int64 i = 0; i < attributesCombinationsMaxRowIndex + 1; i++)
                {
                    if (result[i, 0] != -1)
                    {
                        for (Int64 k = 0; k < attributesCombinationsMaxColumnIndex + 1; k++)
                        {
                            finalResult[actualRowIndex, k] = result[i, k];
                        }

                        actualRowIndex++;
                    }
                }

                instancesCombinationsMaxRowIndex = intPow(actualNumberOfPossibilities, numberOfInstances) - 1;
                instancesCombinationsMaxColumnIndex = numberOfInstances - 1;
            }
            else
            {
                finalResult = new Int64[1, attributesCombinationsMaxColumnIndex + 1];
                for (Int64 k = 0; k < attributesCombinationsMaxColumnIndex + 1; k++)
                {
                    finalResult[0, k] = -1;
                }

                instancesCombinationsMaxRowIndex = 0;
                instancesCombinationsMaxColumnIndex = 0;
            }

            attributesCombinations = finalResult;
            GetPossibilities();
        }
        private Int64[] GetPossiblityByIndex(Int64 rowIndex, Int64 maxRowIndex, Int64 maxColumnIndex, Func<Int64[], bool> validator, OperationMode mode)
        {
            Int64[] result = null;

            if (rowIndex >= 0)
            {
                if (rowIndex <= maxRowIndex)
                {
                    result = new Int64[maxColumnIndex + 1];

                    for (Int64 i = 0; i <= maxColumnIndex; i++)
                    {
                        result[i] = GetPossiblityByIndex(rowIndex, i, maxRowIndex, maxColumnIndex, mode);
                    }

                    if (null != validator)
                    {
                        if (!validator(result))
                        {
                            for (Int64 i = 0; i <= maxColumnIndex; i++)
                            {
                                result[i] = -1;
                            }
                        }
                    }
                }
                else
                {
                    throw new Exception(string.Format("rowIndex can not be greater than {0}", maxRowIndex));
                }
            }
            else
            {
                throw new Exception("rowIndex must be +ve or equal to 0.");
            }

            return result;
        }
        private Int64 GetPossiblityByIndex(Int64 rowIndex, Int64 columnIndex, Int64 maxRowIndex, Int64 maxColumnIndex, OperationMode mode)
        {
            Int64 result = 0;

            if (rowIndex >= 0 && columnIndex >= 0)
            {
                if (rowIndex > maxRowIndex)
                {
                    throw new Exception(string.Format("rowIndex can not be greater than {0}", maxRowIndex));
                }
                else if (columnIndex > maxColumnIndex)
                {
                    throw new Exception(string.Format("columnIndex can not be greater than {0}", maxColumnIndex));
                }
                else
                {
                    Int64 numberOfHops = 1;
                    Int64 numOfItems = 1;

                    switch (mode)
                    {
                        case OperationMode.Attributes:
                            numOfItems = attributesPoolsSizes[columnIndex];
                            if (columnIndex == 0)
                            {
                                numberOfHops = 1;
                            }
                            else
                            {
                                numberOfHops = 1;
                                for (Int64 i = 0; i < columnIndex; i++)
                                {
                                    numberOfHops *= attributesPoolsSizes[i];
                                }
                            }
                            break;
                        case OperationMode.Instances:
                            numOfItems = attributesCombinations.GetLength(0);
                            numberOfHops = intPow(numOfItems, columnIndex);
                            break;
                    }

                    result = GetPossiblityByIndex(numOfItems, numberOfHops, rowIndex);
                }
            }
            else
            {
                throw new Exception("rowIndex and columnIndex must be +ve or equal to 0.");
            }

            return result;
        }
        private Int64 GetPossiblityByIndex(Int64 numberOfItems, Int64 numberOfHops, Int64 rowIndex)
        {
            Int64 result = 0;
            result = rowIndex / numberOfHops;
            result = result % numberOfItems;
            return result;
        }
        private Int64 intPow(Int64 a, Int64 b)
        {
            Int64 result = 0;

            if (0 == b)
            {
                result = 1;
            }
            else if (1 == b)
            {
                result = a;
            }
            else
            {
                result = a;
                for (Int64 i = 0; i < b - 1; i++)
                {
                    result *= a;
                }
            }
            
            return result;
        }
        #endregion Methods
    }

    public enum OperationMode
    {
        Attributes = 0,
        Instances = 1
    }
}


How to use the library?
The library is somehow simple in usage but always keep in mind the complexity of the task it is about to carry out. To see how simple it is you can check the test application below.


MainForm.Designer.cs
namespace TestApp
{
    partial class MainForm
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.btnRun = new System.Windows.Forms.Button();
            this.rtxtOutput = new System.Windows.Forms.RichTextBox();
            this.SuspendLayout();
            // 
            // btnRun
            // 
            this.btnRun.Location = new System.Drawing.Point(86, 312);
            this.btnRun.Name = "btnRun";
            this.btnRun.Size = new System.Drawing.Size(149, 33);
            this.btnRun.TabIndex = 0;
            this.btnRun.Text = "Get All Prizes Combinations";
            this.btnRun.UseVisualStyleBackColor = true;
            this.btnRun.Click += new System.EventHandler(this.btnRun_Click);
            // 
            // rtxtOutput
            // 
            this.rtxtOutput.Location = new System.Drawing.Point(12, 2);
            this.rtxtOutput.Name = "rtxtOutput";
            this.rtxtOutput.Size = new System.Drawing.Size(299, 304);
            this.rtxtOutput.TabIndex = 5;
            this.rtxtOutput.Text = "";
            // 
            // MainForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(321, 347);
            this.Controls.Add(this.rtxtOutput);
            this.Controls.Add(this.btnRun);
            this.Name = "MainForm";
            this.Text = "Test Application";
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Button btnRun;
        private System.Windows.Forms.RichTextBox rtxtOutput;
    }
}


MainForm.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using DevelopmentSimplyPut.CommonUtilities;
using System.Globalization;

namespace TestApp
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void btnRun_Click(object sender, EventArgs e)
        {
            rtxtOutput.Text = string.Empty;

            string[] colors = new string[2] { "White", "Black" };
            string[] brands = new string[2] { "Nokia", "Samsung" };
            string[] os = new string[2] { "Symbian", "Android" };

            Int64[] attributesSizes = new Int64[3];
            attributesSizes[0] = colors.Length;
            attributesSizes[1] = brands.Length;
            attributesSizes[2] = os.Length;

            PossibilitiesCube container = new PossibilitiesCube(2, attributesSizes);
            container.AttributesCombinationValidator = new Func<Int64[], bool>
                    (
                        delegate(Int64[] attributesCombination)
                        {
                            bool result = true;
                            //filter out if the brand is "Samsung" and the os is "Symbian"
                            if (attributesCombination[1] == 1 && attributesCombination[2] == 0)
                            {
                                result = false;
                            }
                            return result;
                        }
                    );

            container.InstancesCombinationValidator = new Func<Int64[], bool>
                    (
                        delegate(Int64[] instanceCombination)
                        {
                            bool result = true;
                            //filter out if both mobile phones are identical
                            if (instanceCombination[0] == instanceCombination[1])
                            {
                                result = false;
                            }
                            return result;
                        }
                    );

            container.BuildPossibilitiesMatrix();

            for (Int64 i = 0; i <= container.InstancesCombinationsMaxRowIndex; i++)
            {
                if (container.CombinationsExist)
                {
                    for (Int64 k = 0; k <= container.InstancesCombinationsMaxColumnIndex; k++)
                    {
                        string color1 = colors[container[i, k, 0]];
                        string brand1 = brands[container[i, k, 1]];
                        string os1 = os[container[i, k, 2]];
                        rtxtOutput.Text += string.Format(CultureInfo.InvariantCulture, "[{0},{1},{2}]", color1, brand1, os1) + ((k != container.InstancesCombinationsMaxColumnIndex) ? "\t" : string.Empty);
                    }

                    rtxtOutput.Text += Environment.NewLine;
                }   
            }

            MessageBox.Show(string.Format(CultureInfo.InvariantCulture, "{0} prize combinations are found.", container.InstancesCombinationsMaxRowIndex + 1));
        }
    }
}


So, after using the library to calculate all possibilities of the problem described above then running the test application, we will get the results as in the image below.

Possibilities Cube Library - A Library Smart Enough To Calculate All Possibilities With Logical Conditions


Notes:
Please keep in mind that if the number of attributes and instances are too big this may cause an arithmetic overflow.

[Update] This library is already used on Application To Generate Combined Images Of All Image-Categories Possible Combinations


That's it. You can download the code from here


Hope you find this library useful.
Goodbye.



2013-08-02

How To Copy SQL Hierarchical Data At Run-time While Keeping Valid Internal References And Self Joins

Sometimes when you deal with hierarchical data structures you may need to perform internal copy operations. To imagine what I mean, you can keep up with the scenario illustrated below.

You have a "Departments" table which include all departments in your system. Each department should have a parent department except for the top department which has no parent.

How To Copy SQL Hierarchical Data At Run-time While Keeping Valid Internal References And Self Joins

Now, assume that at some point in your system you need to make duplicates of the existing departments and this should happen automatically at certain condition or at certain action triggered by system user. So, you need to write a stored procedure which will copy the existing departments in the "Departments" table and insert them in the same table.

So, you may think that it is just a simple INSERT-SELECT statement operating on the same table; "Departments" table. This will leads you to the result as in the image below.

How To Copy SQL Hierarchical Data At Run-time While Keeping Valid Internal References And Self Joins

Now, you should have a look on this image and re-think what you did, is this the result you wish to achieve?
If you don't know or you still think this is the right result, you can have a look on the image below.

How To Copy SQL Hierarchical Data At Run-time While Keeping Valid Internal References And Self Joins

As you see in the image above, the newly inserted departments are messed up regarding their parent departments IDs. This is because while copying the old departments and inserting the new ones you didn't calculate the new IDs of the parent departments so now each department has a parent ID referencing the old department not the appropriate newly created one. This is so wrong.

To understand what I mean, you can have a look on the image below.

How To Copy SQL Hierarchical Data At Run-time While Keeping Valid Internal References And Self Joins

As you can see the department which had ID equals to "1" should now have ID equals to "5". Also, the department which had ID equals to "2" should now have ID equals to 6" and so on........

So, the valid result you wish to achieve is as in the image below.

How To Copy SQL Hierarchical Data At Run-time While Keeping Valid Internal References And Self Joins

So, how to reach this result? This is the main question this article is trying to answer.

Steps
  1. Declare a table variable in which we will keep the IDs mapping. Each record in this table will hold the old copied ID and its corresponding newly inserted ID. This way anytime we need to map an old ID to its new one we can use this table as a reference
  2. Copy and insert departments one by one and for each insert you just copy the "ParentID" column value as it is and we will deal with it later to be updated with the right value. Also, for each insert, insert a record in the IDs mapping table to hold the old and new IDs
  3. Update the "ParentID" column for the newly inserted departments with the new IDs depending on the IDs mapping table which now should be populated with IDs pairs

Now, it is the time for some code.


-- Variable to hold the ID of the department to be copied; the old department ID
DECLARE @OldDeptID INT

-- Variable to hold the ID of the newly copied department; the new department ID
DECLARE @NewDeptID INT

-- A table to hold the departments to be copied from the "Departments" table
-- The idx column is an identity column
DECLARE @DepartmentsToCopy TABLE (idx INT IDENTITY(1,1), ID INT, Name VARCHAR(100), ParentID INT)

-- A table to map each old copied ID to its new inserted ID
DECLARE @IdsMapping TABLE(Old_Id int , New_Id int)

-- A counter to be used in a loop
DECLARE @counter int
SET @counter = 1

-- Inserting the departments to be copied into the @DepartmentsToCopy table
-- Here we selected all records without any filtering but this can be modified
-- according to your business needs
INSERT INTO @DepartmentsToCopy
(
 ID
 , Name
 , ParentID
)
SELECT ID
, Name
, ParentID
FROM Departments

-- Looping on each department record in the @DepartmentsToCopy table to perform
-- the required actions on each record one by one
WHILE @counter <= (select max(idx) from @DepartmentsToCopy)
BEGIN
 -- Inserting a copy of the current department record in the "Departments" table
 -- but with adding the word "New" at the end of the "Name" column
 INSERT INTO Departments
 (
  ID
  , Name
  , ParentID
 )   
 SELECT TOP 1 ID
 , Name + 'New'
 , ParentID
 FROM @DepartmentsToCopy
 WHERE idx = @counter
 
 -- Setting the value of @NewDeptID with the scope identity
 -- in order to hold the ID of the newly inserted department record
 SET @NewDeptID = SCOPE_IDENTITY()
 
 -- Setting the value of @OldDeptID with the old copied ID
 SELECT TOP 1
 @OldDeptID = ID
 FROM @DepartmentsToCopy
 WHERE idx = @counter
 
 -- Inserting a record into the @IdsMapping table to hold the IDs mapping
 -- where the old id is @OldDeptID and the new one is @NewDeptID
 INSERT INTO @IdsMapping
 (
    Old_Id
  , New_Id
 )
 VALUES(@OldDeptID, @NewDeptID)
 
 -- Incrementing the counter to work on the next department record
 SET @counter = @counter + 1
END

-- Updating the ParentID column of the newly inserted departments
-- to match the new IDs using the @IdsMapping table which hold the IDs mapping
UPDATE Departments
SET ParentID = map.New_Id
FROM Departments AS Dept
INNER JOIN @IdsMapping AS newOnly
ON Dept.ID = newOnly.New_Id
INNER JOIN @IdsMapping AS map
ON Dept.ParentID = map.Old_Id


That's it. Hope you will find this helpful someday.



2013-08-01

How To Transform Unsorted Flat Hierarchical Data Structures Into Nested Parent-Child Or Tree Form Objects

Assume that you have hierarchical data structure presented into an SQL database table as in the image below.

How To Transform Unsorted Flat Hierarchical Data Structures Into Nested Parent-Child Or Tree Form Objects

As you can see each employee in the "Employees" table above can have a Manager which is an employee himself. In this case when the employee "Tarek" has "ManagerID" whose ID = 1, then "Tarek" has "Ahmed" as his manager. While "Ahmed" doesn't have a manager as he is the top manager.

This leads us to visualize the whole structure into parent-child relation as in the image below.

How To Transform Unsorted Flat Hierarchical Data Structures Into Nested Parent-Child Or Tree Form Objects

So, as we can see the data we can get from the "Employees" table is somehow flat because each data row will be represented by an "Employee" entity so at the end we can have a list of employees each preserves the ID of his manager as in the "Employee" entity below.

Employee.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DevelopmentSimplyPut.HierarchicalObjectsManagements.Entities
{
    public class Employee
    {
        public int ID { set; get; }
        public string Name { set; get; }
        public int? ManagerID { set; get; }

        public Employee() { }

        public Employee(int id, string name, int? managerID)
        {
            ID = id;
            Name = name;
            ManagerID = managerID;
        }
    }
}

Unfortunately this is not always enough as sometimes we find ourselves in a need to represent this data into a more structured form so that we can bind it with a tree control or whatever. So, we need to write some code to transform this unsorted flat hierarchical data structure into a parent-child or tree form.

To do so, let's first build an entity which will represent our parent-child or tree form to be used later. This leads us to the "EmployeeTreeNode" entity.

EmployeeTreeNode.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DevelopmentSimplyPut.HierarchicalObjectsManagements.Entities
{
    public class EmployeeTreeNode
    {
        public Employee Employee { set; get; }
        public bool IsProcessed { set; get; }
        public int Level { set; get; }

        private List<EmployeeTreeNode> childNodes;
        public List<EmployeeTreeNode> ChildNodes
        {
            get { return childNodes; }
        }

        public EmployeeTreeNode()
        {
            Level = 0;
            childNodes = new List<EmployeeTreeNode>();
        }

        public EmployeeTreeNode(Employee employee, bool isProcessed) : this()
        {
            Level = 0;
            Employee = employee;
            IsProcessed = isProcessed;
        }
    }
}

Now, we need to write the code which will do the transformation part. This is the code where the magic happens.

Utilities.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DevelopmentSimplyPut.HierarchicalObjectsManagements.Entities;

namespace DevelopmentSimplyPut.HierarchicalObjectsManagements.Utilities
{
    public static class Utilities
    {
        public static EmployeeTreeNode GetEmployeeTreeNode(List<Employee> employees)
        {
            EmployeeTreeNode result = new EmployeeTreeNode();
            result.IsProcessed = false;

            List<EmployeeTreeNode> nodes = new List<EmployeeTreeNode>();
            foreach (Employee emp in employees)
            {
                nodes.Add(new EmployeeTreeNode(emp, false));
            }

            foreach (EmployeeTreeNode empNode in nodes)
            {
                if (empNode.IsProcessed)
                {
                    continue;
                }
                else
                {
                    if (null == empNode.Employee.ManagerID)
                    {
                        result = empNode;
                        empNode.IsProcessed = true;
                        empNode.Level = 0;
                    }
                    else
                    {
                        ProcessNode(empNode, nodes);
                    }
                }
            }

            if (result.ChildNodes.Count == 0)
            {
                result.ChildNodes.AddRange(nodes);
            }

            return result;
        }

        private static void ProcessNode(EmployeeTreeNode node, List<EmployeeTreeNode> nodes)
        {
            EmployeeTreeNode parentNode = nodes.DefaultIfEmpty(null).FirstOrDefault(n => n.Employee.ID == node.Employee.ManagerID);
            if (null != parentNode)
            {
                if (!parentNode.IsProcessed)
                {
                    ProcessNode(parentNode, nodes);
                }

                node.IsProcessed = true;
                node.Level = parentNode.Level + 1;
                node.Parent = parentNode;
                parentNode.ChildNodes.Add(node);
            }
            else
            {
                node.IsProcessed = true;
                node.Level = 0;
                node.Parent = null;
            }
        }

        public static string Repeat(this string source, int numOfTimes)
        {
            string result = source;

            if (numOfTimes > 0)
            {
                for (int i = 0; i < numOfTimes - 1; i++)
                {
                    result += source;
                }
            }
            else
            {
                result = string.Empty;
            }

            return result;
        }
    }
}

Now you can use the code above to get your parent-child or tree form from the unsorted flat hierarchical data structure. To validate the code above, here is a demo windows forms application which you can use.

Form1.Designer.cs
namespace HierarchicalObjectsManagements
{
    partial class Form1
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.btn_BuildTree = new System.Windows.Forms.Button();
            this.lst_Employees = new System.Windows.Forms.ListBox();
            this.SuspendLayout();
            // 
            // btn_BuildTree
            // 
            this.btn_BuildTree.Location = new System.Drawing.Point(181, 165);
            this.btn_BuildTree.Name = "btn_BuildTree";
            this.btn_BuildTree.Size = new System.Drawing.Size(75, 23);
            this.btn_BuildTree.TabIndex = 0;
            this.btn_BuildTree.Text = "Build Tree";
            this.btn_BuildTree.UseVisualStyleBackColor = true;
            this.btn_BuildTree.Click += new System.EventHandler(this.btn_BuildTree_Click);
            // 
            // lst_Employees
            // 
            this.lst_Employees.FormattingEnabled = true;
            this.lst_Employees.Location = new System.Drawing.Point(12, 12);
            this.lst_Employees.Name = "lst_Employees";
            this.lst_Employees.Size = new System.Drawing.Size(244, 147);
            this.lst_Employees.TabIndex = 1;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(268, 193);
            this.Controls.Add(this.lst_Employees);
            this.Controls.Add(this.btn_BuildTree);
            this.Name = "Form1";
            this.Text = "Hierarchical Objects Management";
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.Button btn_BuildTree;
        private System.Windows.Forms.ListBox lst_Employees;
    }
}

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using DevelopmentSimplyPut.HierarchicalObjectsManagements.Entities;
using DevelopmentSimplyPut.HierarchicalObjectsManagements.Utilities;

namespace HierarchicalObjectsManagements
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btn_BuildTree_Click(object sender, EventArgs e)
        {
            lst_Employees.Items.Clear();

            List<Employee> employees = new List<Employee>();
            employees.Add(new Employee(4, "Saleh", 2));
            employees.Add(new Employee(1, "Ahmed", null));
            employees.Add(new Employee(5, "Selim", 4));
            employees.Add(new Employee(2, "Tarek", 1));
            employees.Add(new Employee(6, "Mohamed", 2));
            employees.Add(new Employee(3, "Hasan", 1));

            EmployeeTreeNode employeeTreeTopNode = Utilities.GetEmployeeTreeNode(employees);

            BuildTree(employeeTreeTopNode);
            MessageBox.Show("Done");
        }

        public void BuildTree(EmployeeTreeNode node)
        {
            lst_Employees.Items.Add("-".Repeat(node.Level) + node.Employee.Name);
            foreach (EmployeeTreeNode childNode in node.ChildNodes)
            {
                BuildTree(childNode);
            }
        }
    }
}

After running the windows form application, you will get the result as in the image below.

How To Transform Unsorted Flat Hierarchical Data Structures Into Nested Parent-Child Or Tree Form Objects


That's it. This is just a proof of concept but you can tweak it to satisfy your specific business and needs. I hope this helps you someday :)

You can download the code from here


Good Bye.