SQL Server with .NET Applications
Learn how to connect to SQL Server from a C# application using a connection string, with short ADO.NET and Entity Framework Core examples.
Introduction
Everything covered so far has run inside SSMS, but in real applications, T-SQL is almost always driven from application code. This lesson shows how a C# application connects to SQL Server, first with ADO.NET's lower-level SqlConnection and SqlCommand classes, then with the higher-level Entity Framework Core object-relational mapper (ORM). Both talk to the same SQL Server database - they just offer different levels of control and convenience.
- How a connection string identifies a SQL Server database.
- How to query SQL Server with ADO.NET's SqlConnection and SqlCommand.
- How to read results with SqlDataReader.
- Why parameterized queries matter from application code too.
- A basic Entity Framework Core query example.
Connection Strings
A connection string tells the .NET data provider which server and database to connect to, and how to authenticate. It is typically stored in configuration (like appsettings.json), not hardcoded in source files.
// appsettings.json// {// "ConnectionStrings": {// "SalesDb": "Server=localhost;Database=SalesDB;Trusted_Connection=True;TrustServerCertificate=True;"// }// }
string connectionString = "Server=localhost;Database=SalesDB;Trusted_Connection=True;TrustServerCertificate=True;";| Part | Meaning |
|---|---|
| Server | The SQL Server instance to connect to |
| Database | The specific database to use once connected |
| Trusted_Connection=True | Use Windows Authentication instead of a SQL login/password |
| User Id / Password | Used instead of Trusted_Connection for SQL Server Authentication |
ADO.NET: SqlConnection and SqlCommand
SqlConnection represents a connection to the database, and SqlCommand represents a T-SQL statement to run against it. Both implement IDisposable, so they should always be wrapped in a using statement to guarantee the connection is closed and released back to the pool.
using Microsoft.Data.SqlClient;
string connectionString = "Server=localhost;Database=SalesDB;Trusted_Connection=True;TrustServerCertificate=True;";
using (var connection = new SqlConnection(connectionString)){ connection.Open();
var command = new SqlCommand("SELECT COUNT(*) FROM dbo.Customers", connection); int customerCount = (int)command.ExecuteScalar();
Console.WriteLine($"Total customers: {customerCount}");}Click Run to see what this code prints.
Reading Results with SqlDataReader
For queries that return multiple rows, ExecuteReader() returns a SqlDataReader, a fast, forward-only cursor you loop through with Read().
using (var connection = new SqlConnection(connectionString)){ connection.Open(); var command = new SqlCommand( "SELECT CustomerID, FirstName, LastName FROM dbo.Customers WHERE City = 'Seattle'", connection);
using (SqlDataReader reader = command.ExecuteReader()) { while (reader.Read()) { int id = reader.GetInt32(0); string firstName = reader.GetString(1); string lastName = reader.GetString(2); Console.WriteLine($"{id}: {firstName} {lastName}"); } }}Click Run to see what this code prints.
Parameterized Queries in ADO.NET
Just as with sp_executesql in T-SQL, application code should never concatenate user input into a SQL string. SqlCommand.Parameters provides the same protection against SQL injection.
string city = GetCityFromUserInput(); // untrusted input
var command = new SqlCommand( "SELECT CustomerID, FirstName, LastName FROM dbo.Customers WHERE City = @City", connection);command.Parameters.AddWithValue("@City", city);
using SqlDataReader reader = command.ExecuteReader();Building SQL with string interpolation or concatenation of user input (e.g. $"...WHERE City = '{city}'") is exactly as dangerous from C# as it is inside T-SQL, and just as exploitable. Always use SqlParameter.
Entity Framework Core Basics
Entity Framework Core (EF Core) is an ORM that maps C# classes to database tables, letting you write queries in C# (LINQ) instead of raw SQL. A DbContext represents a session with the database, and DbSet<T> properties represent tables.
public class Customer{ public int CustomerID { get; set; } public string FirstName { get; set; } = string.Empty; public string LastName { get; set; } = string.Empty; public string City { get; set; } = string.Empty;}
public class SalesDbContext : DbContext{ public DbSet<Customer> Customers => Set<Customer>();
protected override void OnConfiguring(DbContextOptionsBuilder options) { options.UseSqlServer( "Server=localhost;Database=SalesDB;Trusted_Connection=True;TrustServerCertificate=True;"); }}Querying with EF Core
EF Core translates LINQ queries into parameterized T-SQL automatically, so filters like .Where() are safe from injection by default.
using var context = new SalesDbContext();
List<Customer> seattleCustomers = context.Customers .Where(c => c.City == "Seattle") .OrderBy(c => c.LastName) .ToList();
foreach (var customer in seattleCustomers){ Console.WriteLine($"{customer.CustomerID}: {customer.FirstName} {customer.LastName}");}Click Run to see what this code prints.
ADO.NET vs EF Core
| Aspect | ADO.NET | Entity Framework Core |
|---|---|---|
| Control | Full control over exact SQL executed | SQL is generated by EF from LINQ |
| Productivity | More boilerplate per query | Faster to write typical CRUD code |
| Best for | High-performance, fine-tuned queries | General application development, rapid iteration |
| Learning curve | Lower-level, closer to raw SQL | Requires learning EF's conventions and LINQ translation |
Common Mistakes
- Not wrapping SqlConnection and SqlCommand in using statements, leaking connections from the pool.
- Concatenating user input into SQL strings in C#, the same SQL injection risk covered in T-SQL.
- Storing connection strings (with credentials) directly in source code instead of configuration or a secrets manager.
- Calling .ToList() too early in an EF Core query, pulling the entire table into memory before filtering.
- Not disposing of a DbContext, which can lead to connection and memory issues in long-running applications.
Best Practices
- Always wrap SqlConnection, SqlCommand, and SqlDataReader in using statements.
- Store connection strings in configuration, not hardcoded, and keep credentials out of source control.
- Always use parameters (SqlParameter or LINQ expressions) instead of building SQL strings by hand.
- Let EF Core translate filters (.Where(), .OrderBy()) into SQL rather than fetching everything and filtering in memory.
- Choose ADO.NET for performance-critical paths and EF Core for everyday application CRUD, mixing both where appropriate.
Frequently Asked Questions
Microsoft.Data.SqlClient is the actively maintained, recommended package for new .NET applications; System.Data.SqlClient is the older, now largely legacy provider.
Yes, EF Core can call stored procedures directly (via FromSqlRaw/FromSqlInterpolated for queries, or ExecuteSqlRaw for commands), which is useful when you need to reuse logic already defined at the database level.
EF Core adds some overhead for translating LINQ to SQL and mapping results back to objects, but for most typical application workloads the difference is negligible; performance-critical hot paths can still drop down to raw ADO.NET or raw SQL through EF.
Both ADO.NET and EF Core reuse underlying physical connections from a pool behind the scenes when you Open()/Dispose() a connection with the same connection string, which is why opening and closing connections frequently in code is still efficient.
Key Takeaways
- A connection string identifies the server, database, and authentication method for a .NET application.
- ADO.NET's SqlConnection/SqlCommand/SqlDataReader give direct, low-level control over T-SQL execution.
- Always use parameters from application code, exactly as with sp_executesql in T-SQL.
- Entity Framework Core maps C# classes to tables and translates LINQ into parameterized SQL.
- Choose ADO.NET or EF Core (or both) based on how much control versus productivity a given task needs.
Summary
Whether through ADO.NET or Entity Framework Core, .NET applications ultimately rely on the same safe, parameterized T-SQL principles covered throughout this course. In the final lesson, you will look at Azure SQL Database and wrap up with a set of general best practices for SQL Server and database design.