|

Introduction
I recently became very tired of continuously rewriting the same code over and over again. In my case, it was code to access stored procedures in a SQL Server database. The end result of my frustration is the application presented here.
Background
A search on CodeProject located an article by leppie describing a DBHelper class that he had developed. After test driving the application, I was hooked. Unfortunately, the existing code base did not handle the return value from stored procedures nor did it handle output parameters from the stored procedures. The original article is available here.
I could have used the built in support in Visual Studio and had it generate SqlCommand classes for me. This approach however scatters the database access throughout the application and makes it extremely fragile in the face of changes.
My solution was to use the ideas that I had seen in leppie's article and extend them to handle both return values and output parameters.
Using the application
To use this application, all you have to do is point the application at an existing SQL Server, select the stored procedures that you want wrappers generated for, and hit the lightning bolt. This application and all source code (with the exception of the GetSQL class; for information on it, see this article) are completely free for whatever use you see fit.
Generated Source Code
The generated code provides a single static method on the class that you specify for each stored procedure. The method has the same name as the stored procedure. The sample below is the generated code for a single stored procedure. Using the generated source is as easy as passing in the connection object.
User Codeprivate void somefunc()
{
int iRet=0;
int iNewId=0;
iRet = heatgmsm_DAL.wl_AddUser(myConn,null,"User",
"password",false,false, false,true,ref iNewId);
}
Generated Codenamespace heatgmsm
{
using System.Data;
using System.Data.SqlClient;
public class heatgmsm_DAL
{
private heatgmsm_DAL()
{
}
public static int wl_AddUser(System.Data.SqlClient.SqlConnection connection,
System.Data.DataTable table, string uname, string pwd,
bool IsUserAdmin, bool IsProviderAdmin, bool IsWaitlistAdmin,
bool IsUser, ref int uid)
{
int RETURN_VALUE = 0;
System.Data.SqlClient.SqlCommand cmd = null;
System.Data.SqlClient.SqlDataReader reader = null;
if ((connection == null))
{
throw new System.ArgumentException("The connection object cannot be null");
}
else
{
if ((connection.State == System.Data.ConnectionState.Closed))
{
connection.Open();
cmd = new System.Data.SqlClient.SqlCommand("wl_AddUser",
connection);
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.Parameters.Add("@RETURN_VALUE",
System.Data.SqlDbType.Int, 0);
cmd.Parameters["@RETURN_VALUE"].Direction =
System.Data.ParameterDirection.ReturnValue;
cmd.Parameters["@RETURN_VALUE"].Value = RETURN_VALUE;
cmd.Parameters.Add("@uname", System.Data.SqlDbType.VarChar, 50);
cmd.Parameters["@uname"].Direction =
System.Data.ParameterDirection.Input;
cmd.Parameters["@uname"].Value = uname;
cmd.Parameters.Add("@pwd", System.Data.SqlDbType.VarChar, 50);
cmd.Parameters["@pwd"].Direction = System.Data.ParameterDirection.Input;
cmd.Parameters["@pwd"].Value = pwd;
cmd.Parameters.Add("@IsUserAdmin", System.Data.SqlDbType.Bit, 0);
cmd.Parameters["@IsUserAdmin"].Direction =
System.Data.ParameterDirection.Input;
cmd.Parameters["@IsUserAdmin"].Value = IsUserAdmin;
cmd.Parameters.Add("@IsProviderAdmin", System.Data.SqlDbType.Bit, 0);
cmd.Parameters["@IsProviderAdmin"].Direction =
System.Data.ParameterDirection.Input;
cmd.Parameters["@IsProviderAdmin"].Value = IsProviderAdmin;
cmd.Parameters.Add("@IsWaitlistAdmin", System.Data.SqlDbType.Bit, 0);
cmd.Parameters["@IsWaitlistAdmin"].Direction =
System.Data.ParameterDirection.Input;
cmd.Parameters["@IsWaitlistAdmin"].Value = IsWaitlistAdmin;
cmd.Parameters.Add("@IsUser", System.Data.SqlDbType.Bit, 0);
cmd.Parameters["@IsUser"].Direction =
System.Data.ParameterDirection.Input;
cmd.Parameters["@IsUser"].Value = IsUser;
cmd.Parameters.Add("@uid", System.Data.SqlDbType.Int, 0);
cmd.Parameters["@uid"].Direction =
System.Data.ParameterDirection.InputOutput;
cmd.Parameters["@uid"].Value = uid;
if ((table != null))
{
reader = cmd.ExecuteReader();
}
else
{
cmd.ExecuteNonQuery();
}
if (((table != null) && (reader != null)))
{
table.Clear();
table.Columns.Clear();
for (int i = 0; (i < reader.FieldCount); i = (i + 1))
{
System.Type __type;
string __name;
__type = reader.GetFieldType(i);
__name = reader.GetName(i);
table.Columns.Add(__name, __type);
}
for (; reader.Read();)
{
System.Data.DataRow row = table.NewRow();
object[] rowdata = new object[reader.FieldCount];
reader.GetValues(rowdata);
row.ItemArray = rowdata;
table.Rows.Add(row);
}
reader.Close();
}
uid = ((int)(cmd.Parameters["@uid"].Value));
connection.Close();
RETURN_VALUE = ((int)(cmd.Parameters["@RETURN_VALUE"].Value));
return RETURN_VALUE;
}
else
{
throw new System.ArgumentException("The connection" +
" must be closed when calling this method.");
}
}
}
}
}
As you can see, the amount of code generated, in other words code that you no longer have to write, is quite extensive.
Future Directions
Any and all comments, suggestions, and or feature requests are welcome :).
History
- July 1st 2004 -- Initial submission.
- July 1st 2004
- Updated to use the
GetSQL class from the excellent article by Micheal Potter. This allowed me to eliminate the use of my SQLUtils.dll helper library.
- Added a refresh option to the checked list box context menu.
- Fixed a bug in the application idle event handler that was causing CPU spikes after items had been selected in the checked list box.
- July 2nd 2004
- Fixed a bug pointed out by jobr1ch, namely incorrect method names where generated for stored procedures with spaces in the name. Spaces are now replaced with an underscore character. So, 'Sales By Year' becomes 'Sales_By_Year' instead. Thanks for pointing that out jobr1ch :).
- Sorted the stored procedures by name.
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 29 (Total in Forum: 29) (Refresh) | FirstPrevNext |
|
 |
|
|
Is it possible to use these classes with the objectdatasource? I can't seem to figure out how that would work with the datatable built into the class like that.
Rich http://www.devanddesign.com/
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
A while ago, I searched the internet for a code generator of stored procedures with simple feactures:
- No further modification to the generated code
- Synchronizing with procedure changes
- Directly interacting with data provider methods
- Flexible for effecient use of ADO.Net functionality
- Static data binding
There are a lot of such kind but I had no success.
Now, I have created one and if you are interested, take a look at http://www.povb.com/posharp
Chris Liang
|
| Sign In·View Thread·PermaLink | 1.33/5 (2 votes) |
|
|
|
 |
|
|
... or just use one of the many free tools like MyGeneration: www.mygenerationsoftware.com
Kind regards, thomas
People who wait until the eleventh hour to call on Jesus die at 10:30.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
First to say that your article was very very helpful, however the generated code sucks.
Just spend a couple of minutes thinking.
Take a look at the exceptions thrown. These 2 cases could be the first in the procedure:
if ((connection == null)) { throw new System.ArgumentException("The connection object cannot be null"); } else { if ((connection.State == System.Data.ConnectionState.Closed)) { ................. } else { throw new System.ArgumentException("The connection" + " must be closed when calling this method."); }
Instead of this you could have something way cleaner:
if ((connection == null)) { throw new System.ArgumentException("The connection object cannot be null"); }
if ((connection.State != System.Data.ConnectionState.Closed)) { throw new System.ArgumentException("The connection" + " must be closed when calling this method."); }
Spending a big more time on that it turns out that you will have this piece of IDENTICAL code for every single stored procedure in your data access layer. So you could make it a separate function and just call it. Also this should be done only duting testing and that's why you should be using ASSERT so they won't be compiled in the release.
Another bad part: Resource protection. After you open the database you should wrap the code before closing the database in a try-finally block:
connection.Open(); try { cmd = new System.Data.SqlClient.SqlCommand("wl_AddUser", connection); ............. } finally { connection.Close(); }
Additionally you could put the execution code in another function and just pass the "cmd".
Another part I didn't understand is why are you defining the "cmd" variables in the beginning.
Also why are you requiring a table always but not only when it is neccessary? You said it's faster to use a reader and copy the data to the passed table "manually", but couldn't you create another function to do that to avoid identical code in your program again and again?
Also thinking about what code you will be generating again and again - you could initialize the SqlCommand and pass it to another function to actually perform the stored procedure call.
Here's the result after slightly fixing your code:
private static void CheckConnection(System.Data.SqlClient.SqlConnection connection) { if ((connection == null)) { throw new System.ArgumentException("The connection object cannot be null"); }
if ((connection.State == System.Data.ConnectionState.Closed)) { throw new System.ArgumentException("The connection must be closed when calling this method."); } }
private static void ExecuteSqlCommand(System.Data.SqlClient.SqlCommand cmd, System.Data.DataTable table) { connection.Open(); try { if ((table != null)) { System.Data.SqlClient.SqlDataReader reader = cmd.ExecuteReader(); try { if (reader != null) { table.Clear(); table.Columns.Clear(); for (int i = 0; (i < reader.FieldCount); i = (i + 1)) { System.Type __type; string __name; __type = reader.GetFieldType(i); __name = reader.GetName(i); table.Columns.Add(__name, __type); }
while(reader.Read()) { System.Data.DataRow row = table.NewRow(); object[] rowdata = new object[reader.FieldCount]; reader.GetValues(rowdata); row.ItemArray = rowdata; table.Rows.Add(row); } } } finally { reader.Close(); } } else { cmd.ExecuteNonQuery(); } } finally { connection.Close(); } }
public static int wl_AddUser(System.Data.SqlClient.SqlConnection connection, System.Data.DataTable table, string uname, string pwd, bool IsUserAdmin, bool IsProviderAdmin, bool IsWaitlistAdmin, bool IsUser, ref int uid) { int RETURN_VALUE = 0;
CheckConnection(connection);
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand("wl_AddUser", connection); cmd.CommandType = System.Data.CommandType.StoredProcedure; cmd.Parameters.Add("@RETURN_VALUE", System.Data.SqlDbType.Int, 0); cmd.Parameters["@RETURN_VALUE"].Direction = System.Data.ParameterDirection.ReturnValue; cmd.Parameters["@RETURN_VALUE"].Value = RETURN_VALUE; cmd.Parameters.Add("@uname", System.Data.SqlDbType.VarChar, 50); cmd.Parameters["@uname"].Direction = System.Data.ParameterDirection.Input; cmd.Parameters["@uname"].Value = uname; cmd.Parameters.Add("@pwd", System.Data.SqlDbType.VarChar, 50); cmd.Parameters["@pwd"].Direction = System.Data.ParameterDirection.Input; cmd.Parameters["@pwd"].Value = pwd; cmd.Parameters.Add("@IsUserAdmin", System.Data.SqlDbType.Bit, 0); cmd.Parameters["@IsUserAdmin"].Direction = System.Data.ParameterDirection.Input; cmd.Parameters["@IsUserAdmin"].Value = IsUserAdmin; cmd.Parameters.Add("@IsProviderAdmin", System.Data.SqlDbType.Bit, 0); cmd.Parameters["@IsProviderAdmin"].Direction = System.Data.ParameterDirection.Input; cmd.Parameters["@IsProviderAdmin"].Value = IsProviderAdmin; cmd.Parameters.Add("@IsWaitlistAdmin", System.Data.SqlDbType.Bit, 0); cmd.Parameters["@IsWaitlistAdmin"].Direction = System.Data.ParameterDirection.Input; cmd.Parameters["@IsWaitlistAdmin"].Value = IsWaitlistAdmin; cmd.Parameters.Add("@IsUser", System.Data.SqlDbType.Bit, 0); cmd.Parameters["@IsUser"].Direction = System.Data.ParameterDirection.Input; cmd.Parameters["@IsUser"].Value = IsUser; cmd.Parameters.Add("@uid", System.Data.SqlDbType.Int, 0); cmd.Parameters["@uid"].Direction = System.Data.ParameterDirection.InputOutput; cmd.Parameters["@uid"].Value = uid;
ExecuteSqlCommand(cmd);
// The Parameter @RETURN_VALUE is not an output type // The Parameter @uname is not an output type // The Parameter @pwd is not an output type // The Parameter @IsUserAdmin is not an output type // The Parameter @IsProviderAdmin is not an output type // The Parameter @IsWaitlistAdmin is not an output type // The Parameter @IsUser is not an output type uid = ((int)(cmd.Parameters["@uid"].Value));
RETURN_VALUE = ((int)(cmd.Parameters["@RETURN_VALUE"].Value)); return RETURN_VALUE; }
I could keep optimizing and create a couple more functions for the parameters to clean it up as well.
I hope you will take this as constructive critics not criticising you. I borrowed a couple of ideas from your article by now and I think it's very helpful.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
Hi
The Northwind database has Procedure names with Spaces Ex( Ten Most Expensive Products). How do i call those procedures from Java
I have tried the below options {? = call Northwind.dbo.Ten Most Expensive Products} {? = call Northwind.dbo.[Ten Most Expensive Products]} {? = call [Northwind].[dbo].[Ten Most Expensive Products]} {? = call [Northwind].[dbo].[Ten Most Expensive Products]()} {? = call [Northwind].[dbo].[Ten_Most_Expensive_Products]}
Though i pass the procedure name with spaces still getting error Exception in thread "main" java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC][SQLServer]Could not find stored procedure 'Northwind.dbo.TenMostExpensiveProducts'.
Kindly let me know the right way of passing the procedure name to exectue.
Thanks
Numburisat
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Have you seen http://www.mygenerationsoftware.com
MyGeneration is an extremely flexible development tool written in Microsoft .NET. MyGeneration generates code from templates that can be written in C#, VB.NET, JScript, and VBScript. MyGeneration is great at generating ORM architectures or O/R Mapping files for architectures such as Gentle.NET, NHibernate, and others. The meta-data from your database is made available to your templates through the MyMeta API. MyGeneration supports Microsoft SQL, Oracle, IBM DB2, MySQL, PostgreSQL, Microsoft Access, FireBird, Interbase, SQLite and VistaDB.
MyGeneration also ships with a very popular .NET architecture available in C# and VB.NET known as dOOdads
See http://www.mygenerationsoftware.com/dOOdads/CSharp_MasterSample.aspx[^]
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Could you add or tell me where to add code to integrate Windows Integrated Security and be able to select Database. I made a simple attempt but failed?
"Server=localhost;Integrated Security=SSPI; Database=Pubs"
Regards & Thank you ,
Mark
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The edit boxes should not be the same color as the background-- it should generally be white. In this color, it appears like you can't edit the fields. It's a really great application! Thanks!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'll add that into the next version (due out next week sometime) as well as J# support 
Thanks for the comment
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I have a DBHelper type class for all my SProc calls, etc. as mentioned early in this article, and many of the functions return DataTables. What I'm wondering is instead of using DataAdapter.Fill and returning a DataTable, should I use a Reader and pass it to another simple function that builds a DataTable out of it and returns that? Would that be more efficient?
Also, how do you learn/know little efficiency tid-bits like this?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Wether or not you should use datatables depends on the needs of your applicaiton. For me it is a simple choice, I do not allow direct access to the database, all calls have to go through stored procedures. As such I do not need the additional overhead/flexibility that a dataadapter provides.
As for how do you learn, expierance and examining the code for both various classes involved.
HTH
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I've been trying to figure out how to code a call to sp_helpdb and have get back it's values.
leppie pointed me to this article but I don't think it's a fit ... ?
Thanks,
cb
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
I'm not quite sure what you mean. If you look in LocateDB.cs you will find a method _CheckValid that loads a list of databases from a specific server, however I use the sp_databases call. Using sp_helpdb is really the same kind of call, it simply returns more information than I needed.
HTH
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
Hi!
I was wondering if there is a special reason for the approach you used to populate the datatable?
Wouldn't it be easier to use a DataAdapter?table = new DataTable(); SqlDataAdapter da = new SqlDataAdapter(cmd); //cmd is your SqlCommand object da.Fill(table);
[Added]Also; mark the table parameter as "out" in the parameter list on top.
Have a look at my latest article about Object Prevalence with Bamboo Prevalence.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
The answer is simple, overhead. While it is less typing to fill a datatable by calling IDataAdapter.Fill(x) it adds nothing to the finished datatable and carries quite a bit of overhead. Since I am not typing the code, I choose to go for lower overhead, higher performace with slightly more verbose code.
Feel free to modify to suite your needs and desires
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
This is a very useful tool. Thank you!
I can see that DataTables are created by the wrapper. As I recall, by default values are passed by value in C#. Do I have to modify the generated code to retrieve the DataTable? If so, that is not a problem.
Danny Crowell is the president and founder of Crowell Solutions, Inc. and has been developing enterprise systems since 1993. Crowell Solutions is a software company that develops custom database applications. They save organizations time and money by helping them capture, transfer, and report information. Visit them online at www.crowsol.com.
|
| Sign In·View Thread·PermaLink | 1.00/5 (1 vote) |
|
|
|
 |
|
|
Danny Crowell wrote: As I recall, by default values are passed by value in C#.
The "value" that is beeing copied ( = passed by value) here is the reference to the DataTable object. Both references point to the same DataTable object.
So if I hva understood you right; No, you don't have to modify the generated code to retrieve the DataTable...
Have a look at my latest article about Object Prevalence with Bamboo Prevalence.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Arjan is completely correct you don't have to modify the generated code, just pass in a valid datatable. Be aware that the current contents (if any) of the datatable will be cleared by the method before inserted the results from the stored procedure. If you don't care about the return set (or if the stored proc doesn't have any) simply pass in null for the datatable (nothing for you VBer's out there).
Regards
Charles Horan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi Charles,
great work, this is the way code generation tools made their start. As you will probably see, there will be a lot of request's, how the generated code should look like, and over time , it gets very fuzzy to implement all these request, even if they are your own.
I didn't stepped through the code, but I assume, you hardcoded the output code for all languages you like to support. A more elegant way of doing this would be the use of templates, where you can make the changes how the generated code should look like without touching the application.
Please don't consider this as advertising as the tool I am now going to mention is free to use and comes with a huge free usable template library, and I am not affiliated with the developers of this tool.
mygeneration (www.mygenerationsoftware.com) is a template based code generation utility with full database meta data support for mssql, msaccess, oracle, mysql and postgre giving you all necessary schema information for generating all kind of database application code. You have access to tables, stored procedures, views, indices, primary / foreign keys, fields you can use to build your code.
There is already a quite impressive template library for building all kind of code, such as wrappers for db tables and stored procedures up to a little, charming framework (called dOOdads) for accessing data (load, create, insert, update, delete, sort, filter, ....).
The templates support either ms script (vbscript, jscript) or net scritp (c#, vb.net). The output language is up to your imagination (c#, vb.net, html, sql, ....).
The template library is growing from day to day by submitted user inputs.
If it is your intention to see how code generation could be done, then go on with your solution and improve it, lots of people will like it.
But if you don't want to reinvent the wheel and want to save valuable time, mygneration already provides all you need that you can use to develop your own templates. This way you can concentrate on improving the output code and not the tool to build it.
At least, give it a look. After switching to mygeneration, I love this tool more and more the more I use it and it saved me lots of time. And the best is, the developers are open to user feature requests, which , in some cases, are implemented in a very short time. The update cycles at the moment vary from 5 to 14 days.
Marc Sommer
mygeneration web site : www.mygenerationsoftware.com mygeneration forums : http://www.mygenerationsoftware.com/phpbb2/ mygeneration Template Library : http://www.punktech.com/mygeneration/
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Marc,
Thanks for your response, and I will agree that in it's current state the generator is primitive. In the sense that it is intended to solve a single issue in a standard method. However primitive does not equate to without use. This tool was written for my purposes for my projects, it was never meant to be a general purpose database generation tools, though the hooks are there to make it so. While the structure of the code is predefined, to use your words a template, the generation of the language specific code is left to drivers that implement the CodeDom interface. Also the code that determines the strucuture has been factored at a high level so that in the future, if I feel the need, I can go back and allow users to customize the current template to suite thier need.
Regards
Charles Horan
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Note: Names of stored procedures can include blanks (like: 'Sales by Year' in the Northwind DB), which leads to wrong C# procedurenames.
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
General News 
|