This is a code generator that builds a light weight Data Access Layer (DAL) to do CRUD operation and also creates an C# interface for calling SQL server stored procedures.
Many years ago I noticed most of customer bug reports were due to errors caused by database operations, such as a missing column or parameter, passing a string to a numeric parameter or change in database structure that was not implemented in the UI application.
We needed an application to automatically check our code to make sure its DAL interfaces matches database structure.
Next problem was the absent of Entity Framework from most of our clients' servers. Asking them to install it required writing a justification letter every time and a lengthy process of approval, then sometimes they would install incorrect version which would cause more unrelated errors which in turn would leave behind bad feedback from customer's IT team. I came out with an neat idea. What if I could generate the DAL code based on database and create native language methods.
A few looks around in internals of SQL server and I came out with this application. I migrated one of our company applications to use this DAL during my own free time and presented to my team, they were sceptic at first but when they tried it, almost everyone liked the idea so base on feedback from team I started improving it for our company needs. A few years later majority of feedback from other teams in company was that it makes our job faster, easier and we have less bug reports from customer.
- It runs faster than EF since it does not do the initial checks that are normally done by EF during startup. These checks are not required since assumption is database structure is correct.
- It creates a smaller dll compared to EF. A typical DAL dll created by this app for a database with 200 tables and 1200 procedures is about 45 KB (Yes! Kilobytes not Megabytes)
- It allows higher security on database. Application access to database can be limited to tables and procedures included in the DAL, since it doesn't expose every single table and procedure in the database.
- Since developer will be calling native methods instead of mixing SQL, LINQ and C# commands, chance of making mistakes is lower.
- Most checks such as length and range checks are not implemented which in turn causes faster running applications. (It is job of business logic to validate data before sending them to DAL)
- This can be included in CI/CD pipeline to regenerate the DAL automatically which will ensure changes in database will cause compile errors instead of runtime errors.
- Intellisense will pick up method signature and if this app is used in conjunction with 2 other application in this toolset, will show the column, table and procedure comments as hints for method and parameters.
- Note that using n-tier application architecture (which is recommended way of programming for enterprise applications including cloud) will help protecting the proprietary company code, since no developer is required to have access to entire code base to be able to compile their application. Database (DB) is developed by someone, Business logic (BL) code is generated by someone else and API, Webservices, UI each by different developer.
- From my observation on projects in my company that use this app toolset, I noticed they complete between 60 to 80 percent faster than when EF used. In one case project with 1 year timeline was completed and deployed in 4 month (enterprise level project with minimum 182 active client applications at any time).
This app can generate 2 types of DAL, one with model structures and one without them, the one with models has overhead of having to create an instance of each structure and insert data into it which is then slower than directly using the data row itself.
using (SqlConnection conn = new SqlConnection(Connection_String)) {
using (test.DAL.Entry dal_entry = new test.DAL.Entry(conn)) {
// Add Record
test.Entities.Entry entry = new test.Entities.Entry();
entry.AccountNo = "4568099023246532";
int recordID = dal_entry.Add(entry);
// Edit Record
int result = dal_entry.Edit(recordID, entry);
// Get Record by its Id
test.Entities.Entry rec = dal_entry.Get(11);
Console.WriteLine(rec.AccountNo);
// Delete Record
result = dal_entry.Delete(recordID);
// Call custom stored procedure
DataTable table = dal_entry.GetTransactionList("4568099023246532");
}
}
Without model structures it will look like
using (SqlConnection conn = new SqlConnection(Connection_String)) {
using (test.DAL.Entry dal_entry = new test.DAL.Entry(conn)) {
// Add Record
int recordID = dal_entry.Add("4568099023246532");
// Edit Record
int result = dal_entry.Edit(recordID, "4568099023246532");
// Get Record by its Id
DataTable table = dal_entry.Get(11);
if (table!null && table.Rows.Count > 0){
DataRow row = table.Rows[0];
Console.WriteLine(row["AccountNo"]);
} else {
Console.WriteLine("Record not found");
}
// Delete Record
result = dal_entry.Delete(recordID);
// Call custom stored procedure
DataTable table = dal_entry.GetTransactionList("4568099023246532");
}
}
As you might noticed with structures you will have to pass a model to Add
, Edit
models, but without model row data will be passed as parameters to method. Also when retrieving a record you will receive a structure instead of DataRow
with model based. Each type has their own benefits and drawbacks and from my experience each suits well in different scenario.
To create a meaningful DAL few rules should be followed:
- Name your procedures prefixed with table name followed by an underscore and then method name. For example stored procedure that adds a record to table entry should be named Entry_Add which then generated code will a method called Add in class named Entry.
- If application cannot find a suitable table name to assign procedure to it, it will create a class using schema name of procedure as class name prefixed with underscore. For example if a procedure is called sp_ambiguous_procedure then a method named sp_ambiguous_procedure will be created within _dbo class (default schema is dbo).
- Schema name of table, procedure will be used as part of namespace of class. For example if Entry table is in Finance schema, it will be created as Entry class under namespace namespace_prefix.Entry.
- A namespace prefix is always required, if it is not specified database name will be used.
- If you run it from windows, it will show a GUI and allow you to adjust your settings. These settings will be remembered for next use.
If you run it from console or shell script, GUI will not be shown, instead command line parameters will be used. Running from console without parameters will show the GUI. Command line parameters are as following:
--help, --?, -?, /?, ?
Shows this help
--server=[server address]
Required server address with port if necessary
--oslogin
Use Windows authentication
--user=[username]
Username, required if oslogin is not used
--pass=[password]
Password, required if oslogin is not used
--database=[database name]
Required database name
--output=[output folder]
Output folder surround in double quotes if has spaces,
if left empty current path will be used
--name_space=[namespace]
namespace to be used for Data Access Layer. DAL always
will be attached to namespace, either as prefix or suffix
--withsuffix
attach DAL to namespace as suffix instead of prefix
--wihtmodel
Create with model classes too (slower preformance)
--nolog
Do not save log file
--withsolution
Create solution file too. if exists, will not be overwritten
--omitdbo
Do not add dbo schema to namespace
Note: Running the app from console you will see the result of generation. Due to complications of having an app that can run on both console and windows (output cannot be captured easily), a log file is saved which contains output of application. Console capture will not work since Microsoft didn't make it easy to have both GUI and console in same program even though it was asked frequently since about 2000
After completion, a project will be created in selected output folder which will include classes generated for the DAL. You can add the project directly to your project or add its output as reference.
Note: if the project file exists in output, it will not be overwritten to avoid issues with customized project content. This will cause classes generated for tables added after project was created to not add to project. Developer is required to add them manually to project or delete project file before running the app.
If you are planning to use this app you will need to consider
- Generating without model will not create CRUD methods and simply will use existing procedures to create DAL, thus you will need to create required procedures. To create procedures you can use the other 2 tools in this toolset (Procedure Creator and Writer). This is to reduce clutter and increase security of database by creating a class with exactly what is required.
- Database name, table, column and procedure names that are not valid in C# programming language will cause your generated code not to compile, so use sensible names. For example if name of a column or table is
class
or 'if', it will cause compile error. - If a table doesn't have primary key even if it has usable unique index, will not have an Edit or Delete generated for it.
- List and Search (e.g. List by Parent Id) are essential methods when accessing data in database, however it is too complex to create methods for them. As a workaround create a procedure with desired filtering parameters which a method will be created for it. Also no model will be created for them as recordset might be different based on the parameters passed to procedure, instead a table (
DataTable
) with rows and columns will be returned. - While generating code with model, if you need special Add, Edit, Delete and Get methods, create stored procedures named the same and they will override default generated methods, however you will lose the ability to use entities for passing parameters in input or output.
- This application uses DeriveParameters method (System.Data.SqlClient or Microsoft.Data.SqlClient). If this method fails to determine output of the stored procedure or throws exception, a throwing method will be created. In such scenario as if you insist in having procedure like that and also need to use that procedure, inherit the class in a secondary class and override that specific method.
- DeriveParameters fails when a procedure use temp tables, if possible use table variables instead.
Following features are not supported in this free version.
- Geography, Geometry, HierarchyId columns are not supported. They do not exist in System.Data.SqlTypes shipped with .NET framework and require a Microsoft feature pack that is shipped with SQL Server. To avoid copyright issues I will not support it with this free version.
- This free version uses SQL type variables (
SqlString
,SqlInt32
...) and nullable variables are not supported (String?, Int? ...). to pass null to parameters, you can use SqlString.Null, SqlInt32.Null, etc. - Only first recordset will be returned from each procedure, multi recordset procedures will also return
DataTable
(noDataset
). - Transactions are not supported.
- Only Microsoft SQL Server is supported (There are versions for other databases and other languages too).