Clean history, git LFS
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
+++
|
||||
categories = ["software"]
|
||||
tags = ["mssql"]
|
||||
date = 2024-10-01T14:20:28Z
|
||||
description = ""
|
||||
draft = false
|
||||
slug = "mssql-db-config"
|
||||
title = "💽 Configuring Microsoft SQL Server Database"
|
||||
+++
|
||||
|
||||
I am using Microsoft's Blazor framework to create a Webassembly app that manages my recipes. I began building this web app using an in-memory database, which means the data does not persist after the program stops running. Each time I build, the app rebuilds the database with brand new data. This is annoying and unnecessary now that the general structure of the database has been established. I will transition the app to instead use a database on an on-premise server.
|
||||
|
||||
## Docker
|
||||
As with nearly every other service I host, I will run an instance of SQL Server in a Docker container. Microsoft provides a shell command that will get an instance of Linux-based SQL Server running.
|
||||
|
||||
```shell
|
||||
docker run -e "ACCEPT_EULA=Y" -e "MSSQL_SA_PASSWORD" \
|
||||
-p 1433:1433 --name sql1 --hostname sql1 \
|
||||
-d \
|
||||
mcr.microsoft.com/mssql/server:2022-latest
|
||||
```
|
||||
|
||||
I typically use Docker Compose to manage my services, so I will transform this command into a docker-compose.yml file. There are additional pieces of configuration missing here: timezone, product ID, and data persistence across container restarts. I add them in the final configuration file. Additionally, the System Administrator (user sa) password is stored in a .env file that lives in the root directory of the host.
|
||||
|
||||
|
||||
```shell
|
||||
nicholas@nas:/$ cd /hdd1/nicholas/docker/dockerfiles/
|
||||
nicholas@nas:/hdd1/nicholas/docker/dockerfiles/$ mkdir mssql && cd mssql
|
||||
nicholas@nas:/hdd1/nicholas/docker/dockerfiles/$ nano docker-compose.yml
|
||||
```
|
||||
|
||||
```yaml
|
||||
services:
|
||||
mssql:
|
||||
image: mcr.microsoft.com/mssql/server:2022-latest
|
||||
container_name: mssql
|
||||
hostname: mssql
|
||||
environment:
|
||||
- ACCEPT_EULA=Y
|
||||
- MSSQL_SA_PASSWORD=${MSSQL_SA_PASSWORD}
|
||||
- TZ=America/Chicago
|
||||
restart: always
|
||||
volumes:
|
||||
- ./data:/var/opt/mssql/data
|
||||
- ./logs:/var/opt/mssql/log
|
||||
- ./secrets:/var/opt/mssql/secrets
|
||||
ports:
|
||||
- "1433:1433"
|
||||
```
|
||||
The container runs as user mssql, so I will need to give my container directory permissions to this user.
|
||||
|
||||
```shell
|
||||
sudo chown -R 10001:10001 ./mssql/
|
||||
sudo chmod -R 755 ./mssql/
|
||||
```
|
||||
|
||||
Next I start the container.
|
||||
|
||||
```shell
|
||||
sudo docker compose up -d
|
||||
[+] Running 2/4
|
||||
⠙ mssql [⣿⡀⣿] 168.3MB / 578.3MB Pulling 11.2s
|
||||
✔ 3d29464607d8 Pull complete 5.9s
|
||||
⠙ b14d3f02d050 Downloading [======> ]... 10.1s
|
||||
✔ c4ebe057087c Download complete 3.4sl
|
||||
```
|
||||
|
||||
The server is ready to accept connections now.
|
||||
|
||||
|
||||
## Microsoft SQL Server Management Studio
|
||||
|
||||
I will be using SSMS to connect to and configure the server.
|
||||
|
||||
The server name is the host name of my server, which is nas. I already configured the login credentials in the docker-compose.yml and .env files.
|
||||
|
||||
Here I create a database Formulation and the two tables: Ingredient and IngredientRelationship. Like I said, this could have been a spreadsheet.
|
||||
|
||||
|
||||
```sql
|
||||
CREATE DATABASE Formulation;
|
||||
|
||||
USE Formulation;
|
||||
|
||||
CREATE TABLE Ingredient (
|
||||
Id UNIQUEIDENTIFIER PRIMARY KEY,
|
||||
Name NVARCHAR(100) NOT NULL,
|
||||
Calories FLOAT NOT NULL,
|
||||
Protein FLOAT NOT NULL,
|
||||
Fat FLOAT NOT NULL,
|
||||
Carbohydrates FLOAT NOT NULL,
|
||||
Fiber FLOAT NOT NULL,
|
||||
Sugar FLOAT NOT NULL,
|
||||
Sodium FLOAT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IngredientRelationship (
|
||||
Id UNIQUEIDENTIFIER PRIMARY KEY,
|
||||
ParentId UNIQUEIDENTIFIER NOT NULL,
|
||||
ChildId UNIQUEIDENTIFIER NOT NULL,
|
||||
Proportion FLOAT NOT NULL,
|
||||
FOREIGN KEY (ParentId) REFERENCES Ingredient(Id),
|
||||
FOREIGN KEY (ChildId) REFERENCES Ingredient(Id)
|
||||
);
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Mapping Entity Classes to Database Tables with Entity Framework
|
||||
|
||||
I am using Entity Framework in my web app, so I needed to define my database structure so that it mirrored the C# Entity Class I already defined. The Ingredient table in the database maps onto the C# Entity Class Ingredient.cs. The same is true for the IngredientRelationship table and IngredientRelationship.cs.
|
||||
|
||||
```c#
|
||||
namespace formulation.Common.Models
|
||||
{
|
||||
[Table("Ingredient")]
|
||||
public class Ingredient
|
||||
{
|
||||
[Key]
|
||||
public Guid Id { get; set; }
|
||||
[Required]
|
||||
[StringLength(100, ErrorMessage = "Name cannot be longer than 100 characters.")]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
public double Calories { get; set; } // in kcal
|
||||
public double Protein { get; set; } // in grams
|
||||
public double Fat { get; set; } // in grams
|
||||
public double Carbohydrates { get; set; } // in grams
|
||||
public double Fiber { get; set; } // in grams
|
||||
public double Sugar { get; set; } // in grams
|
||||
public double Sodium { get; set; } // in milligrams
|
||||
|
||||
public ICollection? Ingredients { get; set; }
|
||||
public ICollection? ParentIngredients { get; set; }
|
||||
public Ingredient()
|
||||
{
|
||||
Ingredients = new List();
|
||||
ParentIngredients = new List();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Create SQL Server User for App Connection
|
||||
|
||||
Now I need to create a dedicated SQL Server user for my app – one with only limited privileges. It is best practice to abide by the principle of least privilege, which states that a user should be granted the minimum level of access necessary to perform the required function. Therefore it is not recommended to use the sa user or any high-privilege accounts for an app connection.
|
||||
|
||||
```sql
|
||||
CREATE LOGIN Formulation WITH PASSWORD = '';
|
||||
USE Formulation;
|
||||
CREATE USER Formulation FOR LOGIN Formulation;
|
||||
EXEC sp_addrolemember 'db_datareader', 'Formulation'; -- Read permissions
|
||||
EXEC sp_addrolemember 'db_datawriter', 'Formulation'; -- Write permissions
|
||||
```
|
||||
|
||||
|
||||
## Connection Strings & Secrets
|
||||
|
||||
The next step is to configure a new Connected Service in Visual Studio. First I select the correct service dependency: SQL Server Database - On-premise
|
||||
|
||||
Next I enter the login credentials User name, Password, Server name, Database name which were configured in the steps above. I click test connection to verify that this is working as expected. It is successful.
|
||||
|
||||
Visual Studio automatically writes the connection string using this information. I save the connection strings in a local secrets file Secrets.json.
|
||||
|
||||
|
||||
## Using the App Connection User
|
||||
|
||||
Finally I can reference this connection string in Program.cs to connect to the SQL Server.
|
||||
|
||||
```c#
|
||||
builder.Services.AddDbContext(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("Formulation")));
|
||||
```
|
||||
|
||||
Done.
|
||||
Reference in New Issue
Block a user