Using DiscoverParameters with the Ajax Search Page

by ishaih 2/16/2008 12:20:23 AM

This is a small upgrade to the DoSearch method in the data access layer.
Instead of using the FieldType property for each parameter, I'm using db.DiscoverParameters, and instead of adding new parameters I'm setting the values of the discovered parameters.

I didn't want to use a discovery but after starting to use the Enterprise Library's Data block, I decided to give it a try.
As you can see it's very easy to use, just one line of code, and the discovery results are supposed to be cached, so the performance hit should be too hard.

Here's the updated code:

/// <summary>
/// Performs a search
/// </summary>
/// <param name="SPName">the name of the Stored Procedure to execute</param>
/// <param name="DBName">the name of the DataBase where the stored procedure is (if not the default database)</param>
/// <param name="searchParams">The search parameters including page size</param>
/// <returns>a dataset of the search results</returns>
public DataSet DoSearch(string SPName, string DBName, IshaiHachlili.MyTakeOnDotNet.Entities.SearchParameters searchParams)
{
    Database db = DatabaseFactory.CreateDatabase(DBName);
    DbCommand cmd = db.GetStoredProcCommand(SPName);
    db.DiscoverParameters(cmd);
    
    //Add form input search parameters for this search query
    foreach (QueryParameter fld in searchParams.Parameters)
    {
        //db.AddInParameter(cmd, fld.FieldName, fld.FieldType, fld.FieldValue);
        cmd.Parameters['@' + fld.FieldName].Value = fld.FieldValue;
    }

    //Add common parameters (all search stored procedures should have this parameters and support paging and sorting functionality)
    cmd.Parameters["@PageIndex"].Value = searchParams.PageIndex;
    cmd.Parameters["@PageSize"].Value = searchParams.PageSize;
    cmd.Parameters["@SortColumn"].Value = searchParams.SortColumn;
    cmd.Parameters["@SortOrder"].Value = searchParams.SortOrder;

    DataSet ds = db.ExecuteDataSet(cmd);

    return ds;
}
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Enterprise Library

How To: Use connections not saved in web.config with the Data Application Block

by ishaih 2/15/2008 6:18:00 PM
While working on the search pages solution (read about it here) I realized I need to be able to save data connections in a database.

I’m using a database to manage everything that has to do with the application itself (membership, roles, search page info, etc…) but the actual data comes from other databases.

The Data Application Block supports using different connection as long as these connections are defined in the web.config file.
Because I didn’t want to make changes to the config file too often, and I also wanted users to be able to add new data connections without my help, having these connections in a database will be great.

After looking through the data block’s code, I realized I need to create a new configuration source.
I figured someone has already done this, and sure enough, a quick google search later I found out that such a solution exists right there in the enterprise library’s Quick Starts folder.

I quickly added the SqlConfiguration project to my solution and thought my problems were solved.
Unfortunately, it just didn’t work.
I also didn’t really want to save the entire connections section as on row in a table, that will make it harder to maintain. So I decided to create an implementation just for the connections section and inherit the rest for the SqlConfiguration.

The DataConnections table:
ConnectionId
ConnectionName
ConnectionString
ProviderName

you can see the columns are the same as the connection attributes in web config.


The Configuration Source:

namespace IshaiHachlili.RapidBackOffice.Configuration.ConnectionsSqlConfigurationSource
{
    public class ConnectionsSqlConfigurationSource : IConfigurationSource
    {
        private string defaultConnectionString = String.Empty;
        private const string GetConfig = "EntLib_GetConfig";
        private const string SetConfig = "EntLib_SetConfig";
        private const string RefreshSection = "EntLib_UpdateSectionDate";
        private const string RemoveSection = "EntLib_RemoveSection";

        public ConnectionsSqlConfigurationSource()
        {
            if (ConfigurationManager.ConnectionStrings.Count > 0)
            {
                defaultConnectionString = ConfigurationManager.ConnectionStrings[0].ConnectionString;
            }
        }

        public System.Configuration.ConfigurationSection GetSection(string sectionName)
        {
            if (sectionName != "connectionStrings")
            {
                IConfigurationSource source = new SqlConfigurationSource(defaultConnectionString, GetConfig, SetConfig, RefreshSection, RemoveSection);
                return source.GetSection(sectionName);
            } 
            ConnectionStringsSection section = new ConnectionStringsSection();

            //System.Configuration.ConnectionStringsSection
            //Get the connections from the DL
            ConnectionsDL dl = new ConnectionsDL();
            IDataReader dr = dl.GetDataConnections();
            while (dr.Read())
            {
                string connectionName = dr["ConnectionName"].ToString();
                string connectionString = dr["ConnectionString"].ToString();
                string providerName = dr["ProviderName"].ToString();
                section.ConnectionStrings.Add(new ConnectionStringSettings(connectionName, connectionString, providerName));
            }

            return section;
        }
    }

    public class ConnectionsDL
    {
        public IDataReader GetDataConnections()
        {
            Database db = DatabaseFactory.CreateDatabase();
            DbCommand cmd = db.GetStoredProcCommand("GetDataConnections");
            IDataReader dr = db.ExecuteReader(cmd);
            return dr;
        }
    }
}


The GetDataConnections stored procedure simply returns all the connections in the DataConnections table.
Because I'm using the CreateDatabase() method with no DB name, the default database will be used. This is the management database for my application and the only connection that will be saved in the web.config.


Using the new configuration source in the data layer

protected Database getDatabase(string DBName)
{
    if (String.IsNullOrEmpty(DBName))
        return DatabaseFactory.CreateDatabase();

    IConfigurationSource source = new ConnectionsSqlConfigurationSource();
    DatabaseProviderFactory factory = new DatabaseProviderFactory(source);
    Database db = factory.Create(DBName);
    return db;
}


I added this method to a base class that all the classes in my data layer inherit from. now instead of using the DatabaseFactory.CreateDatabase() I use getDatabase(DBName).
As you can see, when there's no DBName, I still use the DatabaeFactory method, there's no reason to get the connections from the database when using the default connection that's saved in the web.config.
You can also add caching to avoid calling the database everytime you need to get the connections list.
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

Asp.Net | Enterprise Library | Sql Server

My take on implementing searches with AJAX (Part 2)

by ishaih 2/7/2008 12:36:00 PM
This series of posts will show you my way of implementing search pages using .Net and AJAX.

In Part 1, I created client side functions that collect the form values regardless of the actual contents of the form (allowing adding and changing the form without having to update any code)
by the end of that post we got the search parameters to the server side and now we need to implement the search itself.

We already have a web service with a DoSearch method, now we need to create the business logic and data access layers.
I prefer creating separate projects for each layer but you can place these classes in the same project if you like.

Let's start with the BL layer.
I'm going to create a DoSearch method that accepts the same SearchParameters object we received from the client side in the web service.

Because I'm going to use the same methods to execute all searches in the application I need to know which procedure to use. The QueryName property of SearchParameters can be used for that.
You can simply send the name of the stored procedure if you're using one database and you're not worried about the sp name being known.
For these two reasons I decided to send a name that I will use to get the actual query details.

I'm keeping the query details for searches in my application in a table called DataQueries

Table DataQueries
QueryId
QueryName
DBName - the name of the database to use for this query
SPName - the name of the stored procedure to execute

In the actual application I also had some other properties used for permissions testing and application management, so this solution gives you a lot more flexibility

Because the same procedure will be executed many times (different search parameters, paging, or different users) I want to get the query parameters only once and cache them.

The business layer DoSearch method:
public DataSet DoSearch (IshaiHachlili.RapidBackOffice.Entities.SearchParameters searchParams)
{
//Get the query details from the cached queries
QueriesBL.QueryDetails qd = QueriesBL.GetQueryByName(searchParams.QueryName);
SearchesDL dl = new SearchesDL();
DataSet ds = dl.DoSearch(qd.SPName, qd.DBName, searchParams);
return ds;
}

QueryDetails represents a single row of the DataQueries table. The first time a search is run I get a dataset with all the queries, convert it to a Dictionary with the QueryName and QueryDetails class and save the dictionary into the cache. (The code for this will be included in the download with the last post, but it's pretty basic)


Not much happening in the BL layer right now, but we'll get back to it later, for now, let's skip to the DAL.

Executing the search
I'm using the Enterprise Library's Data Application block for data access, so all I have to do is create a Database object using the passed DBName and execute the stored procedure using the SPName and searchParameters.


public DataSet DoSearch(string SPName, string DBName, IshaiHachlili.RapidBackOffice.Entities.SearchParameters searchParams)
{
Database db = DatabaseFactory.CreateDatabase(DBName);
DbCommand cmd = db.GetStoredProcCommand(SPName);

//Add form input search parameters for this search query

foreach (QueryParameter fld in searchParams.Parameters)
{
db.AddInParameter(cmd, fld.FieldName, fld.FieldType, fld.FieldValue);
}

//Add common parameters (all search stored procedures should have this parameters and support paging and sorting functionality)
db.AddInParameter(cmd, "PageIndex", DbType.Int32, searchParams.PageIndex);
db.AddInParameter(cmd, "PageSize", DbType.Int32, searchParams.PageSize);
db.AddInParameter(cmd, "SortColumn", DbType.String, searchParams.SortColumn);
db.AddInParameter(cmd, "SortOrder", DbType.String, searchParams.SortOrder);

DataSet ds = db.ExecuteDataSet(cmd);

return ds;
}


Passing the DBName to CreateDatabase allows using different connections for each search, so you can use different databases and different servers.
Currently the Data Application block supports Sql Server and Oracle, but I've only tested this code with Sql Server.
Also, the connection name refers to the connections saved in Web.Config.

You can also see that I'm using the FieldType property for each QueryParameter when I'm adding parameters to the command.
There are ways to "discover" the expected data type but it was much easier to simply add a property to the HTML form elements.

Stored Procedures
All search stored procedures should accept the specific search parameters, and the paging and sorting parameters and return a table with the results and another table with the total row count (one row and one column)
We need the number of rows so we can display the pager in the client side.

The best way to do this is with dynamic sql inside the stored procedure.
I don't really like using dynamic sql anywhere, I think it complicates maintenance and could cause problems that you will only find out when running the actual code and not when compiling, but after trying other solutions with temporary tables and other methods, this was by far the most efficient.


Business Logic Layer - Proccessing the results
In order to support formatting and links in the search results grid, I had to save some properties for each column.
These properties include some styling properties (width, position, alignement), formatting properties, action properties and permissions.
Some of these should be processed on the server side. for example, if a user doesn't have the permission to see a column, I want to remove it from the results before sending it back to the client side instead of just hiding it in the browser.
I also want to send the column definitions back to the client side where I can do the rest of the processing when the grid is created.

The ColumnCollectionName property is used just for that, it's a reference to a collection of column properties saved in the database.
I could've saved this property in the DataQueries table, but separating the column collection and the query used for a search page allows using the same data query in different pages that require different result columns or formatting.

The business layer DoSearch method - updated to support column processing:
public DataSet DoSearch (IshaiHachlili.RapidBackOffice.Entities.SearchParameters searchParams)
{
//Get the query details from the cached queries
QueriesBL.QueryDetails qd = QueriesBL.GetQueryByName(searchParams.QueryName);

SearchesDL
dl = new SearchesDL();
DataSet ds = dl.DoSearch(qd.SPName, qd.DBName, searchParams);

DataSet
processedDS = UtilitiesBL.ProcessDataTable(ds.Tables[0], searchParams.ColumnCollectionName);
processedDS.Tables.Add("TotalRows");
processedDS.Tables["TotalRows"].Merge(ds.Tables[1], true, MissingSchemaAction.AddWithKey);

return
processedDS;
}


When ProcessDataTable is done with the server side processing, the DataTable holding all the column definitions is merged into the returned dataset.
A third table is added called TotalRows. this table will hold the total row count.


The next posts will explain the server side processing in more details, returning the resutls to the client side and displaying the grid.

Files:
AjaxSearchSamplePart2.zip (849.73 kb)

DataBasesPart2.zip (31.4 MB) (The AjaxSearchSQL and AdventureWorks databases)
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkList

Powered by BlogEngine.NET 1.3.1.0
Theme by Mads Kristensen

About the author

Name of author Ishai Hachlili
I've been developing web applications using Microsoft technologies for over 10 years. This is my way of doing things, it might be a little different...

E-mail me Send mail

Calendar

<<  July 2010  >>
MoTuWeThFrSaSu
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar

Pages

    Recent comments

    Authors

    Disclaimer

    The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

    © Copyright 2010

    Sign in