2

I currently have a Data Access Layer that is also exposing some API using a web service.

API Call

[WebMethod]
public List<GlobalStat> GetStats()
{
    List<GlobalStat> Stats = new List<GlobalStat>();

    string sql = @"
       A huge multi-line SQL query
    ";

    try
    {
        string ConString = Constants.connString;
        con = new SqlConnection(ConString);

        cmd = new SqlCommand(sql, con);
        con.Open();
        dr = cmd.ExecuteReader();

        while (dr.Read())
        {
            GlobalStat stat = new GlobalStat();
            stat.Key = dr[0].ToString();
            stat.Value = int.Parse(dr[1].ToString());

            Stats.Add(stat);
        }

    }
    catch (Exception x)
    {

        Response.Write(x);
    }

    return Stats;
}

I am a bit worried about the how the SQL is written.

There are so many things hard-coded into this: Database name, Table names etc.

To solve this problem, do I just create a separate global file with all SQL commands at one place or is there a better paradigm? I am not creating the SQL tables inside the application but these tables reside on a different pre-built database.

How should I structure an application that uses inline SQL to generate data from a database?

Community
  • 1
  • 1
Legend
  • 113,822
  • 119
  • 272
  • 400
  • Any reason why you need you want to use parameratized queries? There are many mature ORM's (Nhibernate, Entity Framework) that you can reliably call upon. – Jesse Jun 13 '12 at 02:22

3 Answers3

6

The proper way, IMHO, is to call stored procedures. In your C# code you just reference a stored procedure and pass properly typed parameters. Ad hoc SQL built up in your C# code opens the door up for many things - not the least of which are SQL injection and inefficient use of plan cache. Plus it makes it very difficult for you to refactor your queries without recompiling and redeploying the application. That will be necessary for some changes (e.g. when the interface to a stored procedure changes), but shouldn't be necessary for many other typical query changes.

Aaron Bertrand
  • 272,866
  • 37
  • 466
  • 490
  • So I am assuming that these stored procedures would then be first stored on the SQL Server and then just "used" within the application? Lastly, is it a good practice to check and create these stored procedures from within our application or should this be done in a separate step? – Legend Jun 13 '12 at 01:15
  • 1
    This is obviously just opinion, but eeew stored procs. I much prefer my queries in my code, where they're under source control and easier to see and tweak. – josh3736 Jun 13 '12 at 01:17
  • Yes, stored procedures are created on the server, and used by applications. The nice thing about this separation: (1) you can debug them independently of your application, and (2) multiple applications can use the same stored procedures. – Aaron Bertrand Jun 13 '12 at 01:17
  • 1
    @josh3736 "eeew" doesn't sound like a very constructive argument. And since when are you unable to save your stored procedures in source control? – Aaron Bertrand Jun 13 '12 at 01:18
  • I don't know about "ewww", but I think it's a shame to not even mention the concept of ORMs in your answer, since IMO that's a much better approach. – Kirk Woll Jun 13 '12 at 01:20
  • @KirkWoll and that's the problem. Do you think there's a perfect approach that everyone will agree on? If you want to cheerlead for ORM, post an answer! I'm afraid this is quickly going to devolve into a "my dad can beat up your dad" waste of time. – Aaron Bertrand Jun 13 '12 at 01:21
  • 1
    Not at all -- I was just hoping I could be lazy and upvote the canonical answer. But without rigor, this answer doesn't qualify. – Kirk Woll Jun 13 '12 at 01:21
  • @KirkWoll my intention wasn't to provide a canonical answer. I answered the question, based on my opinion (I did, after all, start of with "IMHO"). I do not feel the need to create an answer that has the highest likelihood of appealing to the largest number of potential up-voters not willing to express their differing opinions themselves. – Aaron Bertrand Jun 13 '12 at 01:23
  • @AaronBertrand: Like I said, just my opinion. More *constructive arguments* [have been made many, many times](http://stackoverflow.com/q/15142/201952). – josh3736 Jun 13 '12 at 01:24
  • @KirkWoll it's ok, a lot of people like down-voting answers that can't possibly be wrong since they're an opinion. How can *any* answer to this question be wrong? (Or correct, for that matter?) – Aaron Bertrand Jun 13 '12 at 01:48
  • 1
    @AaronBertrand You do bring up a good point: If this post is eliciting opinions instead of answers, should it even be open? – George Stocker Jun 13 '12 at 01:57
  • @GeorgeStocker I already voted to close it as not constructive. However I will suggest that an opinion accompanied with indisputable facts can count for a little more than opinion. [Anyway at the very least it is a duplicate of the answer josh pointed out.](http://stackoverflow.com/questions/15142/what-are-the-pros-and-cons-to-keeping-sql-in-stored-procs-versus-code) – Aaron Bertrand Jun 13 '12 at 01:59
5

You bring up several issues, and the code you are showing us brings up some more. Things you may want to think about:

  • Try to keep the database centric activities constrained into their own classes. No one else needs to know about how to return a list of rows for the GlobalStats object other than the actual class that handles pulling that data out of the database and making it into an actual object for you. No one. If anyone else does know, then your class is not using information hiding (which since we're in an object oriented language, you should be).

  • If the object implements IDisposable, then you should be wrapping it with a try {} finally {} block, or even better, wrap it with a using statement (see my second example below).

  • Your connection string should only be accessible to the classes that actually need it (part of separating your concerns). Perhaps having a base DataAccess class that has that information?

    public abstract class DataAccess 
    {
        protected const string ConnectionString = "YourConnectionStringHere";
    }
    

Then your repositories can inherit from this class, and you don't have a global static constant that causes your code to be unnecessarily coupled.

Here's how I'd write what you're writing (be careful, this code isn't really meant to be used, it's for illustrative purposes only):

[WebMethod]
public List<GlobalStat> GetStats()
{
    GlobalStatsRepository repository = new GlobalStatsRepository();
    List<GlobalStat> stats = repository.GetStats();
    return stats;
}

DataAccessLayer

public class GlobalStatsRepository
{
    public List<GlobalStat> GetStats()
    {

         string sql = @"SELECT * from GlobalStats"; //no, not a good practice

        var stats = new List<GlobalStat>();

        try
        {
            string ConString = Constants.connString;
            conn = new SqlConnection(ConString);

            cmd = new SqlCommand(sql, conn);
            conn.Open();
            dr = cmd.ExecuteReader();

            while (dr.Read())
            {
                GlobalStat stat = new GlobalStat();
                stat.Key = dr[0].ToString();
                stat.Value = int.Parse(dr[1].ToString());

                stats.Add(stat);
            }

        }
        catch (SQLDataReaderException ex)
        {
            logger.Log(ex);
            throw;
        }
    return stats;
    }
}

Example of Parameritized Query

public List<GlobalStat> GetStatsById(int id)
{
    var stats = new List<GlobalStat>();

    string sql = @"SELECT * from GlobalStats WHERE Id = @Id";
    using (SqlConnection conn = new SqlConnection(ConString))
    {
         conn.Open();
         using (SQLCommand command = new SqlCommand(sql, conn))
         {         
             command.Parameters.Add(new SqlParameter("Id", id));
             SqlDataReader reader = command.ExecuteReader();
             while (reader.Read())
             {
                  GlobalStat stat = new GlobalStat();
                  stat.Key = dr[0].ToString();
                  stat.Value = int.Parse(dr[1].ToString());

                  stats.Add(stat);
             }
         }
     }
     return stats;
}
Community
  • 1
  • 1
George Stocker
  • 57,289
  • 29
  • 176
  • 237
2

Stored Procedures or LINQ to SQL are two popular database access paradigms within C#.

For more information regarding LINQ to SQL check out Using Linq to SQL on Scott Gu's Blog.

Josh Mein
  • 28,107
  • 15
  • 76
  • 87