Click here to Skip to main content
Email Password   helpLost your password?

Introduction

I have been using CodeProject to further my knowledge of .NET for a few years now, and it is way past due that I give back! This is my first article. I had plans to write a number of articles in the past and have been beaten to the punch by many excellent articles on this site. While a data access framework is definitely nothing new, I think my oSo.SimpleSql (pronounced Oh So Simple SQL) framework has a couple of additions that the others don't. I make extensive use of several C# 2.0 and new 3.5 features such as Generics, Lambda Expressions, Extension methods as well as some useful patterns such as the Builder pattern and Fluent Interfaces. Hopefully, someone out in CodeProject-land finds this code or some of the associated concepts useful in their own projects.

All Data Access Frameworks have one thing in common that they are all trying to solve. They are all trying to make it as easy and painless as possible to get data out of and back into a data store which 99% of the time is a database. Many frameworks also try and hide the idiosyncrasies of each database behind a layer of abstraction that, in theory, allows you to easily swap out your data store for another (like going from SQL Server to MySQL, for instance). This abstraction is oftentimes used to additionally convert SQL results into objects that represent your business domain. I try to cover both easy data access and simple object relational mapping with SimpleSql.

Background

Why did I create SimpleSql? There are a myriad of frameworks like Microsoft's DAAB, LINQ to SQL, NHibernate, and the rising star SubSonic on the market now. Is there really a need for yet another one? I had been using an in-house data access framework/layer for a while that really only worked with SQL Server. I had planned to just update the framework to be a little more database agnostic and more closely mirror the types of projects I worked on. I seem to work on a lot of projects these days with Database Administrators that have already created a lot of the database structure. For security reasons, they like to make strict use of Stored Procedures and locked down views to access data. I wanted something that could integrate easily with existing code and database structures while having a super short learning curve for new developers coming on to any project using this framework. I started making a few changes here and there until it turned into a full scale rewrite. Hence, SimpleSql was born.

But, what about those other frameworks, you say? Well, I ruled out LINQ to SQL pretty quickly for several reasons. LINQ to SQL should've really been called LINQ to SQL Server since out of the box it only accesses one database. Also, Microsoft seems to be dropping support for it in favor of the next new Redmond technology, the Entity Framework. NHibernate is a really powerful ORM framework that can do pretty much anything. That's its biggest pro and con. Since it is so powerful, it has a bit of a learning curve. I wanted a data access framework that would allow developers who've never seen the framework to get up to speed very quickly.

SubSonic is the shiny new Object Relational Mapper (ORM) on the block. It is much simpler to use than many of the other ORM tools out there. But, like the other ORMs, it tries to emulate SQL through code while begrudgingly supporting Stored Procedures. SubSonic also works best if you let it control your database schema and associated classes. For new projects, this may be a good fit. But, if you have a lot of existing structure in place already and/or DB administrators that dictate a particular database schema and security, SubSonic won't be as good a fit. SubSonic also adds methods to your Plain Old C# Objects (POCOs) like "Save" to give them the ability to know how to save themselves to the database. I'm not a fan of this pattern. It makes it hard to use these same objects in different contexts and domains.

But, I don't want to sound too dismissive of SubSonic or any of the other frameworks. For instance, I do think SubSonic is very cool and has a ton of work put into it by some very smart guys. I plan to mimic much of its Visual Studio auto-generating code slickness for use in auto-generating my SimpleSql code! In the end, it was just about having a fun project to improve my .NET 3.5 skills!

A Few SimpleSql Benefits

Kudos

I have a number of people to thank on CodeProject for speeding up the development of my new data access framework. To cut out making so many initial Reflection calls, Herbrandson's Dynamic Code Generation article came in handy. I modified his code to make use of Generics everywhere it made sense. I also used a number of helper methods from Andrew Chan's High Performance Reflection ORM article, and modified them to meet the needs of my project. Some of the concepts in the sklivvz's SixPack CodeProject article also helped reinforce some of my design decisions. See the References at the bottom of the page for the links to all these articles.

Enough Talk, Where's the Code?

Basic Usage

You'll see a lot of frameworks with the method name "StoredProcedure" and I don't veer from that precedent :). There really isn't a better word out there to use.

using oSo

List<Employee> employees = SimpleSql
                            .StoredProcedure("GetAllEmployees")
                               .FetchAll<Employee>();

Feel the ORM!

This will call a Stored Procedure with no parameters, and will automagically fill in the public properties of each employee object for each record returned from the database. SimpleSql's Object Relational Mapping (ORM) capabilities make it a breeze to return objects and lists filled with database data. We'll look at more complex scenarios later. You can also make this same call using an inline query like so:

using oSo

List<Employee> employees = SimpleSql
                            .Query("Select * From Employees")
                               .FetchAll<Employee>();

StoredProcedure calls wouldn't be any good without the ability to specify parameters. Here's one with a couple of input parameters:

using oSo

Employee emp = SimpleSql
                .StoredProcedure("pGetEmployee")
                    .AddParameter("@EmployeeID", 1)
                    .AddParameter("@EmployeeLastName", "Smith")
                        .Fetch<Employee>();

*Note: If no records are returned from a single Fetch<T>() call, a null is returned. If no records return from a FetchAll<T> call, an empty list with a count of zero is returned.

Save Me!

You can use the AddParameter methods like I did above to save and retrieve data with any database. But, if you're using SQL Server, it allows SimpleSql to discover the parameters that will be used in a Stored Procedure. This allows you to save your objects in a very simple and ORMish way. Saves like inserts and updates that don't require anything to be returned are handled using one of the Execute method overloads.

using oSo
//Assume that we already got an Employee object named emp from somewhere
SimpleSql.StoredProcedure("pInsertEmployee").Execute(emp);

That Object Doesn't Quite Relate

SimpleSql fills an object by matching the name of the DbDataReader field with a public property in your object. If the names match, the property's set with the database data. But, what happens when you have an existing object with a property that doesn't match the DbDataReader field name? SimpleSql gives you an easy way to handle this via the Column attribute.

using oSo.Attributes;

public class Employee{
  
  public String EmployeeFirstName { get; set; }
  
  [Column("EmployeeAge")]
  public Int32 Age { get; set; }  
  
}

Now, when SimpleSql fetches records, it will match the DbDataReader field with EmployeeAge to fill in the Age property. Since EmployeeFirstName doesn't have a Column attribute, it will use that name to match the database field. If the property name doesn't match and no Column attribute is specified, SimpleSql will simply ignore that property.

Fluent Interfaces, Builder Patterns, and Code, Oh My!

Fluent Interfaces is a term coined by Eric Evans and Martin Fowler. If you've worked with .NET for any amount of time, no doubt you have done something like this several times in your code:

//Contrived example for demonstration purposes
String myString = "A SENTENCE I WANT TO LOWERCASE THAT ALSO CONTAINS AN ' APOSTROPHE.";
myString = myString.Replace("'", "").ToLower().Replace("a ", "A ");

The .NET String class uses the Builder pattern to make it possible to keep chaining method calls that return String. Fluent interfaces go one step beyond this by trying to make the chain of method calls more akin to a Domain Specific Language and read more like a sentence. Fluent Interfaces are harder to construct though, and require a bit of thought to get the methods names right. Here's the QueryBuilder class. It is one of the core classes in SimpleSql.

namespace oSo
{
    /// <summary>
    /// The QueryBuilder class uses the Builder Pattern
    /// with the StoredProcedure and Query Objects.
    /// This is what makes the fluent interface in SimpleSql possible.
    /// </summary>
    /// <typeparam name="T">Any class that inherits
    /// from the BaseQuery class</typeparam>
    /// 
    public class QueryBuilder<T> where T : BaseQuery
    {
        protected internal T query = default(T);

        /// <summary>
        /// Public constructor that sets the query object
        /// used (either StoredProcedure or Query)
        /// </summary>
        /// <param name="obj">The BaseQuery
        ///   or child of BaseQuery object being stored</param>
        /// 
        public QueryBuilder(T obj)
        {
            query = obj;
        }

        /// <summary>
        /// This method is used to map the fields of an object
        ///         to the fields in a DbDataReader in Fetches and FetchAlls
        /// </summary>
        /// <param name="objectRecordMapper">The Func delegate to execute</param>
        /// <returns>this</returns>
        /// 
        public QueryBuilder<T> Map(Func<DbDataReader, object> objectRecordMapper)
        {
            query.Map(objectRecordMapper);
            return this;
        }


        /// <summary>
        /// Set the time the query will wait for the command to complete
        /// </summary>
        /// <param name="timeout">time to wait</param>
        /// <returns>this</returns>
        /// 
        public QueryBuilder<T> CommandTimeout(int timeout)
        {
            query.CommandTimeout = timeout;
            return this;
        }

        /// <summary>
        /// Use this in an existing transaction. Ideally for use
        /// with the SimpleSql.CreateTransaction() method
        /// </summary>
        /// <param name="transaction">the
        ///   existing transaction to use</param>
        /// <returns>this</returns>
        /// 
        public QueryBuilder<T> WithTransaction(DbTransaction transaction)
        {
            query.WithTransaction(transaction);
            return this;
        }

        #region Action Endpoints

        /// <summary>
        /// This will fetch a single record from the DB and map it to a concrete object
        /// </summary>
        /// <typeparam name="O">The type of object
        ///           to map this query to</typeparam>
        /// <returns>The object specified by the type parameter</returns>
        /// 
        public O Fetch<O>()
        {
            return query.Fetch<O>();
        }

        /// <summary>
        /// This will map each record in the DB resultset to a concrete object
        /// </summary>
        /// <typeparam name="O">The type of object
        ///            to map this query to</typeparam>
        /// <returns>A list of strongly type objects specified
        ///            by the type parameter</returns>
        /// 
        public List<O> FetchAll<O>()
        {
            return query.FetchAll<O>();
        }

        /// <summary>
        /// Executes a query where you don't expect any results back
        /// </summary>
        /// 
        public void Execute()
        {
            query.Execute();
        }

        /// <summary>
        /// Executes a query that returns a single value
        /// </summary>
        /// <typeparam name="O">The type of value
        ///           that you want returned</typeparam>
        /// <returns>The type specified by the type parameter</returns>
        /// 
        public O ExecuteScalar<O>()
        {
            return query.ExecuteScalar<O>();
        }

        /// <summary>
        /// Fetches multiple resultSets
        /// </summary>
        /// <returns>A MultiResult object containing the collection of results</returns>
        /// 
        public MultiResult FetchMultiple()
        {
            return query.FetchMultiple();
        }

        /// <summary>
        /// Gives you access to the underlying DbDataReader
        /// to handle the records the way you want
        /// </summary>
        /// <returns>DbDataReader object</returns>
        /// 
        public DbDataReader FetchReader()
        {
            return query.FetchReader();
        }

        /// <summary>
        /// Fetches a DataSet
        /// </summary>
        /// <returns>returns a DataSet</returns>
        /// 
        public DataSet FetchDataSet()
        {
            return query.FetchDataSet();
        }
        
        #endregion

    }
}

There are a few things going on in this code, so let's take a closer look at QueryBuilder. The two main classes used in SimpleSql, namely StoredProcedure and Query, inherit from a base class called BaseQuery.

//Code snippet from QueryBuilder class
public class QueryBuilder<T> where T : BaseQuery
{
    protected internal T query = default(T);

    /// <summary>
    /// Public constructor that sets the query object
    /// used (either StoredProcedure or Query)
    /// </summary>
    /// <param name="obj">The BaseQuery or child
    /// of BaseQuery object being stored</param>
    /// 
    public QueryBuilder(T obj)
    {
        query = obj;
    }

I am using Generics here to make sure that the class that QueryBuilder uses internally can be of any type as long as it inherits from the BaseQuery class at some point in its inheritance chain. The BaseQuery class does most of the heavy lifting for both the StoredProcedure and Query classes since most of the methods they use are common to both of them.

public QueryBuilder<T> CommandTimeout(int timeout)
{
    query.CommandTimeout = timeout;
    return this;
}

Notice the methods like CommandTimeout. After it sets the query object's CommandTimeout property, it returns itself. The "return this" is what allows us to chain other QueryBuilder methods together.

public O Fetch<O>()
{
    return query.Fetch<O>();
}

You saw the Fetch() method used in examples earlier. Methods like Fetch() end the Builder pattern. I call them Action End Points (AEP) since these methods are either void or return a result from the database. All the AEP methods make use of classes from the System.Data.Common namespace to retrieve or persist data. This, theoretically, makes it possible to switch out the entire database by simply changing the provider name in the .config file!

But Where is the ConnectionString?

Since no connection string information was specified in any of the previous code samples, SimpleSql used the first connection string specified in the .config file. You can also pass the connection string by the name specified in the .config file, or explicitly pass all the connection string information.

using oSo
   
int employeeAge = SimpleSql
                    .StoredProcedure("pGetEmployeeAge", 
                                     "WebConfigConnectionName")
                        .AddParameter("@EmployeeID", 2)
                            .ExecuteScalar<int>();

This example returns a single value from a database strongly typed using Generics. No casting needed! You pass connection string parameters as a parameter of the StoredProcedure or Query constructor. If your connection string is not located in the .config file, you can pass the information in directly as well.

using oSo

string connString = "Data Source=.\SQLExpress;Initial Catalog=" + 
                    "SimpleDb;User Id=SimpleUser;Password=SimplePassword;";
string provider = "System.Data.SqlClient";

int employeeAge = SimpleSql
                    .Query("Select EmployeeAge From dbo.Employee Where EmployeeID = 2", 
                           connString, provider)
                        .ExecuteScalar<int>();

Can I Get an Extension?

I wanted all methods to go through the QueryBuilder class so I could chain method calls. But, some methods like "AddOutParameter" which adds an Output parameter are only specific to StoredProcedures and shouldn't show up in Intellisense when chaining method calls for Query. Extension methods to the rescue! Click here for more information about this great C# 3.5 feature.

public static class QueryBuilderExtensionMethods
{
    public static QueryBuilder<StoredProcedure>
       AddOutParameter(this QueryBuilder<StoredProcedure> qb, 
       string parameterName, object value)
    {
        ((StoredProcedure)qb.query).AddOutputParameter(parameterName, value);
        return qb;
    }
    
    /*
    Additional methods not shown......
    */
}

The QueryBuilderExtensionMethods class allows me to define QueryBuilder methods that only show up in the Intellisense dropdown when I'm using the StoredProcedure class! Notice that QueryBuilder is typed directly to StoredProcedure and the internal Query object is cast to StoredProcedure to ensure that the Query object cannot see this method. The object "qb" is returned and is the same as returning "this" in the QueryBuilder class.

You're Injecting Me With What?

Try to mitigate the possibility of succumbing to SQL Injection Attacks when using inline queries. If the values that are part of your SQL query are coming from untrusted sources (i.e. the user via a form, a webservice, etc..), you should use SQL parameters to prevent one of the values from compromising your database. See the example below:

using oSo
using System.Data;

Employee emp = SimpleSql
                .Query("Select EmployeeFirstName, EmployeeAddress 
			From Employee Where EmployeeID = 
			@EmployeeID AND EmployeeLastName = @TheLastName")
                    .AddParameter("@EmployeeID", 1, DbType.Int32)
                    .AddParameter("@EmployeeLastName", "Goodwrench", DbType.String, 50)
                        .Fetch<Employee>();

More Complex ORM Scenarios

SimpleSql can automagically sets the values of public properties that are strings or simple value types. But, in the real world, we have objects that contain properties that can contain other objects among other things. To keep SimpleSql "simple", SimpleSql handles all complex ORM scenarios through its Map method. Here are a couple of simple domain POCO objects:

//ComplexEmployee Class makes use of the Address class
public class ComplexEmployee{

    public Int32 EmployeeID { get; set; }
    public String EmployeeFirstName { get; set; }
    public String EmployeeLastName { get; set; }
    //A more complex property
    public Address EmployerAddress { get; set; }
}

//Holds all the address details
public class Address{
    
    public String Address1 { get; set; }
    public String City { get; set; }
    public String State { get; set; }
    public String Zip { get; set; }
}

Pledging Lambda, Lambda, Lambda

Now, let's hydrate the ComplexEmployee object using SimpleSql.

using oSo

ComplexEmployee ce = 
  SimpleSql
   .StoredProcedure("pGetEmployee")
     .AddParameter("@EmployeeID", 1)
        .Map(reader => {
            return new ComplexEmployee()
            {
                EmployeeID = Convert.ToInt32(reader["EmployeeID"]),
                EmployeeFirstName = Convert.ToString(reader["EmployeeFirstName"]),
                EmployeeLastName = Convert.ToString(reader["EmployeeLastName"]),
                EmployerAddress = new Address()
                {
                    Address1 = Convert.ToString(reader["EmployeeAddress"]),
                    City = Convert.ToString(reader["EmployeeCity"]),
                    State = Convert.ToString(reader["EmployeeState"]),
                    Zip = Convert.ToString(reader["EmployeeZip"])
                }
            };
        }).Fetch<ComplexEmployee>();

As you saw earlier in the QueryBuilder code, the Map() method takes a Func Delegate as a parameter. This allows us to use a Lambda Expression to define how the database data maps to the ComplexEmployee object. Also, notice that I made use of other .NET 3.x goodies like Automatic Properties and Object Initialization.

Dealing with Multiple Resultsets and Output Parameters

You may not make use of these two features too often. But, I wanted these features to be easily handled and strongly typed in SimpleSql for the times that you do need to use them. Let's first look at handling multiple resultsets:

using oSo;
using oSo.Results;

MultiResult results = 
  SimpleSql
    .StoredProcedure("pFetchMultipleEmployeeResults")
          .AddParameter("@EmployeeID", 1)
            .Map(reader => {
                return new Employee(){
                     FirstName = reader["EmployeeFirstName"].ToString()
                     , LastName = reader["EmployeeLastName"].ToString()
                };
            }).Map(reader => {  
                string phoneNum = (Convert.IsDBNull(reader["EmployeePhone"])) ?
                                      "Unlisted" : 
                                      Convert.ToString(reader["EmployeePhone"]);
                return new ComplexEmployee()
                    {
                        EmployeeFirstName = reader["EmployeeFirstName"].ToString()
                         , EmployeeLastName = reader["EmployeeLastName"].ToString()
                         EmployerAddress = new Address()
                        {
                            Address1 = Convert.ToString(reader["EmployeeAddress"]),
                            City = Convert.ToString(reader["EmployeeCity"]),
                            Phone = phoneNum
                        }
                    };
            }).FetchMultiple();

When you call a Stored Procedure or query that returns multiple resultsets, the results come back in a MultiResult object. Each Map() method deals with one resultset. Use multiple Map() methods to handle multiple resultsets. Notice I didn't name this class something like ResultsCollection. I wanted to make sure people didn't get in the mindset that they should use a foreach to loop through the results. Each result contained in the MultiResult object should be retrieved in the order they were mapped, starting with zero.

Each Result in a MultiResult is Strongly Typed

//From Code above
MultiResult results = SimpleSql.......

//Get first resultset
Employee emp = results.Fetch<Employee>(0);

//Get second resultset
List<ComplexEmployee> cplxEmps = 
            results.FetchAll<ComplexEmployee>(1);

It's All About the Output

SimpleSql handles output parameters through an "out" parameter on the Fetch method. Many of the other AEP methods have an out parameter overload as well.

//Declare a collection that will be filled with any output parameters
OutputParameterCollection opColl = null;
Employee emp = SimpleSql
                .StoredProcedure("GetOneEmployeeWithOutput")
                    .AddParameter("@EmployeeID", 1)
                        .AddOutParameter("@DateHiredOutputParam", DbType.String)
                            .Fetch<Employee>(out opColl);
                            
//I can call the output parameter in the collection by Name
DateTime dateHired = opColl.GetValue("@DateHiredOutputParam").Fetch<DateTime>();

//Or by Index
//DateTime dateHired = opColl.GetValue(0).Fetch<DateTime>();

I Need You to Commit or I'll Have to Roll on Back

Although I'm not a real fan of handling transactions through code, I know this is unavoidably necessary from time to time. SimpleSql can use .NET 2.0's TransactionScope, or it can create and use regular transactions.

using (SimpleTransaction st = SimpleSql.CreateTransaction())
{
    try
    {
        SimpleSql.StoredProcedure("pInsertEmployee")
                                 .AddParameter("@EmployeeFirstName", "RollBack")
                                 .AddParameter("@EmployeeLastName", "Candidate")
                                 .AddParameter("@EmployeeAddress", "1 Rollback Lane")
                                 .AddParameter("@EmployeeCity", "Los Angeles")
                                 .AddParameter("@EmployeeState", "CA")
                                 .AddParameter("@EmployeeZip", "90245")
                                    .WithTransaction(st.Transaction)
                                        .Execute();

        SimpleSql.StoredProcedure("pInsertEmployee")
                         .AddParameter("@EmployeeFirstName", "Thrown")
                         .AddParameter("@EmployeeLastName", "Out")
                         .AddParameter("@EmployeeAddress", "2 Left Back Way")
                         .AddParameter("@EmployeeCity", "Los Angeles")
                         .AddParameter("@EmployeeState", "CA")
                         .AddParameter("@EmployeeZip", "90047")
                            .WithTransaction(st.Transaction)
                                .Execute();
        st.Commit();
    }
    catch (SimpleSqlException sse)
    {
        st.Rollback();
        Debug.WriteLine(sse.Message);
    }
}

Instead of having to go back to ADO.NET to create a transaction, SimpleSql introduces the SimpleTransaction class. This class makes use of the Dispose pattern to close out the connection after the transaction is done. The CreateTransaction method has several overloads to specify connection string information. Also, note that all StoreProcedures participating in the Transaction will share the same connection information.

Code De-Coupling and the Case of the Proper Data Access Layer

To enable clean code separation and enable Unit Testing (not to mention proper architecture), I have also created a Data Access Layer (DAL) that uses SimpleSql. This will isolate all database calls to the DAL so you won't have more junior team members trying to use SimpleSql in code-behinds or *gasp* in the .aspx page itself! The DAL also gives you the ability to specify the connection string once through the constructor and use it for every data access method. Setting the connection string via the constructor also allows you to set the connection using a Dependency Injection framework like StructureMap.

//Have your class inherit from SimpleDAL
public class EmployeeDAL : SimpleDAL
{
    /// <summary>
    /// You can explicitly pass in the name of the ConnectionString
    /// from the .config file via the constructor.
    /// This will allow you to use this connection with any method
    /// that uses the SimpleSql methods in this class.
    /// Another benefit is that you can now use a Dependency Injection
    //  framework like StructureMap or Spring.Net etc.
    /// to inject the name of the ConnectionString!
    /// </summary>
    /// <param name="connectConfigName">ConnectionString
    ///           name from the .config file</param>
    /// 
    public EmployeeDAL(string connectConfigName) 
                : base(connectConfigName)
    {
    }

    /// <summary>
    /// A Sample method that gets all employees from the DB
    /// </summary>
    /// <returns>List of employees</returns>
    /// 
    public List<Employee> GetEmployees()
    {
        List<Employee> employees = null;

        try
        {
            //SimpleSql is built into the DAL so you
            //can directly call Query or StoredProcedure
            employees = StoredProcedure("pGetAllEmployees")
                            .FetchAll<Employee>();
        }            
        catch (SimpleSqlException sse)
        {
            throw;
            //I'm just throwing it up the method chain. 
            //Do whatever you want here to handle the error.
        }
        
        return employees;
    }
}

Included in the Zip Download

There are three projects included with SimpleSql.

  1. The fully commented SimpleSql source code including the example SQL Server Express 2005 database used. I also generated the SQL scripts if you wish to install the database on another version of SQL Server.
  2. Unit Tests were done using MSTest so people not familiar with Unit Testing wouldn't have to download and install another tool besides VS 2008. But, I would recommend XUnit if you have a choice.
  3. Last, but not least, I have included a quick sample Web Forms project that shows how to use the SimpleSql DAL. I wanted to do an MVC project, but I didn't want to get people who haven't been exposed to ASP.NET MVC mired down in Views, Controllers, and Routes just to show how you get data from your database to your ASPX page via SimpleSql!

Using SimpleSql in Your Project

Just make a reference to oSo.dll, put the "using oSo" and "using oSo.Exceptions" statements at the top of your class file, and have a go at it!

Points of Interest

This was fun making use of a lot of C# 3.5 features in one project. While I know some people love the new var keyword, I have not been so enamored to have this littered about my code. But, among the other fun things in the code, I finally found what I consider to be a very good use for var!

When returning a List<T> as one of the results from a multiple resultset, I strongly type the return. But, before the result is strongly typed, it is either stored in an object for a single result or in an ArrayList for multiple results. When the user tells SimpleSql the actual type, it is then converted to the proper type. Before the var keyword, I would've to use code like this:

//C# 2.0 implementation of the MultiResult FetchAll method
List<T> resultset = new List<T>();
ArrayList results = resultList[index] as ArrayList;
//If this object isn't an ArrayList the value will be null
if (null != results){
    foreach (Object r in (ArrayList)results)
    {
        resultset.Add((T)r);
    }
    }else{
        //Assume its just one object, so add that one object into the list
        resultset.Add((T)results);
    }
    return resultset;

Now, with var and Extension Methods, the code is a bit more compact (if you remove the comments).

//C# 3.5 implementation of the MultiResult FetchAll method
List<T> resultset = new List<T>();
//The new var keyword uses type inference to figure out what the actual type is
var results = resultList[index];
if (results is ArrayList)
{
    //The Cast<T> Extension method save a few lines of looping code
    resultset.InsertRange(0, ((ArrayList)results).Cast<T>());
}
else
{
    resultset.Add((T)results);
}
return resultset;

References

History

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralMapping with list
Boer Coene
12:26 17 Feb '10  
Is is possible to map a list? For example: An order may have multiple details, so I would like to access it through order.details[0] (= which is a lists containing all details for this order).
GeneralRe: Mapping with list
Charles T II
16:14 19 Feb '10  
I've been so busy the last 6 months I haven't updated the article like I should. I have made the framework much more powerful and even easier to work with since this article. But, certain things still work similarly. To fill up a list you have a few choices in SimpleSQL. You can:

1. Create an object, then assign the result of a SimpleSql fetch to the property whose type is the list. Here's a quick and dirty example (assume we're inside some method):

MyCoolObject coolObj = new MyCoolObject();
coolObj.orderDetailList = SimpleSql.StoredProcedure("getResults").FetchAll();

//Do something with coolObj

2. You can create a stored procedure with multiple result sets. One of the result sets would be the list of order details, another may be order metadata like customer info, invoice number, etc. The first result would get the single parent order object. The second result would return a list of OrderDetail objects. SimpleSql is smart enough to know whether each Map method should create a single object or a list of objects. You just have to give it an example of how you want the object's properties filled when it gets result(s) back from the database. When calling a stored proc with multiple results you get back a MultiResult object. You can then call methods on the MultiResult object to create your complex Order object with the list of OrderDetails:

MultiResult results =
SimpleSql
.StoredProcedure("getOrderInfo")
.AddParameter("@InvoiceID", 1)
.Map(reader => {
return new Order(){
InvoiceNumber = Convert.ToInt32(reader["EmployeeFirstName"])
, CustomerName = reader["FullName"].ToString()
};
}).Map(reader => {
return new OrderDetail()
{
OrderID = Convert.ToInt64(reader["OrderID"])
, OrderDate = Convert.ToDateTime(reader["EmployeeLastName"])

};
}).FetchMultiple();

//Get first result
Order ord = results.Fetch(0);

//Get second resultset
List od = results.FetchAll(1);
ord.details = od;


Hope that helps!


Charles
GeneralUse with VB.net
Albo
10:46 12 Nov '09  
Of course .Net allows us to mix and match .Net languages. A simple example shows that I can call your framework from VB however exceptions that you throw did not see to display properly in VB Express. Are there any gotchas with VB that you know of?
GeneralRe: Use with VB.net
Charles T II
11:32 17 Nov '09  
Sorry for the late reply. Not sure why VB.Net would not show errors. You should be able to catch either a general exception or SimpleSqlException and get an the details of the error back. I have an updated version with new functionality like optionalParameters and auto-mapping for multi-results sets. It also includes some bug fixes. It may take a few days to update the article. I can send it to you before that if you don't want to wait.

Thanks,

Charles
GeneralUsing oSo.SimpleSql with .NET 2.0
Stefano Gallina
23:14 24 Sep '09  
Hi Charles,

I'd like to use your oSo.SimpleSql in a project of mine.
I have to develop an application with .NET 2.0 using WindowsForm....is it possible using oSo.SimpleSql with .NET 2.0 ?
Cheers.

Zoster
GeneralRe: Using oSo.SimpleSql with .NET 2.0
Charles T II
12:22 12 Oct '09  
Sorry for the late reply. Unfortunately, I take advantage of a number of C# 3.5 features in SimpleSQL so it requires the latest version of the .NET framework. Sorry.
Generalnull values for SqlCommand
Smuus
23:46 3 Sep '09  
Hi Charles,

I have another suggestion: Consider an edit form which returns null values, if you delete a value. Then the oSo Framework doesn't supply the corresponding parameter for the SqlCommand. The stored procedure for instance throws an exception "parameter not supplied...". So I changed the code to set the SqlCommand-Parameter to DbNull.Value if the value would be null. So the SqlCommand won't miss the parameter anymore.

Greets,
Thomas
GeneralRe: null values for SqlCommand
Charles T II
15:12 4 Sep '09  
Thanks Thomas. I have built DBNull handling into the framework. I have just been so busy I just got time to document it. I will put up the new version that will do this shortly. It handles null inserts as well as converting nulls coming out of the database.

Thanks,

Charles
GeneralRequest for Quote
PiyushVarma
18:52 1 Sep '09  
Hi Charles,

We want to invite you to give us your quote to convert our in house Membership Application developed in ASP, VB6, JavaScript, SQL Server 2000, and custom menu component into a modern .Net architecture.

Please let us know if you would be interested.

Thank you,

Piyush Varma
Phone: (925) 275-6674
GeneralRe: Request for Quote
Charles T II
9:22 8 Sep '09  
Sorry, I was out of town and didn't have your number on me. I will contact you later today.

Thanks,

Charles
QuestionCommandTimeout and StoredProc Error Code
Smuus
6:45 26 Aug '09  
Hi Charles,

i like this Framework very much, but there are two things I'd like to suggest/ask:

1. The default CommandTimeout is set to 15 seconds. In some cases this is too short. Although you can override the timeout value with Query(...).CommandTimeout(int), it would be more comfortable to be able to set the timeout in the .config file.

2. Stored procedures always have an output int parameter. With the oSo StoredProc(...).Execute Method you get nothing but a void. Is it possible to get to this parameter or do I have to implement an extra output parameter on the storedProc side?

Cheers,
Thomas
AnswerRe: CommandTimeout and StoredProc Error Code
Charles T II
8:18 26 Aug '09  
Good questions...see my answers below:

1. 15 seconds for the commandtimeout is too low. I was probably thinking about the connection timeout. It's easy to increase that in the source to 30 seconds. I will increase that to 30 seconds in the next revision. If I have time, I will make a global setting for commandTimeouts. I usually only need commandTimeout's increased on certain long running queries and not every query in my app though. That's why I didn't change the implementation from the standard ADO.NET way of setting the commandTimeout per query instead of globally.

2. Honestly, the only reason I didn't include the output parameter is because I never use it! The Execute method is designed to be more of a fire and forget it type operation. It is probably pretty trivial for me to add the functionality back in. In the meantime, you could get the value back using ExecuteScalar. I'm curious, what do you use the return int for?

Thanks,

Charles
GeneralRe: CommandTimeout and StoredProc Error Code
Smuus
6:40 27 Aug '09  
Hi Charles,

a simple use for the return int would be a stored proc which inserts a record and returns the generated ID of that record so you can work with it without calling an extra function. And you could say that it returned a negative value as an error code or something.

Cheers,
Thomas
GeneralRe: CommandTimeout and StoredProc Error Code
Charles T II
11:56 27 Aug '09  
Aaah. I figured as much. My ExecuteScalar method will give you this ability, but, I will add it to the Execute method.

Thanks,

Charles
GeneralHow to call a DB function instead of a stored proc ?
boutblock
7:47 19 Aug '09  
Let me answer to my own question now I know the answer.

As I had to search to find the solution as using StoredProcedure obviously does not work,
let me save you some time with an example:

                    var operatorID = SimpleSql.Query("SELECT dbo.GetOperatorID()")
.CommandTimeout(Properties.Settings.Default.DbCommandTimeout)
.ExecuteScalar<Int64>()

Maybe you could had it as a sample in your article too ?
GeneralRe: How to call a DB function instead of a stored proc ?
Charles T II
12:40 19 Aug '09  
This is Sql Server specific I take it? I've been extremely busy the past couple of weeks. I will add this to some docs I'm working on that I will posting up to my website in the near future. Thanks for the help!

Charles
GeneralAbout connections and transactions
boutblock
0:40 7 Aug '09  
Hi (again)

I'm looking at the way you manage the transactions using .WithTransaction().

It sounds to me like unconsistent with the way you primarily define the connection.

Either you define the connection as parameters of the constructor of .StoredProcedure()/.Query() and you do the same with the transaction, or you add a .UsingConnection() extension method consistent with .WithTransaction().

Otherwise you may have a side effect, like creating a query with a connection as a parameter that will be replaced by the connection you defined for the transaction which may be different.

Another thought, maybe the transaction should be automatically commited by default when exiting the using scope during the Dispose() call (if not rolled back within of course).
This would be more convinient to only have to use the rollback on errors and would avoid exiting transaction scope without commit or rollback calls.

Cheers Smile
GeneralRe: About connections and transactions
Charles T II
10:08 7 Aug '09  
Hi boutblock. I like the ideas and refactors you keep bringing to the table! Let me address the 2 issues you raise concerning method consistency and transaction commits. There is a method to my madness regarding putting the connection in the constructor (get it.. method to my madness when talking about methods..Ok, that was a real bad joke Smile ). The reason I don't have a .WithConnection or something of that sort, was really a judgement call. Transactions are not used as often as connection strings and aren't mandatory in a database call, so I wanted to show it being optional with the ".WithTransaction" method.

The query or stored proc name and connection strings are really more mandatory parts of setting up a query, so it made more sense to me to put in the constructor. The "SimpleSql" class is just a static wrapper around the classes StoredProcedure and Query which do all the heavy lifting. When you write .Query for instance you are calling the constructor of that class and I used the initializer to setup the 2 things every query has to have (a query or proc name and a connection string). The ability for the framework to grab the first connection string in the web.config was actually a late addition that was added just before I released the framework. So that's why the connection string starts to look more optional. It's still grabbed behind the scenes in the constructor as well if not specified. But, you are right, it could go either way, that was just my preference (and also didn't require any retesting for the late change Smile ).

I like the commit on dispose idea. I could commit the transaction on Dispose and either eat or handle errors thrown in Dispose. I'm not sure what the proper course of action would be with handling any exceptions from Commit when the Dispose method is called and how that exception if thrown in Dispose would have to be handled by the programmer. They would probably have to put a try/catch block around the using SimpleTransaction statement to catch the exception. Basically, I decided to go down the path of least resistance and make everything explicit. I may revisit this in the future though.

What are your thoughts?

Thanks,

Charles
GeneralRe: About connections and transactions
boutblock
23:52 11 Aug '09  
Now I see your point about the mandatory aspect of the connection string that could justify its presence in the ctor's parameters list.

About the Commit in Dispose call, you're also right, it was a false good idea.
The dispose call is later made by the GC and not done while getting out of the using statement.
Moreover, how to handle any exception on commit ? I was completly wrong !

I'll try to bite my tongue and think a bit further next time before posting, sorry Smile
GeneralRe: About connections and transactions
Charles T II
0:14 12 Aug '09  
Not a problem. It is always good to get people's input on code (aka the peer review). It's the way we improve. So, don't hesitate to comment on something you think isn't right or could be done better. I can take it Smile

Thanks,


Charles
GeneralFetchAll Exception [modified]
syn87
12:08 5 Aug '09  
Hi Charles,

i guess you're caching somewhere the wrong way. I've got 2 classes City and Street, both of them inherit from the class BusinessBase and both of them got the Properties ID and Name (they are NOT implemented in BusinessBase).

After calling SimpleSql.Query(q1).FetchAll() and then SimpleSql.Query(q2).FetchAll() i get the exception. It says that it cannot convert City to Street.

Why i guess it's a caching problem is because after i changed the Properties of Street from ID to ID2 and Name to Name2 all works fine.

Any idea?

Cheers

modified on Wednesday, August 5, 2009 5:14 PM

GeneralRe: FetchAll Exception
Charles T II
14:01 5 Aug '09  
Hmmm. I do make use of some caching of your objects for speed purposes. I will create 2 objects from the same base class with the same property names and try to make 2 query calls like you did. I will do this tonight and get back to you with the fix.

Thanks,

Charles
GeneralRe: FetchAll Exception
syn87
15:17 5 Aug '09  
Yepp! Thank you.

And thank you for this great lib too Smile It is very helpful if using business objects for databindings.

hope u can fix it.

bye
GeneralRe: FetchAll Exception
Charles T II
8:24 6 Aug '09  
Thanks. I will post the code snippet here later today. I had a busier work night than I expected.
GeneralRe: FetchAll Exception
Charles T II
8:57 7 Aug '09  
Sorry for the late reply. It has been a hectic week organizing this Saturday's conference I'm running on Palm Pre development. I created 2 classes with the same properties inheriting from the same base class and didn't receive an error. Could you give me more detail on what you did to generate the error (such as what the field types were, what was in the base class etc)?

Thanks,

Charles


Last Updated 20 Jun 2009 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010