Best way to use HttpClient

// in Program.cs
builder.Services.AddSingleton<IWeatherClient, OpenWeatherClient>();
builder.Services.AddHttpClient("openWeatherApi", client =>
{
    client.BaseAddress = new Uri("https://api.openweathermap.org/data/2.5/");
});

// in OpenWeatherClient.cs
public class OpenWeatherClient : IWeatherClient
{
    private const string OpenWeatherMapApiKey = "";
    private readonly IHttpClientFactory _httpClientFactory;

    public OpenWeatherClient(IHttpClientFactory httpClientFactory)
    {
        _httpClientFactory = httpClientFactory;
    }

    public async Task<WeatherResponse?> GetCurrentWeatherForCity(string city)
    {
        var client = _httpClientFactory.CreateClient("openWeatherApi");
        return await client.GetFromJsonAsync<WeatherResponse>(
            $"weather?q={city}&appid={OpenWeatherMapApiKey}");
    }
}
// in Program.cs
 builder.Services.AddHttpClient<IWeatherClient, OpenWeatherClient>(client =>
 {
     client.BaseAddress = new Uri("https://api.openweathermap.org/data/2.5/");
 });

// in OpenWeatherClient.cs
public class OpenWeatherClient : IWeatherClient
{
    private const string OpenWeatherMapApiKey = "";
    private readonly HttpClient _httpClient;

    public OpenWeatherClientSecond(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<WeatherResponse?> GetCurrentWeatherForCity(string city)
    {
        return await _httpClient.GetFromJsonAsync<WeatherResponse>(
            $"weather?q={city}&appid={OpenWeatherMapApiKey}");
    }
}

C# From String to Stream, and From Stream to String

static void Main( string[] args )
{
    string test = "My sample text";

    // convert string to stream
    byte[] byteArray = Encoding.ASCII.GetBytes( test );
    MemoryStream stream = new MemoryStream( byteArray ); 

    // convert stream to string
    StreamReader reader = new StreamReader( stream );
    string text = reader.ReadToEnd();

    Console.WriteLine( text );
    Console.ReadLine();
}
static void Main( string[] args )
{
    string test = "My sample text";

    // convert string to stream
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter( stream );
    writer.Write( test );
    writer.Flush();

    // convert stream to string
    stream.Position = 0;
    StreamReader reader = new StreamReader( stream );
    string text = reader.ReadToEnd();
}

Oh My Posh

Official website: https://ohmyposh.dev/

NerdFonts: https://www.nerdfonts.com/font-downloads

Installation (https://www.hanselman.com/blog/my-ultimate-powershell-prompt-with-oh-my-posh-and-the-windows-terminal)


Install-Module -Name Terminal-Icons -Repository PSGallery
Install-Module -Name oh-my-posh -RequiredVersion 6.17.0
Get-Module oh-my-posh
Import-Module oh-my-posh
oh-my-posh --init --shell pwsh --config jandedobbleer.omp.json | Invoke-Expression

Microsoft.PowerShell_profile.ps1

Import-Module -Name Terminal-Icons
Import-Module oh-my-posh
cd Powershell/Modules/oh-my-posh
./oh-my-posh --init --shell pwsh --config C:\Users\...\PowerShell\Modules\oh-my-posh\6.17.0\themes\ohmyposhv3-v2.json | Invoke-Expression

Linq Grouping examples

I have a collection of items, here it is:

AgencyID VendorID StateID Amount Fee
1        1        1       20.00  5.00
1        1        1       10.00  2.00
1        1        1       30.00  8.00    
2        2        1       20.00  5.00
2        2        1       5.00   5.00
1        1        2       20.00  5.00
2        2        2       20.00  5.00
2        2        2       40.00  9.00
1        2        2       35.00  6.00
1        2        2       12.00  3.00

I’d like these items to be grouped based on the AgencyID, VendorID, and StateID, and the Total calculated from Amount and Fee (Amount + Fee)

So using the data above, I’d like to have these results:

AgencyID VendorID StateID Total
1        1        1       75.00    
2        2        1       35.00
1        1        2       25.00
2        2        2       74.00
1        2        2       56.00
var agencyContracts = _agencyContractsRepository.AgencyContracts
    .GroupBy(ac => new
                   {
                       ac.AgencyContractID, // required by your view model. should be omited
                                            // in most cases because group by primary key
                                            // makes no sense.
                       ac.AgencyID,
                       ac.VendorID,
                       ac.RegionID
                   })
    .Select(ac => new AgencyContractViewModel
                   {
                       AgencyContractID = ac.Key.AgencyContractID,
                       AgencyId = ac.Key.AgencyID,
                       VendorId = ac.Key.VendorID,
                       RegionId = ac.Key.RegionID,
                       Amount = ac.Sum(acs => acs.Amount),
                       Fee = ac.Sum(acs => acs.Fee)
                   });

Second Example

public class ConsolidatedChild
{
    public string School { get; set; }
    public string Friend { get; set; }
    public string FavoriteColor { get; set; }
    public List<Child> Children { get; set; }
}

public class Child
{
    public string School { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string Friend { get; set; }
    public string Mother { get; set; }
    public string FavoriteColor { get; set; }
}
var consolidatedChildren =
    from c in children
    group c by new
    {
        c.School,
        c.Friend,
        c.FavoriteColor,
    } into gcs
    select new ConsolidatedChild()
    {
        School = gcs.Key.School,
        Friend = gcs.Key.Friend,
        FavoriteColor = gcs.Key.FavoriteColor,
        Children = gcs.ToList(),
    };

var consolidatedChildren =
    children
        .GroupBy(c => new
        {
            c.School,
            c.Friend,
            c.FavoriteColor,
        })
        .Select(gcs => new ConsolidatedChild()
        {
            School = gcs.Key.School,
            Friend = gcs.Key.Friend,
            FavoriteColor = gcs.Key.FavoriteColor,
            Children = gcs.ToList(),
        });

MongoDB Test projections

I have to test this:

using MongoDB;
using MongoDB.Driver;
using MongoDB.Bson;
using MongoDB.Driver.Builders;

var connectionString = "mongodb://user:pass@xxx.mongolab.com:53139/xxx";
var client = new MongoClient(connectionString);
var server = client.GetServer();
var database = server.GetDatabase("xxx");
                    
var courses = database.GetCollection("Course");

//Option A (without any where clause)
 var course =courses.FindAllAs<Course>().SetFields(Fields.Include("Title", "Description").Exclude("_id")).ToList();
 //Option B (with where clause)
 var course = courses.FindAs<Course>(MongoDB.Driver.Builders.Query.EQ("Title", "Todays Course")).SetFields(Fields.Include("Title", "Description").Exclude("_id")).ToList();
 
 //Option C (with where clause). Its an alternative approach of Option B.
  var query = new QueryDocument("Title", "Todays Course");
  var course = courses.FindAs<Course>(query1).SetFields("Title","Description").ToList();

C# MongoDB tutorial

MongoDB

MongoDB is a NoSQL cross-platform document-oriented database. It is one of the most popular databases available. MongoDB is developed by MongoDB Inc. and is published as free and open-source software.https://ff935c8ad059c2ed9c66a1f419887c88.safeframe.googlesyndication.com/safeframe/1-0-38/html/container.html

A record in MongoDB is a document, which is a data structure composed of field and value pairs. MongoDB documents are similar to JSON objects. The values of fields may include other documents, arrays, and arrays of documents. MongoDB stores documents in collections. Collections are analogous to tables in relational databases and documents to rows.

MongoDB represents JSON documents in binary-encoded format called BSON behind the scenes. BSON extends the JSON model to provide additional data types, ordered fields, and to be efficient for encoding and decoding within different languages. The .NET driver uses BsonDocument to represent BSON.

MongoDB.Driver

MongoDB.Driver is the fficial .NET driver for MongoDB.

$ dotnet add package MongoDB.Driver

We need to add the MongoDB.Driver package to each .NET Core project.

MongoDB create database

The mongo tool is an interactive JavaScript shell interface to MongoDB, which provides an interface for systems administrators as well as a way for developers to test queries and operations directly with the database.

$ mongo testdb
MongoDB shell version v4.0.7
connecting to: mongodb://127.0.0.1:27017/testdb?gssapiServiceName=mongodb
...
> db
testdb
> db.cars.insert({name: "Audi", price: 52642})
> db.cars.insert({name: "Mercedes", price: 57127})
> db.cars.insert({name: "Skoda", price: 9000})
> db.cars.insert({name: "Volvo", price: 29000})
> db.cars.insert({name: "Bentley", price: 350000})
> db.cars.insert({name: "Citroen", price: 21000})
> db.cars.insert({name: "Hummer", price: 41400})
> db.cars.insert({name: "Volkswagen", price: 21600})

We create a testdb database and insert eight documents in the cars collection. We will work with this data in the tutorial.

C# MongoDB list databases

The first example connects to the MongoDB server and retrieves its databases.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace SimpleEx
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");
            var dbList = dbClient.ListDatabases().ToList();

            Console.WriteLine("The list of databases are:");

            foreach (var item in dbList)
            {
                Console.WriteLine(item);
            }
        }
    }
}

The example connects to the MongoDB server and retrieves all its databases.

var dbClient = new MongoClient("mongodb://127.0.0.1:27017");

MongoClient class is used to connect to the MongoDB server. The 27017 is the default port on which the MongoDB server listens.

var dbList = dbClient.ListDatabases().ToList();

We get the list of the databases with the ListDatabases method.

foreach (var item in dbList)
{
    Console.WriteLine(item);
}

We go through the list and print the items.

$ dotnet run
The list of databases are:
{ "name" : "admin", "sizeOnDisk" : 32768.0, "empty" : false }
{ "name" : "config", "sizeOnDisk" : 86016.0, "empty" : false }
{ "name" : "local", "sizeOnDisk" : 81920.0, "empty" : false }
{ "name" : "test", "sizeOnDisk" : 212992.0, "empty" : false }
{ "name" : "testdb", "sizeOnDisk" : 155648.0, "empty" : false }

C# MongoDB RunCommand

The RunCommand method runs a command on the database.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace MongoCommand
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");

            IMongoDatabase db = dbClient.GetDatabase("testdb");

            var command = new BsonDocument { { "dbstats", 1 } };
            var result = db.RunCommand<BsonDocument>(command);
            Console.WriteLine(result.ToJson());
        }
    }
}

The example connects to the testdb database and gets its statistics.

IMongoDatabase db = dbClient.GetDatabase("testdb");

We get the database with the GetDatabase method.

var command = new BsonDocument { { "dbstats", 1 } };
var result = db.RunCommand<BsonDocument>(command);

With the RunCommand method, we execute the dbstats command. The command returns a document which is a representation of a MongoDB document.

Console.WriteLine(result.ToJson());

We print the document to the console in the JSON format.

$ dotnet run
{ "db" : "testdb", "collections" : 3, "views" : 0, "objects" : 15, "avgObjSize" : 57.0,
"dataSize" : 855.0, "storageSize" : 77824.0, "numExtents" : 0, "indexes" : 3,
"indexSize" : 77824.0, "fsUsedSize" : 160688828416.0, "fsTotalSize" : 254721126400.0, "ok" : 1.0 }

C# MongoDB find document

We query for a document with a specific filter. The filter is given to the Find method, which looks for a document applying the given filter.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace FindDocument
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");

            IMongoDatabase db = dbClient.GetDatabase("testdb");
            var cars = db.GetCollection<BsonDocument>("cars");

            var filter = Builders<BsonDocument>.Filter.Eq("price", 29000);

            var doc = cars.Find(filter).FirstOrDefault();
            Console.WriteLine(doc.ToString());
        }
    }
}

The example finds a document with a car whose price is 29000.

var cars = db.GetCollection<BsonDocument>("cars");

We get the cars collection with the GetCollection method.

var filter = Builders<BsonDocument>.Filter.Eq("price", 29000);

A filter is created; we look for a car with price equal to 29000.

var doc = cars.Find(filter).FirstOrDefault();

We pass the filter to the Find method and retrieve it with the FirstOrDefault method.

$ dotnet run
{ "_id" : ObjectId("5d4d1408463315268eb7376e"), "name" : "Volvo", "price" : 29000.0 }

C# MongoDB find all documents

If we do not specify a filter condition for the Find method, we get all documents.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace FindAll
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");
            IMongoDatabase db = dbClient.GetDatabase("testdb");

            var cars = db.GetCollection<BsonDocument>("cars");
            var documents = cars.Find(new BsonDocument()).ToList();

            foreach (BsonDocument doc in documents)
            {
                Console.WriteLine(doc.ToString());
            }
        }
    }
}

The example retrieves all documents from the cars collection.

$ dotnet run
{ "_id" : ObjectId("5d4d13d6463315268eb7376b"), "name" : "Audi", "price" : 52000 }
{ "_id" : ObjectId("5d4d13f5463315268eb7376c"), "name" : "Mercedes", "price" : 57127.0 }
{ "_id" : ObjectId("5d4d1408463315268eb7376e"), "name" : "Volvo", "price" : 29000.0 }
{ "_id" : ObjectId("5d4d140d463315268eb7376f"), "name" : "Bentley", "price" : 350000.0 }
{ "_id" : ObjectId("5d4d1411463315268eb73770"), "name" : "Citroen", "price" : 21000.0 }
{ "_id" : ObjectId("5d4d1415463315268eb73771"), "name" : "Hummer", "price" : 41400.0 }
{ "_id" : ObjectId("5d4d1419463315268eb73772"), "name" : "Volkswagen", "price" : 21600.0 }

We get all seven documents.

C# MongoDB query

Mongo support several query filtering operators such as GtLt, or Gte.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace MongoQuery
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");

            IMongoDatabase db = dbClient.GetDatabase("testdb");
            var cars = db.GetCollection<BsonDocument>("cars");

            var builder = Builders<BsonDocument>.Filter;
            var filter = builder.Gt("price", 30000) & builder.Lt("price", 55000);

            var docs = cars.Find(filter).ToList();

            docs.ForEach(doc => {
                Console.WriteLine(doc);
            });
        }
    }
}

The example prints all documents whose car prices are between 30000 and 55000.

var filter = builder.Gt("price", 30000) & builder.Lt("price", 55000);

We build a filter with two operators: Gt and Lt.

$ dotnet run
{ "_id" : ObjectId("5d4d13d6463315268eb7376b"), "name" : "Audi", "price" : 52000 }
{ "_id" : ObjectId("5d4d1415463315268eb73771"), "name" : "Hummer", "price" : 41400.0 }

We have found two documents that match the criteria.

C# MongoDB insert document

A new document is inserted into the collection with the InsertOne method.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace InsertDocument
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");

            IMongoDatabase db = dbClient.GetDatabase("testdb");

            var cars = db.GetCollection<BsonDocument>("cars");

            var doc = new BsonDocument
            {
                {"name", "BMW"},
                {"price", 34621}
            };

            cars.InsertOne(doc);
        }
    }
}

The example inserts a new car document into the cars collection.

var doc = new BsonDocument
{
    {"name", "BMW"},
    {"price", 34621}
};

A new BsonDocument is created.

cars.InsertOne(doc);

The document is inserted into the collection with the InsertOne method.

C# MongoDB skip and limit

The limit query option specifies the number of documents to be returned and the skip option skips the specified number of documents.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace LimitSkip
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");

            IMongoDatabase db = dbClient.GetDatabase("testdb");

            var cars = db.GetCollection<BsonDocument>("cars");
            var docs = cars.Find(new BsonDocument()).Skip(3).Limit(3).ToList();

            docs.ForEach(doc =>
            {
                Console.WriteLine(doc);
            });
        }
    }
}

The example reads from the testdb.cars collection, skips the first three documents, and limits the output to three documents.

$ dotnet run
{ "_id" : ObjectId("5d4d140d463315268eb7376f"), "name" : "Bentley", "price" : 350000.0 }
{ "_id" : ObjectId("5d4d1411463315268eb73770"), "name" : "Citroen", "price" : 21000.0 }
{ "_id" : ObjectId("5d4d1415463315268eb73771"), "name" : "Hummer", "price" : 41400.0 }

The output contains three documents.

C# MongoDB projections

Projections determine which fields are going to be included in the query output.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace Projections
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");

            IMongoDatabase db = dbClient.GetDatabase("testdb");

            var cars = db.GetCollection<BsonDocument>("cars");
            var docs = cars.Find(new BsonDocument()).Project("{_id: 0}").ToList();

            docs.ForEach(doc =>
            {
                Console.WriteLine(doc);
            });
        }
    }
}

In the example, we find all documents. We exclude the _id from the output.

$ dotnet run
{ "name" : "Audi", "price" : 52000 }
{ "name" : "Mercedes", "price" : 57127.0 }
{ "name" : "Volvo", "price" : 29000.0 }
{ "name" : "Bentley", "price" : 350000.0 }
{ "name" : "Citroen", "price" : 21000.0 }
{ "name" : "Hummer", "price" : 41400.0 }
{ "name" : "Volkswagen", "price" : 21600.0 }

The output of the documents does not contain the _id field.

C# MongoDB delete document

A document is deleted with the deleteOne method.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace DeleteDocument
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");

            IMongoDatabase db = dbClient.GetDatabase("testdb");

            var cars = db.GetCollection<BsonDocument>("cars");
            var filter = Builders<BsonDocument>.Filter.Eq("name", "BMW");

            cars.DeleteOne(filter);
        }
    }
}

The example deletes a document whose car name is BMW.

C# MongoDB update document

A document is updated with the UpdateOne method.Program.cs

using MongoDB.Driver;
using MongoDB.Bson;

namespace UpdateDocument
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbClient = new MongoClient("mongodb://127.0.0.1:27017");

            IMongoDatabase db = dbClient.GetDatabase("testdb");

            var cars = db.GetCollection<BsonDocument>("cars");

            var filter = Builders<BsonDocument>.Filter.Eq("name", "Audi");
            var update = Builders<BsonDocument>.Update.Set("price", 52000);

            cars.UpdateOne(filter, update);
        }
    }
}

The example updates a car document whose name is Audi. It sets a new price for this document.

cars.UpdateOne(filter, update);

The UpdateOne method takes a filter to find the exact document and the update operation to perform the actual change.

In this tutorial, we have worked with MongoDB in C#.

Bson mapper C#

MongoDB.Bson (2.5+) has support to map between BsonValues and .Net objects. BsonTypeMapper Class

Mapper source is here https://github.com/mongodb/mongo-csharp-driver/blob/3c2ddae9d5f0840a9723e723fba58598a59b0791/src/MongoDB.Bson/ObjectModel/BsonTypeMapper.cs#L426

To map a BsonValue (or BsonDocument) to .Net object use

var dotNetObj = BsonTypeMapper.MapToDotNetValue(bsonDoc);

You can then use your choice of serialization library. For example,

JsonConvert.SerializeObject(dotNetObj);

If you have a List of BsonDocument

var dotNetObjList = bsonDocList.ConvertAll(BsonTypeMapper.MapToDotNetValue);

Serilog

            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                //.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", true)
                .Build();

        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(configuration)
            .Enrich.WithEnvironmentName()
            .Enrich.WithProperty("ApplicationNameaaaa", "my application")
            .CreateLogger();
            var configuration = new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json")
                //.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", true)
                .Build();

        Log.Logger = new LoggerConfiguration()
            .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
            .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning)
            .Enrich.FromLogContext()
            .WriteTo.Console(new RenderedCompactJsonFormatter())
            .WriteTo.Debug(new RenderedCompactJsonFormatter())
            .WriteTo.File(
                new RenderedCompactJsonFormatter(),
                @"./logs/log-.txt",
                fileSizeLimitBytes: 1_000_000,
                rollOnFileSizeLimit: true,
                shared: true,
                flushToDiskInterval: TimeSpan.FromSeconds(1))
            .CreateLogger();

in appsettings.json:

  "Serilog": {
    "MinimumLevel": {
      "Default": "Information",
      "Override": {
        "Microsoft": "Warning",
        "System": "Warning",
        "System.Net.Http.HttpClient": "Warning"
      }
    },
    "WriteTo": [
      {
        "Name": "Debug",
        "Args": {
          "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
          "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact"
        }
      },
      {
        "Name": "Console",
        "Args": {
          "theme": "Serilog.Sinks.SystemConsole.Themes.AnsiConsoleTheme::Code, Serilog.Sinks.Console",
          "formatter": "Serilog.Formatting.Compact.RenderedCompactJsonFormatter, Serilog.Formatting.Compact"
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "Contains(SourceContext, 'nomeapp') and (@Level = 'Error' or @Level = 'Fatal' or @Level = 'Warning')"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "Logs/Error/applog_.json",
                  "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
                  "rollingInterval": "Day",
                  "retainedFileCountLimit": 7
                }
              },
              {
                "Name": "MongoDBBson",
                "Args": {
                  "databaseUrl": "mongodb://user:password@server1,server2,server3/database?authSource=admin&replicaSet=database",
                  "collectionName": "logs",
                  "cappedMaxSizeMb": "1024",
                  "cappedMaxDocuments": "50000"
                }
              }
            ]
          }
        }
      },
      {
        "Name": "Logger",
        "Args": {
          "configureLogger": {
            "Filter": [
              {
                "Name": "ByIncludingOnly",
                "Args": {
                  "expression": "Contains(SourceContext, 'nomeapp') and @Level = 'Information'"
                }
              }
            ],
            "WriteTo": [
              {
                "Name": "File",
                "Args": {
                  "path": "Logs/Info/applog_.json",
                  "formatter": "Serilog.Formatting.Json.JsonFormatter, Serilog",
                  "rollingInterval": "Day",
                  "retainedFileCountLimit": 7
                }
              },
              {
                "Name": "MongoDBBson",
                "Args": {
                  "databaseUrl": "mongodb://user:password@server1,server2,server3/database?authSource=admin&replicaSet=replicaname",
                  "collectionName": "logs",
                  "cappedMaxSizeMb": "1024",
                  "cappedMaxDocuments": "50000"
                }
              }
            ]
          }
        }
      }
    ],
    "Enrich": [ "FromLogContext", "WithMachineName", "WithThreadId", "WithThreadName" ],
    "Properties": {
      "app": "nome",
      "app_environment": "dev", /*dev,tst,itg,pef,prd*/
      "log.level": "", /*error,info,debug,warning*/
      "tenant": "nometeam" 
    }
  }

Deadlock

Situazione in cui due o più processi o azioni si bloccano a vicenda, aspettando che uno esegua una certa azione (es. rilasciare il controllo su una risorsa come un file, una porta input/output ecc.) che serve all’altro e viceversa.

Un esempio è rappresentato da due persone che vogliono disegnare: hanno a disposizione solo una riga e una matita e hanno bisogno di entrambe. Potendo prendere un solo oggetto per volta, se uno prende la matita, l’altro prende la riga, e se entrambi aspettano che l’altro gli dia l’oggetto che ha in mano, i due generano uno stallo.

Questa situazione può esser vista come un paradosso e non può essere risolta, ma si può prevenire. Applicazioni che sono tipicamente soggette agli stalli sono le basi di dati, nel caso in cui ci siano richieste circolari di accesso esclusivo da parte di diverse transazioni sulle stesse risorse, oppure i sistemi operativi che gestiscono l’accesso contemporaneo a file e a dispositivi di I/O di diversi processi.

Condizioni necessarie

In un deadlock si verificano sempre queste condizioni, dette anche di Havender:

  1. Mutua esclusione: almeno una delle risorse del sistema deve essere ‘non condivisibile’ (ossia usata da un processo alla volta oppure libera).
  2. Accumulo incrementale: i processi che possiedono almeno una risorsa devono attendere prima di richiederne altre (già allocate ad altri processi).
  3. Impossibilità di prelazione: solo il processo che detiene la risorsa può rilasciarla.
  4. Attesa circolare: esiste un gruppo di processi {P0,P1,…,Pn} per cui P0 è in attesa per una risorsa occupata da P1, P1 per una risorsa di P2, ecc. Pn per una risorsa di P0.

Una situazione di deadlock può essere riconosciuta analizzando il grafo delle attese dei processi del sistema.

deadlock si possono verificare solo se sono presenti contemporaneamente le quattro condizioni di cui sopra (che sono quindi necessarie). Le condizioni diventano anche sufficienti nel caso di una sola istanza per ciascun tipo di risorsa.

Gestione

Evitare i deadlock

Magnifying glass icon mgx2.svgLo stesso argomento in dettaglio: Stato sicuro.

È una soluzione possibile solo se il sistema è capace di mantenere delle informazioni sulle risorse disponibili nel sistema e sulle risorse che ogni processo può potenzialmente richiedere.

Si definisce stato sicuro di un sistema quando è possibile eseguire i processi in una sequenza tale per cui, allocando ad ognuno di essi tutte le risorse che potenzialmente può richiedere, gli si permetta di terminare la propria esecuzione e quindi evitare il deadlock. Se il sistema si trova in uno stato sicuro il deadlock può essere evitato, ma uno stato non sicuro non implica necessariamente un deadlock.

Il sistema può dunque evitare del tutto gli stalli se, ad ogni richiesta di una risorsa da parte di un processo, effettua una verifica dello stato in cui si troverebbe allocando la risorsa. Se lo stato è sicuro la risorsa può essere tranquillamente allocata. A tal fine si può utilizzare l’algoritmo del banchiere.

Tuttavia, per la maggior parte dei sistemi è impossibile conoscere in anticipo le risorse che richiederà un processo, per cui è spesso impossibile evitare del tutto i deadlock.

Prevenire i deadlock

In questo caso si cerca di evitare i deadlock annullando una o più delle condizioni di cui sopra: essendo esse collettivamente sufficienti basta infatti invalidarne una.

Per esempio:

  1. Annullando la condizione di mutua esclusione e permettendo l’accesso contemporaneo ad una stessa risorsa da parte di diversi processi (es. file aperti in lettura). Può essere impossibile come nei casi di scrittura su file o accesso ad una risorsa senza spooling.
  2. Si può richiedere ad un processo di richiedere tutte le risorse di cui dovrà disporre all’avvio oppure con la convenzione di rilasciare una risorsa prima di richiederne un’altra. Spesso questa soluzione non è praticabile o poco efficace. Tuttavia è utilizzata nei database che fanno uso di two-phase locking.
  3. Permettere la prelazione, il che quindi permetterebbe a un processo di rilasciare la risorsa detenuta da un altro processo: ciò può lasciare l’applicazione vittima in uno stato inconsistente, dato che finisce per perdere una risorsa che stava utilizzando.
  4. L’attesa circolare si può risolvere permettendo ad ogni processo di richiedere solo una risorsa alla volta oppure imponendo un ordinamento (o una gerarchia) sui processi e prevenire in tal modo la formazione di cicli nel grafo delle attese.

Risolvere i deadlock

Quando non è possibile evitare o prevenire i deadlock si possono solo definire degli algoritmi per riconoscere e risolvere gli stati di deadlock.

Per quanto riguarda la risoluzione, si può procedere con la terminazione di tutti i processi in stallo o di un processo alla volta fino alla risoluzione del Deadlock, oppure con la prelazione sulla risorsa che causa il problema. Particolare cura deve essere riposta nella scelta della vittima della prelazione.

Deadlock distribuiti

In presenza di un sistema distribuito (come per esempio un database risiedente su diversi server), riconoscere una situazione di potenziale deadlock si rende ancora più complessa. In genere la rivelazione del possibile stato insicuro può essere effettuata solo ricostruendo il grafo delle attese globale a partire da quelli locali o con particolari algoritmi (vedi la variante distribuita del two-phase locking).

Tuttavia, la possibilità che il grafo delle attese globale non rifletta sempre correttamente l’effettivo stato del sistema distribuito, può rivelare dei deadlock inesistenti (phantom deadlocks) che sono già stati risolti perché uno dei processi ha terminato la sua esecuzione nel frattempo o che non sono mai esistiti realmente.

Stato sicuro

Si definisce “stato sicuro” uno stato in cui è possibile allocare tutte le risorse richieste da un processo senza che questo finisca in un deadlock.

Il fatto che il sistema si trovi in uno stato sicuro non implica che tutte le allocazioni avverranno con successo, ma solo che esiste almeno un modo per allocare tutte le risorse. Se il sistema si trova in uno stato sicuro il deadlock può essere evitato, ma uno stato non sicuro non implica necessariamente un deadlock.

Il sistema può dunque evitare del tutto gli stalli se, ad ogni richiesta di una risorsa da parte di un processo, effettua una verifica dello stato in cui si troverebbe allocando la risorsa. Se lo stato è sicuro la risorsa può essere tranquillamente allocata. A tal fine si può utilizzare l’algoritmo del banchiere.

Tuttavia, per la maggior parte dei sistemi è impossibile conoscere in anticipo le risorse che richiederà un processo, per cui è spesso impossibile evitare del tutto i deadlock visto che non si può determinare in anticipo se un futuro stato sarà sicuro.