PI06 i PI06-1. Docker definitions for MSSQL and Postgres. Data seeder/generator for countries and people. Entity Framework example with variants for Postgres and MSSQL

This commit is contained in:
Boris Milašinović
2026-04-19 16:49:07 +02:00
parent 44a663e170
commit 6f56d107a2
89 changed files with 7305 additions and 0 deletions

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,53 @@
using System.Diagnostics;
namespace Barricades;
class Course(string name)
{
public string Name { get; private set; } = name;
//Consider why is dictionary private variable, and why not a property!
//indexer serves as a barricade
private Dictionary<string, int> grades = new Dictionary<string, int>();
/// <summary>
/// Assign the new grade to the student.
/// Old value (if had existed) is replaced.
/// </summary>
/// <param name="name">Student's name</param>
/// <returns>The student's grade or -1 if the student does not have a grade</returns>
/// <exception cref="System.ArgumentOutOfRangeException">Thrown if value is not valid grade [1-5]</exception>
public int this[string name]
{
get
{
//what if there is no student's name in the dictionary
//throw exception, return special value? See error handling techniques
bool exists = grades.TryGetValue(name, out int grade);
return exists ? grade : -1;
}
set
{
if (value < 1 || value > 5)
{
throw new ArgumentOutOfRangeException($"Invalid grade {value}. It shoud be from 1 to 5");
}
grades[name] = value;
}
}
public double AverageGrade()
{
int sum = 0;
foreach (var pair in grades)
{
int grade = pair.Value;
Debug.Assert(grade >= 1 && grade <= 5,
$"Invalid grade in dictionary {pair.Key} = {pair.Value}. This should never happens!");
sum += grade;
}
return grades.Count > 0 ? (double)sum / grades.Count : 0;
}
}

View File

@@ -0,0 +1,14 @@
using Barricades;
using System.Text;
Console.OutputEncoding = Encoding.UTF8;
Course c = new Course("PI");
c["Pero"] = 2;
c["Ana"] = 5;
c["Ivan"] = 5;
c["Marko"] = 1;
c["Luka"] = 3;
//c["Marija"] = -7; //should throw an exception
Console.WriteLine("Average grade: " + c.AverageGrade());
Console.WriteLine("Šime has grade: " + c["Šime"]); //prints -1

View File

@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DefineConstants>TRACE;DEMO</DefineConstants>
</PropertyGroup>
</Project>

View File

@@ -0,0 +1,20 @@
#define DEMO2
using System.Diagnostics;
Console.WriteLine("Start");
#if DEMO
Console.WriteLine("Print something...");
#endif
CheckSomething();
Console.WriteLine("End");
[Conditional("DEMO")]
static void CheckSomething()
{
Console.WriteLine("Print from CheckSomething method");
}

View File

@@ -0,0 +1,49 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30413.136
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Barricades", "Barricades\Barricades.csproj", "{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Conditional", "Conditional\Conditional.csproj", "{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Using", "Using\Using.csproj", "{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Logging", "Logging\Logging.csproj", "{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Secrets", "Secrets\Secrets.csproj", "{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EC679AD5-7BC4-49E3-B44C-DB4750FBC179}.Release|Any CPU.Build.0 = Release|Any CPU
{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}.Debug|Any CPU.Build.0 = Debug|Any CPU
{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}.Release|Any CPU.ActiveCfg = Release|Any CPU
{997943C5-5BA1-4BC4-A2C9-AAE0CA3BB934}.Release|Any CPU.Build.0 = Release|Any CPU
{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{227D0323-D09C-44D5-B1CD-C2DD27CFC4DB}.Release|Any CPU.Build.0 = Release|Any CPU
{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7534F26-8855-4CA0-BEBC-C60ED2D1E7B1}.Release|Any CPU.Build.0 = Release|Any CPU
{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}.Release|Any CPU.ActiveCfg = Release|Any CPU
{78BFB6E2-A394-4CA1-A947-0CC2D8CDE421}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0C808F1C-DC45-4A56-8D42-13C593C59A78}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,60 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
namespace Logging;
public class DataLoader : IDataLoader
{
private readonly ILogger<DataLoader> logger;
public DataLoader(ILogger<DataLoader> logger)
{
this.logger = logger;
}
public List<(string, DateOnly)> LoadData(string filename)
{
List<(string, DateOnly)> list = new();
if (!File.Exists(filename))
{
string message = $"File {filename} does not exist";
logger.LogError(message);
throw new FileNotFoundException(message);
}
else
{
//use File.ReadAllLines for small files
using (var reader = new StreamReader(File.OpenRead(filename)))
{
string? line;
while((line = reader.ReadLine()) != null) {
string pattern = "[0-3]?[0-9].[0-3]?[0-9].[0-9]{4}.";
var match = Regex.Match(line, pattern);
if (match.Success)
{
string datetext = line.Substring(match.Index, match.Length);
if (DateOnly.TryParseExact(datetext, "d.M.yyyy.", CultureInfo.InvariantCulture, DateTimeStyles.None, out DateOnly date))
{
var item = (line.Remove(match.Index, match.Length).Trim(), date);
logger.LogTrace(item.ToString());
list.Add(item);
}
else
{
logger.LogWarning("Invalid date in line: " + line);
}
}
else
{
logger.LogWarning("No date found in line: " + line);
}
}
}
}
return list;
}
}

View File

@@ -0,0 +1,9 @@
using System;
using System.Collections.Generic;
namespace Logging;
public interface IDataLoader
{
List<(string, DateOnly)> LoadData(string filename);
}

View File

@@ -0,0 +1,30 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.6" />
<PackageReference Include="NLog" Version="6.1.2" />
<PackageReference Include="NLog.Extensions.Logging" Version="6.1.2" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="data.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="nlog.config">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,45 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using NLog.Extensions.Logging;
using System;
using System.Linq;
namespace Logging;
class Program
{
static void Main(string[] args)
{
using (var serviceProvider = BuildDI())
{
var dataLoader = serviceProvider.GetRequiredService<IDataLoader>();
var list = dataLoader.LoadData("data.txt");
var sortQuery = list.OrderByDescending(t => t.Item2)
.ThenBy(t => t.Item1);
Console.WriteLine("Valid data: ");
foreach (var item in sortQuery)
{
Console.WriteLine($"{item.Item2:yyyy-MM-dd} {item.Item1}");
}
}
}
private static ServiceProvider BuildDI()
{
var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
IServiceCollection services = new ServiceCollection();
var provider = services.AddLogging(configure => {
configure.AddConfiguration(configuration.GetSection("Logging"));
configure.AddConsole();
configure.AddNLog(new NLogProviderOptions { RemoveLoggerFactoryFilter = false });
})
.AddTransient<IDataLoader, DataLoader>()
.BuildServiceProvider();
return provider;
}
}

View File

@@ -0,0 +1,9 @@
{
"Logging": {
"LogLevel": {
"Default": "Trace",
"System": "Warning",
"Microsoft": "Trace"
}
}
}

View File

@@ -0,0 +1,5 @@
Ana 22.5.1988.
01.03.1995. Ivan John
Ema
Mario 15.13.1980.
Klara 11.07.1998.

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
autoReload="true"
internalLogLevel="Warn"
internalLogFile="logs/internal-nlog.txt">
<!-- the targets to write to -->
<targets>
<!-- write logs to file -->
<target xsi:type="File" name="allfile" fileName="logs/nlog-all-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${logger}|${uppercase:${level}}|${message} ${exception}" />
<!-- another file log, only own logs. Uses some ASP.NET core renderers -->
<target xsi:type="File" name="ownFile" fileName="logs/nlog-own-${shortdate}.log"
layout="${longdate}|${event-properties:item=EventId_Id:whenEmpty=0}|${logger}|${uppercase:${level}}| ${message} ${exception}" />
<!-- write to the void aka just remove -->
<target xsi:type="Null" name="blackhole" />
</targets>
<!-- rules to map from logger name to target -->
<rules>
<!--All logs, including from Microsoft-->
<logger name="*" minlevel="Trace" writeTo="allfile" />
<!--Skip Microsoft logs and so log only own logs-->
<logger name="Microsoft.*" minlevel="Trace" writeTo="blackhole" final="true" />
<logger name="*" minlevel="Information" writeTo="ownFile" />
</rules>
</nlog>

View File

@@ -0,0 +1,8 @@
namespace Secrets;
public class Demo
{
public int Key0 { get; set; }
public string? Key1 { get; set; }
public string? Key2 { get; set; }
}

View File

@@ -0,0 +1,23 @@
using Microsoft.Extensions.Configuration;
using Secrets;
var enumerator = Environment.GetEnvironmentVariables().GetEnumerator();
while (enumerator.MoveNext())
{
Console.WriteLine($"{enumerator.Key} = {enumerator.Value}");
}
Console.WriteLine("----------------------------");
IConfiguration configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json") //order is important!
.AddJsonFile("missing_one.json", optional: true) //
.AddEnvironmentVariables() // include Package Microsoft.Extensions.Configuration.EnvironmentVariables
.AddUserSecrets("PI-Secrets") //package Microsoft.Extensions.Configuration.UserSecrets
.Build();
Console.WriteLine($"Custom env variable using configuration = " + configuration["CustomEnvValue"]);
Console.WriteLine($"Name = " + configuration["Name"]);
Console.WriteLine($"Key0 = " + configuration["Demo:Key0"]); //or Demo__Key0
Demo? demo = configuration.GetSection("Demo").Get<Demo>(); // package Microsoft.Extensions.Configuration.Binder
Console.WriteLine($"Key1 = " + demo?.Key1);
Console.WriteLine($"Key2 = " + demo?.Key2);

View File

@@ -0,0 +1,10 @@
{
"profiles": {
"Secrets": {
"commandName": "Project",
"environmentVariables": {
"CustomEnvValue": "Demo"
}
}
}
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>PI</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="10.0.6" />
<PackageReference Include="Microsoft.Extensions.Configuration.UserSecrets" Version="10.0.6" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,7 @@
{
"Name": "Bob",
"Demo": {
"Key0": 123,
"Key1": "something that should not be publicly available"
}
}

View File

@@ -0,0 +1,16 @@
namespace Using;
class C : IDisposable
{
public string Id { get; set; }
public void Dispose()
{
Console.WriteLine("** {0} : Dispose **", Id);
}
public C(string id)
{
Id = id;
Console.WriteLine("----> {0} : Ctor", Id);
}
}

View File

@@ -0,0 +1,20 @@
using Using;
try
{
C a1 = new C("A1");
using (C b2 = new C("B2"))
using (C d4 = new C("D4"))
{
C c3 = new C("C3");
throw new Exception("It is time for an exception");
}
a1.Dispose();
}
catch (Exception exc)
{
Console.WriteLine("Exc: " + exc.Message);
//throw exc;
//throw;
}

View File

@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>