Access SQL queries Generated By Entity Framework Core 3

Create a class EntityFrameworkSqlLogger.cs

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Microsoft.Extensions.Logging;

namespace MyApp.Logging
{
    /// <summary>
    /// ILogger implementation
    /// </summary>
    public class EntityFrameworkSqlLogger : ILogger
    {
        #region Fields
        Action<EntityFrameworkSqlLogMessage> _logMessage;
        #endregion

        #region Constructor
        public EntityFrameworkSqlLogger(Action<EntityFrameworkSqlLogMessage> logMessage)
        {
            _logMessage = logMessage;
        }
        #endregion

        #region Implementation
        public IDisposable BeginScope<TState>(TState state)
        {
            return default;
        }

        public bool IsEnabled(LogLevel logLevel)
        {
            return true;
        }

        public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
        {
            if (eventId.Id != 20101)
            {
                //Filter messages that aren't relevant.
                //There may be other types of messages that are relevant for other database platforms...

                return;
            }

            if (state is IReadOnlyList<KeyValuePair<string, object>> keyValuePairList)
            {
                var entityFrameworkSqlLogMessage = new EntityFrameworkSqlLogMessage
                (
                    eventId,
                    (string)keyValuePairList.FirstOrDefault(k => k.Key == "commandText").Value,
                    (string)keyValuePairList.FirstOrDefault(k => k.Key == "parameters").Value,
                    (CommandType)keyValuePairList.FirstOrDefault(k => k.Key == "commandType").Value,
                    (int)keyValuePairList.FirstOrDefault(k => k.Key == "commandTimeout").Value,
                    (string)keyValuePairList.FirstOrDefault(k => k.Key == "elapsed").Value
                );

                _logMessage(entityFrameworkSqlLogMessage);
            }
        }
        #endregion
    }
    
    /// <summary>
    /// Data model
    /// </summary>
    public class EntityFrameworkSqlLogMessage
    {
        public EntityFrameworkSqlLogMessage(
            EventId eventId,
            string commandText,
            string parameters,
            CommandType commandType,
            int commandTimeout,
            string elapsed
        )
        {
            EventId = eventId;
            CommandText = commandText;
            Parameters = parameters;
            CommandType = commandType;
            Elapsed = elapsed;
            CommandTimeout = commandTimeout;
        }

        public string Elapsed { get; }
        public int CommandTimeout { get; }
        public EventId EventId { get; }
        public string CommandText { get; }
        public string Parameters { get; }
        public CommandType CommandType { get; }
    }
    
    /// <summary>
    /// ILogger provider
    /// </summary>
    public class SingletonLoggerProvider : ILoggerProvider
    {
        #region Fields
        ILogger _logger;
        #endregion

        #region Constructor
        public SingletonLoggerProvider(ILogger logger)
        {
            _logger = logger;
        }
        #endregion

        #region Implementation
        public ILogger CreateLogger(string categoryName)
        {
            return _logger;
        }

        public void Dispose()
        {
        }
        #endregion
    }
}

Add Logger to DbContext OnConfiguring Met

public class CartServiceDbContext : DbContext
{
...

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
      var entityFrameworkSqlLogger = new EntityFrameworkSqlLogger((m) =>
      {
          System.Console.WriteLine($"SQL Query:\r\n{m.CommandText}\r\nElapsed:{m.Elapsed} millisecods\r\n\r\n");
      });

      var myLoggerFactory = LoggerFactory.Create(builder =>
      {
          builder
              .AddFilter((category, level) =>
                  category == DbLoggerCategory.Database.Command.Name
                  && level == LogLevel.Information);
      });

      myLoggerFactory.AddProvider(new SingletonLoggerProvider(entityFrameworkSqlLogger));
      optionsBuilder.UseLoggerFactory(myLoggerFactory);
      DbConnectorUtils.ConfigureOptionsBuilder(optionsBuilder);
  }

...
}

References

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();
}

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);

nuget packages

xUnit & NUnit
Moq & NSubstitute
Polly
FluentAssertions
BenchmarkDotNet
Serilog
Autofixture & Bogus
Scrutor
Automapper
Dapper & Entity Framework Core
MediatR & Brighter
FluentValidation
Refit & RestSharp
Json.NET

Containerize a .NET Core app

Essential commands

Docker has many different commands that create, manage, and interact with containers and images. These Docker commands are essential to managing your containers:

Here an example:
https://docs.microsoft.com/en-us/dotnet/core/docker/build-container

(from Visual Studio 2019 it is possible to add Docker support):

Opzione di menu Aggiungi Supporto Docker in Visual Studio