2

I work on a ancient large WinForms project that contains a lot of win form libraries.

Each form uses hard-coded sql commands in methods. Sometimes the same SQL strings are duplicated, and modifying one I should research "similar" strings in the form's code.

I understand that the architecture is not super-pretty, but at moment we can't perform large architecture modifications. I think to do "little steps" to ameliorate the separation o the UI and BD logic....

A first 'step', by eg. I thought "separate" in a way sql strings from code. How could this be achieved, using Resources, a special class, a XML file?

Variant I - using named Resources

' ancient variant '
_SqlPeriod = String.Format("SELECT * FROM DBO.GP_PERIOD WHERE PERIOD = {0} ORDER BY LABEL", Me._Period)

' a better way (?) ... '
_SqlPeriod = String.Format(My.Resources.ResourceManager.GetString("SelectAPeriod"), Me._Period)
Mogsdad
  • 44,709
  • 21
  • 151
  • 275
serhio
  • 28,010
  • 62
  • 221
  • 374

4 Answers4

2

This is what your .NET code could look if you were to rewrite the whole thing into stored procedures:

 public List<User> listUsers(Guid pPersonTypeId, string pFirstName, string pLastName)
        {
            List<User> list = new List<User>();
            List<SqlParameter> pa = new List<SqlParameter>();
                       pa.Add(SqlHelper.createSqlParameter("@ID_PERSONTYPE", SqlDbType.UniqueIdentifier, pPersonTypeId != Guid.Empty ? (Guid?)pPersonTypeId : null));
            pa.Add(SqlHelper.createSqlParameter("@FNAME", SqlDbType.NVarChar, String.IsNullOrEmpty(pFirstName) ? null : pFirstName));
            pa.Add(SqlHelper.createSqlParameter("@LNAME", SqlDbType.NVarChar, String.IsNullOrEmpty(pLastName) ? null : pLastName));

            try
            {
                DataSet ds = SqlHelper.ExecuteDataset("proc_USER_list", pa.ToArray());
                if (ds.Tables.Count == 0)
                    return list;
                foreach (DataRow r in ds.Tables[0].Rows)
                {
                    User u = new User();
                    //populate your User object with data from the DataRow
                    ...
                    list.Add(u);
                }
                return list;
            }
            catch (Exception ex)
            {
                throw (new BaseException(ex));
            }
        }

You would of course need to implement the SQLHelper and SQLParameter classes, which is fairly simple.

if this is not a way you want to go right now, I suggest something like creating an XML document (one per data access class), storing your queries in it, and writing a very simple wrapper class to fetch your queries based on a query code or ID. The XML could look like this for example:

<query code="listUsers"> select * from USER where NAME = {0} </query>

To go even further, I guess you could use the method name as a query code and even have the wrapper use reflection to see the method it's being called from and use the name of the method to look for your query in the XML.

Using resource classes to store your queries is even simpler, but I'm not sure there are any benefits at all to be had there, with the XML at least you get SOME separation.

Filip
  • 51
  • 1
  • 4
  • 1
    I think your proposal demonstrates that using alternatives to the class approach needs much more initial work, before you can gather benefits. – Ekkehard.Horner Apr 22 '11 at 10:12
  • 1
    Rewriting the app to use stored procedures is a lot of work, but using the XML approach seems pretty easy at this point. I don't really see the benefits of using a class to store your strings. – Filip Apr 22 '11 at 10:38
2

Generally, your biggest concern is using String.Format to form your SQL queries, because this is prone to Sql Injection Attacks, if you don't explicitly validate your parameters. So separating these strings is not an improvement (and moving them into an xml file would be a security disaster, if they are resolved during runtime).

To prevent this, you need to either create SqlCommand instances and set parameters programatically (check MSDN - How To: Protect From SQL Injection in ASP.NET), or use a completely different approach and switch to an ORM framework.

Since moving to ORM might take considerable amount of time if you haven't used it before, I believe you will opt for the first option. In that case, simply switching to Sql parameters as described above will be an improvement (even with sql strings left hard-coded).

To prevent duplication, you should consider moving all database access calls into a separate project (a Data Access Layer, DAL). If you want to start in smaller steps, you might consider (for start) delegating the creation of the SQL string to a different layer, e.g.:

_SqlPeriod = QueryStrings.GetPeriodsById(Me._Period) ' <- returns an sql string

This would allow you to move parameter validation into a common project. Later, you will want to avoid passing sql strings to your UI layer completely, and simply get the data:

_ActualData = Dal.GetPeriodsById(Me._Period) ' <- gets the actual list of periods
vgru
  • 49,838
  • 16
  • 120
  • 201
1

I would go for a class. At first you just replace literal sql statements with getter calls to centralize the SQL access. Then you can use the class' init code to fine tune the statements where appropriate (local pathes, dates, users, other environment factors). Later you can refactor the getters to return prepared statements on a second call or do other fancy things.

A resource would give you just a list of strings; an .xml file would need a lot of structural planning and supporting code before you can exploit XML features.

Ekkehard.Horner
  • 38,498
  • 2
  • 45
  • 96
  • a class per form, per project or per solution? – serhio Apr 22 '11 at 09:18
  • I work this way to, a class doing the job. A class per form, project, solution? Depends on your project size and SQL cross-project usage (are you using same code between all projects or not?). If yes, a project containing your class will be the best thing to do. – Arnaud F. Apr 22 '11 at 09:51
  • @serhio: you loose nothing if you put the class into a project of its own and use the project just once for now. "per form" seems "not centralized" to me. – Ekkehard.Horner Apr 22 '11 at 10:09
1

I would use stored procedures instead of having SQL in code, XML or resource.

You can have a look at this for some pros and cons of using SP. What are the pros and cons to keeping SQL in Stored Procs versus Code

Actually, in this thread the guys against SP has the most votes but don't let that put you off :).

One of the biggest advantages that I see is that you can change and test SP without changes the client code as long as you work with the same parameters and your datasets contain the same columns. Another is that your queries are checked against dependencies when you create them and you can fairly easy find out if a table or column is used by some SP before you change the table structure. Not that easy when the query is stored away in a resource or embedded in code

Community
  • 1
  • 1
Mikael Eriksson
  • 136,425
  • 22
  • 210
  • 281
  • anyway, you will still have sql strings like "EXEC MY STORED PROCEDURE {arg1}, {arg2}, {argX}". And this is not a little "step" for me rewriting thounsands of "select * from MYTABLE" into stored procs. At least, at the moment... – serhio Apr 22 '11 at 09:04
  • @serhio – I don't think you need to write the code like that. I use Delphi so I don't know what is available in .NET but I think that you should have some kind of object that handles SP's where you put the SP name in one property and add the parameters to a parameter collection. Moving the SQL from code to SP is, as I see it, no more work than moving it to a resource or a XML file. – Mikael Eriksson Apr 22 '11 at 09:13
  • if I'm the guy with (most) votes, I'm not the guy against SPs. I just thought SPs wouldn't fit serhio's scenario. – Ekkehard.Horner Apr 22 '11 at 10:15
  • @Ekkehard - No, it was not you I was referring to. I meant the top voted answer in the SO question I linked to. He started his answer with `I am not a fan of stored procedures` :) – Mikael Eriksson Apr 22 '11 at 10:35