Taoffi's blog

prisonniers du temps

Silverlight database to DataGrid, yet another approach- Part V

The data record (the ‘form’)

As I mentioned in Part II, my proposed solution (aimed to transfer and communicate data between a Silverlight Client and the database server) is composed of the following schematic classes:

Data level classes

SilverDataTable

Represents the table (or view, or function…) data.

Contains:

A meta-data table

A list of data rows

SilverDataRow

Represents one record of data.

Contains:

List of data cells

Linked to:

A parent table

SilverDataCell

Represents one record’s data cell.

Linked to:

A parent row

A parent meta-data column

Meta-data level classes

SilverMetaTable

Represents the table’s meta-data (schema)

Contains:

A list of meta-data columns

Linked to:

A parent table

SilverMetaColumn

Represents one column’s schema information.

Note: This is the ‘Key Class’ where you can insert all your required business logic.

Linked to:

A parent meta-data table

 

The SilverMetaColumn class is the key class which will transport many of business logic information from the database server to the client (and back to the server).

A meta-data column object of this class can transport information like:

§  The column’s data type;

§  Can the column contain null value?

§  The semantic type of the field (ex: control type… i.e. is this an option list? a value entry field?…)

§  Valid value ranges;

§ 

§  etc.

 

What I want to expose here is: how to use this meta-data column object to ‘automatically’ (or semi-automatically) compose a data record (form) on the client side.

 

From the field semantic àto the control àto the form

After having examined many of the data entry forms in various contexts, I think that a data entry (from the user interface point of view) is either:

§  A value entry control (i.e. a TextBox or TextBox-like: Numeric Up-down control for example)

§  Or a choice control (i.e. a ListBox, ComboBox, CheckBox, Radio-buttons-collection or other type of collection container controls)

 

Of course, the visual aspect of the control and its interactivity scenario may vary… but its presentation-semantic remains the same: a value entry or a choice.

From the business logic point of view (at the server side), what is important is not the visual aspect or visual transitions for presenting and collecting the data. What is important is the conformance of the collected data to specific business logic constraints:

§  Does the entry conform to the required data type?

§  Does the entry conform to the required valid range?

§ 

 

According to this approach, we can imagine a solution where the server tells the client:

§  Here is the requested data record composed of:

§  Column 1 : this is the record key column, It is Read-only (i.e. it will be handled at the server)

§  Column 2: this is an entry of data type Text, Required (cannot be null), cannot exceed 50 characters, Current value is “sample entry”;

§  Column 3: this is an entry of data type integer, Can be null, Valid range is 0 through 40, Default is 25, Current value is 32;

§  Column 4: this a single choice of the following option list (x1, x2, x3), Cannot be null, Current value is x2;

§  Column 5: this a boolean value, default is True, Current value is False;

§ 

 

Receiving this information, the client can then undertake the presentation process, for example:

§  Start record presentation transition (animation);

§  For each column, create the corresponding control (ex: according to the user interface graphical chart);

§  Set each control’s properties to conform to business constraints (read-only, max chars, valid values check… etc.)

 

Here is an example of a solution using this approach:

 

Silverlight database to DataGrid, yet another approach- Part IV

Filtering database data

As we have seen in Part III, the SQL Select statement is composed of the following parts:

SELECT                 [field1], [field2],… [fieldn]

FROM                   [database table or ‘table-like’ store]

WHERE                [conditions]

ORDER BY           [field name] <sort direction>,

                               [field name] <sort direction>

 

It is the ‘WHERE’ clause of the SQL statement that is used to filter the data.

‘WHERE’ is followed by ‘conditions’. Several conditions are concatenated using AND or OR operator.

What is a filter condition?

A ‘condition item’ can be presented as:

[Field name] [Comparison operator] [Filter value]

 

A collection of ‘condition items’ can be presented as something like:

<Condition item> <Concatenation operator> <Other condition item>

 

Examples:

Name = ‘John’

Amount >= 100

(Last_name LIKE ‘%bama’) AND (Age >= 20)

 

That seems easy to be represented by an object:

public class SilverFiltertItem

{

       string              m_field_name,

                           m_filter_value;

       CompareOperator     m_compare_operator  = CompareOperator.EQUAL;

       ConcatOperator      m_concat_operator   = ConcatOperator.AND;

       ...

       ...

The concatenation and comparison operators are members of the following enum types:

public enum filter_concat_operator

{

       and,

       or,

       none

};

 

public enum filter_compare_operator

{

       Equal,

       Not_equal,

       Like,

 

       GreatertThan,

       GreatertThanOrEqual,

       LessThan,

       LessThanOrEqual

       ...

       ...

};

 

Two properties can give us the Compare and Concatenate strings when required:

protected string ConcatString

{

       get

       {

             if( m_concat_opertaor == filter_concat_operator.none)

                    return "";

 

             // return ‘AND’ or ‘OR’

             return Enum.GetName( typeof( filter_concat_operator), m_concat_opertaor).ToUpper();

       }

}

 

protected string CompareString

{

       get

       {

             switch (m_compare_operator)

             {

                    case filter_compare_operator.Equal:

                           return "=";

 

                    case filter_compare_operator.Like:

                           return " Like ";

 

                    case filter_compare_operator.Not_equal:

                           return "!=";

 

                    case filter_compare_operator.GreatertThan:

                           return ">";

 

                    case filter_compare_operator.GreatertThanOrEqual:

                           return ">=";

 

                    case filter_compare_operator.LessThan:

                           return "<";

 

                    case filter_compare_operator.LessThanOrEqual:

                           return "<=";

 

                    default:

                           throw new Exception("Unknown Compare opertor encoutered!");

             }

       }

}

 

The FilterItem object can now provide us with its SQL string through a property like the following:

public string SqlFilterString

{

       get

       {

             if( string.IsNullOrEmpty(m_filter_value)

                           || string.IsNullOrEmpty( m_target_column_name))

                    return "";

 

             string       field_name   = "[" + m_target_column_name + "]",

                          str_ret      = ConcatString + "(";

 

             string str_value    = m_filter_value;

            

             str_ret      += field_name + " " + CompareString + " " + str_value + ") ";

             return str_ret;

       }

 

A filter collection class may look like this:

public class SilverFilter : List<SilverFilterItem>

{

       ...

       ...

 

The filter collection can return the entire SQL filter string through successive calls to its member items:

public string SqlFilterString

{

       get

       {

             if( Count <= 0)

                    return "";

 

             int    ndx          = 0;

             string str_ret      = "";

 

             foreach (SilverFilterItem f in this)

             {

                    if (ndx <= 0)

                           f.Concat_operator = filter_concat_operator.none;

                    else

                    {

                           if( f.Concat_operator == filter_concat_operator.none)

                                  f.Concat_operator   = filter_concat_operator.and;

                    }

 

                    str_ret      += f.SqlFilterString;

                    ndx++;

             }

 

             return str_ret;

       }

 

 

Our DataTable object (see previous posts) can now include a Filter collection object and construct the SQL Where clause:

 

protected SilverFilter    m_filter     = new SilverFilter();

...

...

 

public string SqlCommand

{

       get

       {

             ...

             ...

             string       str_cmd      = "SELECT * FROM " + m_table_name;

             string       str_sort     = m_sort.StrSql;

             string       str_filter   = m_filter.SqlFilterString;

 

             if( ! string.IsNullOrEmpty( str_filter))

                    str_cmd      += " WHERE(" + str_filter + ")";

 

             if( ! string.IsNullOrEmpty( str_sort))

                    str_cmd      += " ORDER BY " + str_sort;

 

             return str_cmd;

       }

 

Note: a better approach would, of course, be to map the filter field names to the table’s meta-data table fields… or directly use MetaDataTable’s columns as filter members (instead of using field names as strings). In this last case, we would be able to check columns’ data types and also act more closely to database’s business logic.

 

In other words: what is proposed here is a simple approach that can evolve according to your needs.

 

 

You can download the sample code: SilverDbDataGrid-2-sorting&filtering.zip (1.87 mb)

Silverlight database to DataGrid, yet another approach- Part III

Sorting and filtering database records

After having succeeded to display our server data into Silverlight DataGrid (see previous post), we will now continue the adventure to complete our solution with some useful and necessary features that Silverlight DataGrid originally proposes (for example, sorting records) and add some new features for filtering records.

 

Silverlight DataGrid allows the user to sort displayed rows by clicking into column headers.

That is nice and helpful. But, in the context of data provided by a server through a database, that doesn’t seem to be quite a correct approach.

In fact, in such context, if you click to sort a column in the ascending order, the displayed data records may not contain the entire values stored in the database. The data should be ‘refreshed’ to represent the effective sorted values according to currently stored database data in its entirety.

 

Sorting database data

How database data can be sorted?

The answer: this can be done using the SQL ‘ORDER BY’ clause.

We use SQL to query the data according to the following (simplified) statement template:

SELECT                [field1], [field2],… [fieldn]

FROM                  [database table or ‘table-like’ store]

WHERE                [conditions]

ORDER BY           [field name] <sort direction>,

                            [field name] <sort direction>

 

The ORDER BY part, contains a list of field names (each of which should be one of the listed SELECT fields, or, at least, one of the queried table or ‘table-like’ fields)

Each field in the ORDER BY clause can have a specified sort direction (ASC for ascending, or DESC for descending). The default being the ascending direction (if the direction is omitted)

 

If we have to represent an ‘order by’ item as an object, the object may then be something like this:

public class SortItem

{

       string       m_field_name;

       SortDirection m_direction  = SortDirection.ASC;

       ...

       ...

 

So, in our case, we can simply sort our DataGrid by building a list (List<SortItem>) of clicked column names (met-data column names) and passing them to the server to compose the ORDER BY part of the query accordingly and return too us the desired sorted data.

 

Implementing the solution

Let’s begin, at the server side, by enriching our service’s SilverDataTable (see Part II) object by a new SortList object, which will simply be a List<> of SortItem.

 

public enum sort_direction

{

       asc,

       desc,

       none

};

 

A SortItem object can be defined as follows:

 

[DataContract]

public class SilverSortItem

{

       protected string           m_target_column_name       = "";

       protected sort_direction   m_sort_direction    = sort_direction.asc;

       ...

       ...

 

We can now define a sort-list object which is mainly a List<SilverSortItem > that takes care for some constraints like, for example, not adding duplicate fields:

 

public class SilverSort : List<SilverSortItem>

{

 

The class exposes an indexer that returns the field by its name:

public SilverSortItem this[string field_name]

{

       get

       {

             if( string.IsNullOrEmpty( field_name) || Count <= 0)

                    return null;

 

             foreach (SilverSortItem e in this)

             {

                    if( string.Compare( e.Target_column_name, field_name, true) == 0)

                           return e;

             }

             return null;

       }

 

An Add method, to add or modify settings of an existing sort field:

public new void Add(SilverSortItem element)

{

       if( element == null || string.IsNullOrEmpty( element.Target_column_name))

             return;

 

       SilverSortItem      e      = this[element.Target_column_name];

 

       if( e == null)

             base.Add( element);

       else

             e.CopyOther( element);

}

 

And a property that returns the SQL sort string of the contained fields:

public string StrSql

{

       get

       {

             if (Count <= 0)

                    return "";

 

             string str_ret = "";

 

             foreach (SilverSortItem s in this)

             {

                    if( str_ret.Length > 0)

                           str_ret      += ", ";

 

                    str_ret += s.StrSql;

             }

             return str_ret;

       }

}

 

Let’s now include a Sort list into our SilverDataTable class:

[DataContract]

public class SilverDataTable

{

       protected SilverSort       m_sort = new SilverSort();

 

 

We can now extend our SqlCommand property to include the sort string:

string str_sort     = m_sort.StrSql;

 

if( string.IsNullOrEmpty( str_sort))

       return "SELECT * FROM " + m_table_name;

return "SELECT * FROM " + m_table_name + " ORDER BY " + str_sort;

 

We will also change our WCF service method to include an optional list of sort field names:

 

[OperationContract]

public SilverDataTable GetData(string str_connet_string,

                           string str_sql_command,

                           SilverSort sort_items,

                           int n_records)

 

 

Sorting… the Client-side job

At the client-side, our DataGrid should send us a message each time a column should be added to the SilverDataTable sort list. And, unfortunately, that is not as ‘intuitive’ as we may like!

Manish Dalal published a very interesting paper about custom sorting and paging inside a DataGrid. His work presents a good (and extensible) start point.

The usable part of his work in our project is the CollectionView class which can give us more control on sorting in response to DataGrid Column Click events.

As you may see at Bea Stollnitz interesting blog, Silverlight DataGrid internally uses a CollectionViewSource (an object that implements ICollectionView interface).

That is: When we set DataGrid’s ItemsSource to an observable collection, it integrates it into its own ICollectionView object.

Fortunately, the DataGrid is smart enough to let us implement this object ourselves, i.e. if we set its ItemsSource to an object that implements the ICollectionView, the DataGrid will use this instead of its own one.

So, let’s create a class that implements the ICollectionView:

public class SilverCollectionView<T> : ObservableCollection<T>, ICollectionView

{

 

The ICollectionView exposes a collection of ‘SortDescriptions’ that we will use, for the requirements of our project, as a list of field names to be sorted. i.e. each time a column will be clicked, we will add the column name to the SortDescriptions list and generate a Refresh event to request the server’s data sorted as desired.

Here is the SilverDatasetView helper class that will integrate our SilverDataTable as an ICollectionView:

public class SilverDatasetView : SilverCollectionView<SilverDataRow>

{

       public SilverDatasetView() : base()

       {

       }

 

       public SilverDatasetView(SilverDataTable service_data_table) : base()

       {

             CopyDataset( service_data_table);

       }

 

       public bool CopyDataset(SilverDataTable data_table)

       {

             if( data_table == null || data_table.Rows == null)

                    return false;

 

             base.SourceCollection      = data_table.Rows;

             return true;

       }

 

Once we receive the data from our database server, we can now set the DataGrid’s ItemsSource to a SilverDatasetView object:

SilverDatasetView   dataset_view = new SilverDatasetView(m_table.Rows);

data_grid.ItemsSource                   = dataset_view;

 

To tell the DataGridColumn how to send us desired sort items, we will set its SortMemberPath to the data column name:

Grid_col.CanUserSort              = true;

Grid_col.SortMemberPath    = data_col.Name;

 

The last part is to respond to the Refresh event of our SilverDatasetView in order to collect the desired sort field names and request the data accordingly:

dataset_view.OnRefresh     += new EventHandler<RefreshEventArgs>(m_dataset_view_OnRefresh);

 

void m_dataset_view_OnRefresh(object sender, RefreshEventArgs e)

{

       if( m_sort_fields == null)

             m_sort_fields = new ObservableCollection<SilverSortItem>();

 

       m_sort_fields.Clear();

 

       SilverSortItem      sort_element;

 

       foreach (SortDescription s in e.SortDescriptions)

       {

             sort_element = new SilverSortItem();

             sort_element.Target_column_name   = s.PropertyName;

             sort_element.Sort_direction             =

                           (s.Direction == ListSortDirection.Ascending)

                           ?(sort_direction.asc)

                           :(sort_direction.desc);

 

             m_sort_fields.Add( sort_element);

       }

      

       RequestSilverDataset();

}

 

RequestSilverDataset() method will, mainly, do the following:

DataServiceClient   cli    = service_helpers.DataServiceProxy(5);

 

cli.GetDataCompleted += new EventHandler<GetDataCompletedEventArgs>(cli_GetDataCompleted);

cli.GetDataAsync( str_connet_string, str_sql_command, m_sort_fields, n_records);

 

That is it… your DataGrid can now be sorted according to the real data on the server.

Don’t forget: Click the column to sort/ shift + click to add the column to the sorted list…

 

Download the sample code:

SilverDbDataGrid-2-sorting.zip (1.36 mb)

Silverlight database to DataGrid, yet another approach- Part I

Introduction

As I exposed in an earlier post, Database structure can be declined into the following hierarchy

§ A table

§ Containing rows

§ Containing data //(arranged into columns)

 

This schema cannot live without the following meta-data structure

§ Table definition

§ Column definition:

§ Data type / data size (storage space reservation maximum size)

 

One important thing to observe is the relation between the meta-data definitions and the data storage or presentation:

 

 

 

.

All these structures and concepts have been used for years through several database access technologies (ado.net for example) using some well known objects like Dataset, DataTable… etc. and everybody was easily able to read and display data into grid controls in Windows and Web forms.

 

Now this question is once again exposed with Silverlight: How to read and display data into a DataGrid!

The reason for this is, mainly, that Silverlight is a “multiplatform browser plug-in”, and, as such, don’t know about database facility objects like Datasets, DataTables…

Also, Silverlight being a “client”, it cannot directly have access to what is stored or otherwise going-on on the server-side end. Accessing such information is done by calling Xml web services hosted at the server.

 

Interesting solutions

Some interesting work has been done to try to solve this dilemma. Some of the solutions proposed a (very interesting) workaround which requests the server’s xml dataset, and entirely creates database objects at runtime (MSIL!) on the Silverlight client side.

Although this seems quite fascinating, it is still a sort of ‘black magic’ solution, and lets me uncomfortable to use it as a sustainable solution. It also misses some of the interesting features of Silverlight, like Binding Converters.

 

Starting at the bottom line

One may think: A Dataset already has all what we need: Meta data definitions, Data rows… etc.

Let’s create a Dataset on the server-side and then write it down to the client through a web service. The client can then consume the received dataset by incorporating it into the required classes.

 

I tried this solution, but ended up by finding myself in front of a great deal of ‘raw’ data that should be reformatted on the client… And, worse, this data reformatting altered some important business logic that should be decided on the server-side.

For example, in a sales representative client application, if a ‘customer’ field value inside an ‘order’ should be only one of the current user’s authorized customers; this would obviously be better decided on the server not the client.

Even the fact of presenting the ‘customer’ field as selection option (combo box) or as a plain text field is better to be decided at the server end.

 

The ‘Dataset’ is a great and useful object, but it only provides ‘raw’ relational data. It needs to be complemented by other features in order for the business logic (at the server-side) to take control over data presentation and manipulation process (at the client-side).

 

 

The diagram below illustrates the basic schema used by the proposed solution to represent the data stored in a database.

 

The diagram’s classes represent the 2 levels of a database table:

§  At the meta-data level : SilverMetatTable, composed of one or more SilverMetaColumn(s);

§  The data storage level is represented by SilverDataTable, which has a meta-data table definition (SilverMetaTable). The data table contains zero or more rows (SilverDataRow), each of which containing data cells (SilverDataCell). Each data cell is linked to the corresponding meta-data column.

 

The meta-data column represents the basic properties of a database column (data type, default value, is/is not nullable… etc.), but can now expose all the desired presentation features and functionalities as required by the business logic.

It may, for example, expose the desired presentation type (plain text / option list… etc.), for an option list: the choice-elements (or where to request them), the data access options (read-only / read-write…) according to session context or application context… etc.

 

Surprisingly coding the required classes for this part of the solution was a matter of a few hundreds of lines of code (approx 4 hours + 2 for repairing some of my ‘chronic’ errors… I do many!)

 

In the next post, I will talk about ‘The client side’ job… and deliver a fully-functional sample application.

See part II

 

Revisiting the Database design foundations

Software is about defining objects structures, relationships and behavioral contour. This is probably a too short sentence to define what software is. But it can be a good commencement for what I would like to expose here.

Years passed for the software industry to mature and be able to express the complexity of this task. And, it seems that there still will be some more years to come for this maturity to gain some sort of stability and normalization.

Remember the lessons about the ‘Date object’ which would never display a ‘February 30’ day. Although that seems so far away, we still encounter things of the same kind (remember, for example, the ‘page 1 of 0’ syndrome!). Those are simple indications about how complex it can be to mimic the ‘real world’ system, its large span of objects, relationships and behaviors.

 

One of the early, and interesting, questions that the Software had to solve was: data storage.

Historically speaking, we can summarize the maturity of the provided solution into three stages:

§ The folder/file structure

§ The data table

§ The ‘relational’ database

 

The ‘data table’ solution seems particularly interesting:

If you have a look at a data table (even within any of the most elaborate of today’s database engines) you can easily state how the simplicity of the structure looks embarrassing:

§ A meta-data space which describes the table’s columns.

§ And, a storage space where the columns’ data is stored into rows.

 

 

 

 

Basically, the column definition describes its data type and storage size. This allows the storage system to organize the data rows according to theses definitions.

 

Of course, with the time going, so many other attributes and artifacts had been added to the data table solution. Column definitions, for example, got more elaborate (ex: default value, auto-increment, indexes, data validation constraints… etc.), Columns’ relationships (foreign keys…), Operations like row insertions, row deletions or row updates can be triggered by procedures that execute particular ‘business’ logic… etc. But the foundations of the solution remain strictly the same as the early reflection and design…

One of the reasons of this design success seems to be the readability of the solution.

 

Anyway, the fact is, today, nearly no one single software solution can live without a database! And that itself is a profound demonstration of the efficiency of the initial design.

 

The Software ‘Code / Database’ schism!

Strangely, databases still live almost apart from the software development cycle. When you talk software, you seem to be supposed talking about classes, inheritance, behaviors, user interface… etc. and databases almost seem so simplistic compared to this ‘alive’ jungle!

The reasons for this schism seem to be cultural and social (within the development community) more than being a verdict against the database solution efficiency.

 

Industrially speaking, this schism seems often to be one of the most common explanations of software projects failures. It is also, probably, one of the profound reasons for some of the software industry’s inefficiencies.

 

What I think is that databases efficiency can be beneficially used for leveraging software objects properties and behaviors. And that database organization and readability bring interesting applications and solutions for controlling objects behaviors in order to create more versatile and scalable software solutions.

At a very simple level, think about application configuration files (which are great solutions but can sometimes hardly be altered without producing inconsistencies).

Think about a truly structured configuration file, one that guaranties no typing errors, no inconsistencies according to application’s specific logic.

This can be, relatively easily, achieved using a Database!

 

Database-stored menus… extending and redirecting actions

Let’s go a step forward and use the database to store, for instance, application menus.

A menu is basically a displayed text that, when clicked, invokes an application’s functionality.

By storing menus in the database, we may be wishing to expose only some of our application’s functionalities (according to specific application version for example). We may also be wishing to let our customer’s IT team to expose different functionalities to user groups according to a predefined business role hierarchy.

We can also use this structure to localize our application menus without having to recompile specific localized versions.

Another advantage: to be able to add a new menu after a new functionality is added to the software, or remove one that no more exist etc.

 

Menus are good examples for the ‘action’ concept. In fact, clicking a menu, a button or other button-like item in the user interface, results in invoking an action in the software. The action to be invoked has an entry point (a function) which is, normally, part of the handled object’s code.

 

Thanks to .NET System.Reflection (or any Reflection-like library) we can retrieve a method by its name. This can allow us to store object’s method names in the database, and, in the action properties, tell the software which method to be called. The software can then search the defined method, and, if found, invokes it when the action is activated.

 

Of course there are some other considerations to be taken into account: invoked function parameters, return value... etc. But the main concept remains correct and feasible.

 

Database-based solutions advantages?

Well, just to name a few:

§  Runtime object properties modification: Imagine a (dynamic) ‘web page section’ object which is initially created as 600/800 pixels dimensions. A database-based solution can allow the user to easily change these properties to obtain, say, a 1027/800 pixels object on runtime… without having to recompile the application’s code;

§  Runtime object properties extension: Software (compiled) objects’ structures NEVER satisfy at 100% real world structures or requirements (at least during a reasonable period of time). With a database-based solution, using, for example, an EAV (Entity Attribute Value) implementation, you can envisage extending compiled software objects… without having to recompile the application’s code;

§  Action processing redirection: Imagine a software functionality that may require authentication only in some contexts and none in other contexts, depending on the customer’s environment. Thanks to database readability and simplicity, the customer can easily choose which entry point to assign to the action according to his or her environment (without having to recompile the software code)

§ 

§ 

 

SQL recursive schemas

SQL recursive schemas

 

One of the interesting features of relational databases is the possibility for a table to reference itself.

Suppose we want to represent the file folder system in a database. We can define a table having, for example, 3 fields: id, parent_id and name:

 

We can then create a self referencing relationship between the id and parent_id field.

Simple (and elegant) solution!

Yes, but that also let us inherit all specific constraints of recursive patterns.

 

The figure above shows the simple (and somehow elegant) of the table schema or pattern.

What it doesn’t show is how the ‘real world’ records will look like when stored in the table:

 

 

Two important constraints of such recursive schema are:

·         Path readability: i.e. as the schema allows an infinite nest levels, reading an item path may cause a stack overflow;

·         Cyclic references: i.e. a folder that references itself (or one of its children) as a parent (which may end up by creating infinite path readability loop;

 

The Nest level problem

A function or stored procedure for reading an item path may look like this:

 

CREATE FUNCTION dbo.Read_path

(

       @item_id int

)

RETURNS nvarchar(max)

AS

BEGIN

       declare             @parent_id   int,

                    @path        nvarchar(max),

                    @name        nvarchar(50)

      

       select @parent_id   = parent_id,

             @name        = name

       from dbo.sample_folders

       where(id = @item_id)

      

       if @parent_id is null

             return @name

 

       -- recursive call  

       return dbo.Read_path ( @parent_id) + N'\' + @name

END

GO

 

 

Such a function will work nicely as long as the folder’s nest level remains in a ‘reasonable’ value. For example, in MS SQL Server, nest level (recursive call stack whose value can be returned at any time by the @@nestlevel system function) has a maximum of 32.

 

So, it is more precautious to test current @@nestlevel value before proceeding:

 

if @@nestlevel >= 32

       return N'Path nest level exceeds allowed value'

 

However, doing so breaks the efficiency and usability of our pattern. The best solution would be to get rid of the recursive call inside our function. Here is an example using a goto label:

 

       DECLARE @folder_name       nvarchar(255),

              @parent_id   int,

              @cur_item_id int,

              @path        nvarchar(max)

 

       SELECT @path        = N'',

              @cur_item_id = @item_id

 

-- recursion label

get_nested_path:

       SELECT @folder_name = f.name,

             @parent_id   = f.parent_id

       FROM   dbo.sample_folders AS f

       WHERE( f.id = @cur_item_id)

 

 

       if @parent_id is null

       begin

              -- We are on the root

              return @folder_name + @path

       end

 

       select @cur_item_id = @parent_id,

              @path        = N’\’ + @folder_name + @path

 

       -- goto recursion label

       goto get_nested_path

 

 

Cyclic references problem

Our read path function can still be trapped into an infinite loop that can be caused by cyclic references… i.e. a folder referencing itself or one of its children as a parent:

 

 

One way to prevent cycling references is to create a constraint on the folders table that tests for cycling references. The constraint may call a function that returns true (1) if the item is not valid:

 

CREATE FUNCTION [dbo].[Is_cyclic_folder]

(

       @item_id            int,

       @parent_id          int

)

RETURNS bit

AS

BEGIN

       -- if this is a root item: OK

       if @parent_id is null

             return 0

      

       -- self referencing?

       if @parent_id = @item_id

             return 1

      

       declare             @parent_folder_id   int,

                    @folder_id          int

      

       -- initial parent folder

       select @parent_folder_id = @parent_id

      

       while NOT @parent_folder_id is null

       begin

             -- if the current parent folder is one of children folders:

             -- then this item is cyclic (NOT VALID)

             if @parent_folder_id = @item_id

                    return 1

            

             select @folder_id   = @parent_folder_id

            

             select @parent_folder_id   = parent_id

             from dbo.sample_folders

             where( id = @folder_id)

       end

      

       return 0

END

 

The constraint may look like this:

 

ALTER TABLE dbo.sample_folders ADD CONSTRAINT

       CK_sample_folders_cyclic CHECK ([dbo].[Is_cyclic_folder](id, parent_id) = 0)