{"id":22065556,"url":"https://github.com/karenpayneoregon/sql-basics","last_synced_at":"2025-05-13T01:17:42.697Z","repository":{"id":168009534,"uuid":"643381565","full_name":"karenpayneoregon/sql-basics","owner":"karenpayneoregon","description":"Basics and intermediate topics  for working with SQL-Server using C#","archived":false,"fork":false,"pushed_at":"2025-05-07T20:32:49.000Z","size":8258,"stargazers_count":33,"open_issues_count":0,"forks_count":8,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-05-13T01:17:28.484Z","etag":null,"topics":["csharpcore","dapper-donet-core","dapper-dot-net","efcore7","sql-server","tsql"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/karenpayneoregon.png","metadata":{"files":{"readme":"readme.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-05-21T01:18:27.000Z","updated_at":"2025-05-07T20:32:52.000Z","dependencies_parsed_at":null,"dependency_job_id":"754accf9-b7b3-43c3-b3be-e39b42eefe11","html_url":"https://github.com/karenpayneoregon/sql-basics","commit_stats":null,"previous_names":["karenpayneoregon/sql-basics"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2Fsql-basics","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2Fsql-basics/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2Fsql-basics/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/karenpayneoregon%2Fsql-basics/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/karenpayneoregon","download_url":"https://codeload.github.com/karenpayneoregon/sql-basics/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253851079,"owners_count":21973674,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharpcore","dapper-donet-core","dapper-dot-net","efcore7","sql-server","tsql"],"created_at":"2024-11-30T19:19:29.834Z","updated_at":"2025-05-13T01:17:42.679Z","avatar_url":"https://github.com/karenpayneoregon.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tips on interacting with a database with C#\n\nThis article main intent is to provide useful information for interating with data using a managed data provider, adding [Dapper](https://dapperlib.github.io/Dapper/) to the mix for new developers to get the basics which in turn allows a developer to write clean reusable code.\n\nSome developers might want to jump into [Entity Framework Core](https://learn.microsoft.com/en-us/ef/core/) which is fine but understanding the basic is going to be helpful when diving into Entity Framework Core.\n\nWhich is better, data provider or Entity Framework Core? It depends on the task and performance expected. The author uses both.\n\n## Writing your first SELECT statement\n\nThe average developer creates a database with a table or perhaps a set of tables then writes an SQL statement to select records as shown below.\n\n:heavy_check_mark: Connection string is an issue we will address later\n\nThe developer saw others on the web cancatenating an SQL statement WHERE condition and heard it worked so they write the following code and pass in **Brown** for the WHERE condition. In this case **Brown** exist and the primary key is shown.\n\n\n```csharp\nstatic void Playground(string lastName)\n{\n\n    string connectionString = \n        \"Data Source=(localdb)\\\\MSSQLLocalDB;\" + \n        \"Initial Catalog=NorthWind2022;Integrated Security=True\";\n\n    using var cn = new SqlConnection(connectionString);\n    using var cmd = new SqlCommand\n    {\n        Connection = cn,\n        CommandText = \"SELECT ContactId FROM  dbo.Contacts WHERE  (LastName = '\" + lastName +\"')\"\n    };\n\n    cn.Open();\n\n    var reader = cmd.ExecuteReader();\n    if (reader.HasRows)\n    {\n        reader.Read();\n        Console.WriteLine(reader.GetInt32(0));\n    }\n    else\n    {\n        Console.WriteLine(\"No matches\");\n    }\n}\n```\n\nSo what happens if **Roel's** is used? Upon executing the code we get\n\n```\nMicrosoft.Data.SqlClient.SqlException: 'Incorrect syntax near 's'.\nUnclosed quotation mark after the character string ')'.'\n```\n\nThe resolution, use [parameters](https://learn.microsoft.com/en-us/dotnet/api/system.data.sqlclient.sqlcommand.parameters?view=dotnet-plat-ext-7.0), all data providers have parameters.\n\nFor SQL-Sever in SSMS (SQL-Server Management Studio) a parameter begin with **@**\n\n\n```sql\nDECLARE @LastName AS NCHAR(50) = N'Roel''s';\n\nSELECT ContactId,\n       FirstName,\n       LastName\nFROM dbo.Contacts\nWHERE (LastName = @LastName);\n```\n\nIn SSMS we must escape an apostrophe as shown above while when using parameters in C# with the command object, we create a new parameter with **Parameters.Add** where the first parameter in the name of the parameter (in SSMS DECLARE), the second parameter is the type, in this case for string **SqlDbType.NVarChar** then tag on .Value which here is `lastname`. Internally the code escapes the apostrophe.\n\nNew updated version of code\n\n```csharp\nstatic void Playground(string lastName)\n{\n\n    string connectionString = \n        \"Data Source=(localdb)\\\\MSSQLLocalDB;\" + \n        \"Initial Catalog=NorthWind2022;Integrated Security=True\";\n\n    using var cn = new SqlConnection(connectionString);\n    using var cmd = new SqlCommand\n    {\n        Connection = cn,\n        CommandText = \"SELECT ContactId FROM  dbo.Contacts WHERE  (LastName = @LastName)\"\n    };\n\n    cmd.Parameters.Add(\"@LastName\", SqlDbType.NVarChar).Value = lastName;\n    Console.WriteLine(cmd.CommandText);\n    cn.Open();\n\n    var reader = cmd.ExecuteReader();\n    if (reader.HasRows)\n    {\n        reader.Read();\n        Console.WriteLine(reader.GetInt32(0));\n    }\n    else\n    {\n        Console.WriteLine(\"No matches\");\n    }\n}\n```\n\nNote the line **Console.WriteLine(cmd.CommandText);** which writes out.\n\n```sql\nSELECT ContactId FROM  dbo.Contacts WHERE  (LastName = @LastName)\n```\n\nWe do not see the actual value for LastName, this is a bonus as there is no room for [SQL-Injection](https://owasp.org/www-community/attacks/SQL_Injection).\n\nSo how do we know if the statement is good? We test by writing the statement in SSMS and we can also run my NuGet package [DbPeekQueryLibrary](https://www.nuget.org/packages/DbPeekQueryLibrary/).\n\n```csharp\ncmd.Parameters.Add(\"@LastName\", SqlDbType.NVarChar).Value = lastName;\nConsole.WriteLine(cmd.ActualCommandText());\ncn.Open();\n```\n\nWhich shows\n\n```sql\nSELECT ContactId FROM  dbo.Contacts WHERE  (LastName = 'Roel''s')\n```\n\n\u003e **Note**\n\u003e When adding parameters as shown above, the order they are added does not matter. If we were working with MS-Access the parameter order matters. If we were working with Oracle, the default is same as MS-Access but using BindByName = true on the command object parameter order does not matter, same as SQL-Server.\n\nThe following example uses a larger SELECT with several JOINS, two WHERE conditions and the parmeters are out of order which is fine as they are by name not ordinal.\n\n```csharp\nstatic void Playground2(int countryIdentifier, int contactTypeIdentifier)\n{\n\n    string connectionString =\n        \"Data Source=(localdb)\\\\MSSQLLocalDB;\" +\n        \"Initial Catalog=NorthWind2022;Integrated Security=True\";\n\n    using var cn = new SqlConnection(connectionString);\n    using var cmd = new SqlCommand\n    {\n        Connection = cn,\n        CommandText = \"\"\"\n            SELECT C.CustomerIdentifier,\n                   C.CompanyName,\n                   C.ContactId,\n                   C.Street,\n                   C.City,\n                   C.PostalCode,\n                   C.CountryIdentifier,\n                   C.Phone,\n                   C.ContactTypeIdentifier,\n                   CT.ContactTitle,\n                   Cont.FirstName,\n                   Cont.LastName\n            FROM dbo.Customers AS C\n                INNER JOIN dbo.Countries AS A\n                    ON C.CountryIdentifier = A.CountryIdentifier\n                INNER JOIN dbo.ContactType AS CT\n                    ON C.ContactTypeIdentifier = CT.ContactTypeIdentifier\n                INNER JOIN dbo.Contacts AS Cont\n                    ON C.ContactId = Cont.ContactId\n                       AND CT.ContactTypeIdentifier = Cont.ContactTypeIdentifier\n            WHERE (C.CountryIdentifier = @CountryIdentifier)\n                  AND (C.ContactTypeIdentifier = @ContactTypeIdentifier);\n            \"\"\"\n    };\n\n    cmd.Parameters.Add(\"@ContactTypeIdentifier\", SqlDbType.NVarChar).Value = contactTypeIdentifier;\n    cmd.Parameters.Add(\"@CountryIdentifier\", SqlDbType.NVarChar).Value = countryIdentifier;\n    Console.WriteLine(cmd.ActualCommandText());\n    cn.Open();\n\n    var reader = cmd.ExecuteReader();\n    if (reader.HasRows)\n    {\n        while (reader.Read())\n        {\n            Console.WriteLine($\"Id: {reader.GetInt32(0),-4} Company: {reader.GetString(1)}\");\n        }\n        \n    }\n    else\n    {\n        Console.WriteLine(\"No matches\");\n    }\n}\n```\n\n## About connections\n\nSince a developer could interact with database there are many who create a global connection, when needed check if the connection is open, if not open and use the connection.\n\nRather go into all the reason this is bad, the recommended approach is to create a new connection when needed and once finished dispose of the connection.\n\nEach of the examples follow this approach. \n\n## About connection strings\n\nDO NOT store connection string in each **SqlConnection**, for ASP.NET Core and Razor Pages we can store the connection string in a json file typically named **appsettings.json**.\n\n```json\n{\n  \"Logging\": {\n    \"LogLevel\": {\n      \"Default\": \"Information\",\n      \"Microsoft.AspNetCore\": \"Warning\"\n    }\n  },\n  \"AllowedHosts\": \"*\",\n  \"ConnectionStrings\": {\n    \"DefaultConnection\": \"Data Source=(localdb)\\\\MSSQLLocalDB;Initial Catalog=NorthWind2022;Integrated Security=True\"\n  }\n}\n```\n\nAnd in Program Main have acccess via\n\n```csharp\nbuilder.Configuration.GetConnectionString(\"DefaultConnection\")\n```\n\nFor other project types create a model for appsettings.json and read the settings or use my NuGet package [ConfigurationLibrary](https://www.nuget.org/packages/ConfigurationLibrary/).\n\nSample appsettings.json\n\n```json\n{\n  \"ConnectionsConfiguration\": {\n    // do not get hung up on the next line it comes from allowing multiple environments\n    \"ActiveEnvironment\": \"Development\",\n    \"Development\": \"Data Source=(localdb)\\\\MSSQLLocalDB;Initial Catalog=NorthWind2022;Integrated Security=True;Encrypt=False\"\n  }\n}\n```\n\nAdd the following using to the class that works with a database.\n\n```csharp\nusing static ConfigurationLibrary.Classes.ConfigurationHelper;\n```\n\nCreate a new connection passing **ConnectionString()** which reads te Development item under ConnectionsConfiguration.\n\n```csharp\nusing var cn = new SqlConnection(ConnectionString());\n```\n\n\u003e **Note**\n\u003e Go to the package source which explains having three connection strings, development, stagging and production.\n\n\n## Placement of SQL statements\n\nIn all the example above the SQL statements were inline with the command object, a better idea is to create a class, in this case SqlStatements.cs and place the SQL in there.\n\nIn the last code sample the SQL took up a lot of space and can only be used in that method.\n\nHere we have a better solution, cleans up your code and is reusable.\n\n\n```csharp\npublic class SqlStatements\n{\n    public static string CustomersByContactTypeAndCountry()\n        =\u003e \"\"\"\n                SELECT C.CustomerIdentifier,\n                       C.CompanyName,\n                       C.ContactId,\n                       C.Street,\n                       C.City,\n                       C.PostalCode,\n                       C.CountryIdentifier,\n                       C.Phone,\n                       C.ContactTypeIdentifier,\n                       CT.ContactTitle,\n                       Cont.FirstName,\n                       Cont.LastName\n                FROM dbo.Customers AS C\n                    INNER JOIN dbo.Countries AS A\n                        ON C.CountryIdentifier = A.CountryIdentifier\n                    INNER JOIN dbo.ContactType AS CT\n                        ON C.ContactTypeIdentifier = CT.ContactTypeIdentifier\n                    INNER JOIN dbo.Contacts AS Cont\n                        ON C.ContactId = Cont.ContactId\n                           AND CT.ContactTypeIdentifier = Cont.ContactTypeIdentifier\n                WHERE (C.CountryIdentifier = @CountryIdentifier)\n                      AND (C.ContactTypeIdentifier = @ContactTypeIdentifier);\n                \"\"\";\n}\n```\n\nCleaner code\n\n```csharp\nusing var cn = new SqlConnection(ConnectionString());\nusing var cmd = new SqlCommand\n{\n    Connection = cn,\n    CommandText = SqlStatements.CustomersByContactTypeAndCountry()\n\n};\n\ncmd.Parameters.Add(\"@ContactTypeIdentifier\", \n    SqlDbType.NVarChar).Value = contactTypeIdentifier;\ncmd.Parameters.Add(\"@CountryIdentifier\", \n    SqlDbType.NVarChar).Value = countryIdentifier;\n\ncn.Open();\n```\n\n## Reading back data\n\nWhen there are more than one record we use **reader.HasRows** to see if there are any records then use a while to iterate data. DO NOT use this if only one record is being returned.\n\n```csharp\nvar reader = cmd.ExecuteReader();\nif (reader.HasRows)\n{\n    while (reader.Read())\n    {\n        Console.WriteLine($\"Id: {reader.GetInt32(0),-4} Company: {reader.GetString(1)}\");\n    }\n}\n```\n\nFor returning a single record, see if there is data then if there is use the Read method of the reader.\n\n```csharp\nvar reader = cmd.ExecuteReader();\nif (reader.HasRows)\n{\n    reader.Read();\n    Console.WriteLine(reader.GetInt32(0));\n}\n```\n\n## Storing/returning data\n\nIn each of the example above all that was done was display data to the console window. \n\nOne option is to return data into a [DataTable](https://learn.microsoft.com/en-us/dotnet/api/system.data.datatable?view=net-7.0). DataTables are okay for desktop projects but generally are too heavy for modern day web applications.\n\nExample which the caller passing an int and we get a DataTable back.\n\n```csharp\nstatic DataTable Playground1DataTable(int top)\n{\n\n    DataTable dt = new DataTable();\n\n    using var cn = new SqlConnection(ConnectionString());\n    using var cmd = new SqlCommand\n    {\n        Connection = cn,\n        CommandText = SqlStatements.ContactDemo()\n    };\n\n    cmd.Parameters.Add(\"@Top\", SqlDbType.NVarChar).Value = top;\n\n    cn.Open();\n\n    dt.Load(cmd.ExecuteReader());\n    return dt;\n}\n```\n\nAnother option, create a model/class which has properties for each column in a SELECT statement.\n\nThe following example uses Dapper.\n\nSELECT statement\n\n```csharp\npublic class SqlStatements\n{\n    public static string ContactDemo() =\u003e\n        \"\"\"\n        SELECT  ContactId,\n                FirstName,\n                LastName,\n                ContactTypeIdentifier  \n        FROM dbo.Contacts \n        WHERE ContactId \u003e @Top\n        \"\"\";\n```\n\nModel to match the SELECT statement\n\n```csharp\npublic class Contact\n{\n    public int ContactId { get; set; }\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n    public int ContactTypeIdentifier { get; set; }\n    public override string ToString() =\u003e LastName;\n}\n```\n\nCode to read\n\n```csharp\nstatic List\u003cContact\u003e Playground1Dapper(int top)\n{\n\n    using SqlConnection cn = new (ConnectionString());\n    // specify our parameter for the WHERE condition\n    var parameters = new { @Top = top };\n\n    List\u003cContact\u003e list = cn.Query\u003cContact\u003e(\n        SqlStatements.ContactDemo(), \n        parameters).ToList();\n\n\n    return list;\n}\n```\n\n## Multiple Result Sets for SQL-Server\n\nLearn how to read reference table from SQL-Server using a single method. What is shown provides an efficient way to either a connection, command objects to read data via a SqlDataReader for conventional work using methods from SqlClient and also Dapper which requires two lines of code to read data and one line of code to store data into list.\n\n### Goal\n\nTo read from three reference table in a modified version of Microsoft NorthWind database, Categories, ContactType and Countries tables.\n\nIn all code samples all records are read from each table, in some cases not all records may be needed, simply change the SQL SELECT statement with a WHERE clause. Also, the same goes for columns.\n\n### Project\n\nSee [GitHubSamples](https://github.com/karenpayneoregon/sql-basics/tree/master/NextResultsApp) (name came from another of this author's work)\n\n\n## Provides code samples\n\nThere is one console project uses to write all the code samples which appeared in this article. All code is in this project.\n\nThere is a Windows Form project where unlike the console project, all data opertions are in a class project.\n\nAnd finally a project which uses Entity Framework which is another console project were all dta operations are in other class project. \n\n## Preparing to run code\n\n- Make sure to use Microsoft Visual Studio 2022\n- Run both scripts in the root of the solution\n\n## Source code\n\nClone the following [GitHub repository](https://github.com/karenpayneoregon/sql-basics).","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarenpayneoregon%2Fsql-basics","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkarenpayneoregon%2Fsql-basics","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkarenpayneoregon%2Fsql-basics/lists"}