C# function to execute queries in a SQL Server database public static DataSet execute_query(string query)
{
    DataSet ds = new DataSet();
    SqlConnection con = new SqlConnection();
    con.ConnectionString = DataAccessLayer.Properties.Settings.Default.cn;
    try
    {
        con.Open();
        SqlDataAdapter da = new SqlDataAdapter(query, con);
        da.Fill(ds);
    }
    catch (Exception ex)
    {
        ds = null;
    }
    finally
    {
        if (con.State == ConnectionState.Open) con.Close();
    }

    return ds;
}
SqlConnection implements the IDisposable interface, and so does the SqlDataAdapter class - any type that implements IDisposable should have its Dispose method called; by disposing the connection you don't need to explicitly close it.

The best way to ensure Dispose gets called is to wrap the object in a using block, which is basically compiler magic for a try/finally block.

using (SqlConnection connection = new SqlConnection(connectionString))
{
    connection.Open();
    using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connection))
    {
        DataSet result = new DataSet(); 
        adapter.Fill(result);
        return result;
    }
}


The SqlAdapter+DataSet way will work on any version of .net - but it's also a pretty dated way to go about it, and since DataSet also implements IDisposable, returning it makes for awkward code, because normally it's the responsibility of the code that creates an IDisposable object to call its Dispose method - but if you do that (or wrap it in a using block), then the client code will run into an ObjectDisposedException when it tries to use the data set.

Slightly more advanced is the ADO.NET SqlCommand, which lets you create SqlParameter objects to send a parameterized command to the SQL Server. This puts the responsibility of dealing with parameters on the database server, where it belongs (Greg Burghardt's answer hints at this).

If you're not constrained to .net 2.0 then the next best thing would be LINQ-to-SQL, or Entity Framework (or NHibernate, or Dapper, depending on your needs): these more modern tools often eliminate the need to ever concatenate SQL strings, or even to explicitly create SqlParameter objects for a parameterized query - for example this:

public User GetById(int id)
{
    using (var context = new MyDbContext(connectionString))
    {
        return context.Users.SingleOrDefault(user =&gt; user.Id == id);
    }
}


Generates a SQL query similar to:

SELECT TOP 1 Id, FirstName, LastName, BirthDate, Gender 
FROM dbo.Users
WHERE Id = @id


...without ever needing to explicitly create a parameter or concatenate an arbitrary string into a bit of SQL - and C# looks so much better without SQL string literals all over the place! And the best part, it returns a User object ("entity") instead of a DataTable.

But it's great that you're starting with the lower-level stuff: lots of people start with Entity Framework and don't quite understand what's happening under the hood.



Other issues:


The method being static hints at procedural-style. C# is multi-paradigm, but the overwhelmingly dominant one is the object-oriented one: by working with static methods, you're working with types, not objects. The difference will start showing when you start measuring things like coupling and testability.
Method names should be PascalCase, so execute_query should be ExecuteQuery.
query isn't an ideal name for a string containing an SQL statement, because starting in .NET 3.5 a query is a good name for an IQueryable&lt;T&gt; object that's actually building a query via a LINQ provider. Keep it simple: sql is a perfectly acceptable and descriptive name.