EntityFramework 5 Logging in Azure Functions - entity-framework

TLDR; How can we make EF5 logging into its own category instead of the category of the Azure Function?
After we upgraded our Azure Functions from .NET core to .NET5/6 and respectively EF to EF5, we noticed that all DB context queries are ending up as (information) logs in the Azure Function category -and not in a dedicatedd category, i.e.Microsoft.EntityFrameworkCore. The host.json looks like this:
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingExcludedTypes": "Request",
"samplingSettings": {
"isEnabled": true
}
},
"logLevel": {
"Default": "Warning",
"Function": "Information",
"Microsoft.EntityFrameworkCore": "Warning",
}
}
}
The observed behavior is that the query of context.Employees.ToList() is logged as information:
[2022-04-27T08:00:16.362Z] Entity Framework Core 5.0.15 initialized 'MyDbContext' using provider 'Microsoft.EntityFrameworkCore.SqlServer' with options: MaxBatchSize=100
[2022-04-27T08:00:17.896Z] Executed DbCommand (59ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
[2022-04-27T08:00:17.899Z] SELECT [p].[Name]
[2022-04-27T08:00:17.900Z] FROM [mdm].[Employees] AS [p]
If we change the log level of "Function" to warning, the logs disappear (together with all(!) other logs related to the function). To me, it seems that the default EF5 logger ignores the concept of log-categories and just writes into the standard one. Ideally, we wanted to log into dedicated category "Microsoft.EntityFrameworkCore": "Warning" so that we can turn it on/off on a need basis.
We noticed that there is some simple logging change which can be influenced by db context options. Also using LoggerFactory doesn't have the concept of categories?
private static void AddDefaultContext(this IServiceCollection services, ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
{
services.AddDbContext<CreditRiskDashboardDbContext>(
s, options) =>
{
var config = s.GetService<IOptions<CreditRiskConfiguration>>().Value;
// This works, but overrides/ignores anything set in host.json
//options.UseLoggerFactory(LoggerFactory.Create(builder => builder.SetMinimumLevel(LogLevel.Warning)));
}, serviceLifetime);
}
The documentation doesn't speak to log categories either. So what to do?

Related

Azure Functions (Queue Trigger) with DI, Connecting to specific database at runtime

Looking to understand if this is possible. If so, then how? If not, then why?
I would like to have a single azure queue function process messages like this:
{
"tenanatDb": Db_Name_3102,
"command": "getCountryCurrencies",
"args": "US"
}
I'm using DI so in my Function I have a Startup
internal class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddDbContext<MyDbContext>(options =>
options
.UseSqlServer("<ConnectionString>"));
builder.Services.AddSingleton<IMyService>((s) => {
return new MyService();
});
}
}
I can provide a connection string, however, what can I do so that a standard connection string is used, but the Database is specified at runtime from the Queue Message:
Server=myServerAddress;Database=***Db_Name_3102***;User Id=myUsername;Password=myPassword;
Thanks
I have been able to get this working in this way, however, it does not use Dependency Injection.
SomeClass obj = new SomeClass("DatabaseName");
obj.getCoutnryCurrecies(args);
In the SomeClass constructor, I'm setting up the database connection string.

ASP.NET Core reference connection string in class library project

I've created an ASP.NET core 5 Solution and it consists of different projects. The Startup class and DataContext class are not in the same project so when I'm going to add a new migration on the Data project where DataContext is located I get the following error.
Unable to create an object of type 'DataContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728
It is the structure of the Solution:
I managed to solve the problem by adding the Startup project's name while adding a new migration, but this way seems annoying to add the project's name every time creating a new migration.
dotnet ef migrations add initialcreation -s ..\API\API.csproj
No database provider has been configured for this DbContext. A provider can be configured by overriding the 'DbContext.OnConfiguring' method or by using 'AddDbContext' on the application service provider. If 'AddDbContext' is used, then also ensure that your DbContext type accepts a DbContextOptions object in its constructor and passes it to the base constructor for DbContext.
I also add a new constructor with no parameter in the DataContext class and the result was the following error:
No database provider has been configured for this DbContext. A
provider can be configured by overriding the 'DbContext.OnConfiguring'
method or by using 'AddDbContext' on the application service provider.
If 'AddDbContext' is used, then also ensure that your DbContext type
accepts a DbContextOptions object in its constructor and
passes it to the base constructor for DbContext.
Finally, I added the OnConfiguring function on DataContext and now it is like this:
public class DataContext : DbContext
{
public DataContext() {}
public DataContext(DbContextOptions<DataContext> options) : base(options)
{
this.ChangeTracker.LazyLoadingEnabled = false;
}
public DbSet<AppUser> Users { get; set; }
public DbSet<UserPhoto> Photos { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlite("name=DefaultConnection");
}
}
}
And again I got a new error while creating a new migration:
A named connection string was used, but the name 'DefaultConnection'
was not found in the application's configuration. Note that named
connection strings are only supported when using 'IConfiguration' and
a service provider, such as in a typical ASP.NET Core application. See
https://go.microsoft.com/fwlink/?linkid=850912 for more information.
UPDATE:
Here is appsettings.json file:
"ConnectionStrings": {
"DefaultConnection": "Data Source=database1.db"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"

VSCode - getConfiguration() Returning Cache Value?

An extension I'm writing has a single value in it's Contributes section like this:
"main": "./out/extension.js",
"contributes": {
"configuration": {
"title": "My Extension Features",
"properties": {
"myextension.extensionStore": {
"type": "string",
"default": "C:\\Users\\SomeUser\\Desktop\\ExtensionStore"
}
}
},
In one of my classes that extends TreeDataMenuProvider we'll call GreatMenuProvider, when it is instantiated does the following:
export class GreatMenuProvider implements vscode.TreeDataProvider<GreatMenuItem> {
_extensionStore: any = vscode.workspace
.getConfiguration("myextension")
.get("extensionStore");
// ...remainder of the implementation that builds the tree view I want, which worked until later
}
export class GreatMenuItem extends vscode.TreeItem {
constructor(
public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public uri: vscode.Uri,
public type: vscode.FileType,
public iconPath,
public version: string | number,
public children?: GreatMenuItem[],
public readonly comand?: vscode.Command
) {
super(label, collapsibleState);
}
setIcons() {
this.iconPath = {
light: path.join(
myUtils.getExtensionResourcesDir(),
"light",
"doc-text-inv.svg"
),
dark: path.join(
myUtils.getExtensionResourcesDir(),
"dark",
"doc-text.svg"
)
};
}
}
In doing some testing against fake or inaccessible paths, I changed the value of myextension.extensionStore to something else, which failed as expected in the check in my implementation.
However, even though I can change the value listed in the default key for .extensionStore, no matter what I set it to, the old value continues to get picked up when I run my extension to debug.
Since it looks for icons, I have a working version installed, and the error does not occur, so something cached the fake/old value. The fake value is nowhere in my code now, but continues to show up every time I run this.
I thought I had this solved by deleting my ./out folder, turning on watch in my tsconfig.json, but still keeps showing up.
Stepped through alot of extensionHostProcess.js but couldn't figure out where or how it's finding the old value.
What else in a VS Code extension could cache this if it's not anywhere in my Typescript files?
Also I've tried changing to numerous different commit hashes, and no matter what the problem persists. Also tried deleting all kinds of stuff out of %AppData%/Code which didn't work either.
UPDATE: Eventually I just nuked my entire %AppData%/Code folder, and now it works again. I don't know whether it was one of the .vscdb files, stuff from backups, or otherwise, but don't know what files or combination of them persisted that nagging piece of bad info.
Oh well!
Erased all of %AppData%\Code, things work again

Get generated SQL for a DbContext.SaveChanges in Entity Framework Core

In Entity Framework Core, is it possible to see the SQL that will be applied when the SaveChanges() method is called on the DbContext?
EF Core 5.0+
There are a simple builtin solution, add the following function to the DbContext class.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.LogTo(Console.WriteLine);
this will log both queries and commands.
See here for mote details: Simple Logging
Here are the docs on creating a LoggerFactory in Core 3. In short:
var loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
.AddFilter("LoggingConsoleApp.Program", LogLevel.Debug)
.AddConsole()
.AddEventLog();
});
You may need to add a reference to Microsoft.Extensions.Logging.Console.
Use the DbContextOptionsBuilder to enable logging for a context.
optionsBuilder.UseLoggerFactory(loggerFactory)
I'll repeat the warning from here:
It is very important that applications do not create a new ILoggerFactory instance for each context instance. Doing so will result in a memory leak and poor performance.
Therefore, they recommend using a singleton/global instance:
public static readonly ILoggerFactory MyLoggerFactory =
LoggerFactory.Create(builder => { builder.AddConsole(); });
you can use console logger "EF Core logging automatically integrates with the logging mechanisms of .NET Core "
you can read about here :
https://www.entityframeworktutorial.net/efcore/logging-in-entityframework-core.aspx
You can use DbContextOptionsBuilder.UseLoggerFactory(loggerFactory) method to log all sql output.By using constructor Injection like below
public class DemoContext : ObjContext
{
private readonly ILoggerFactory _loggerFactory;
public DemoContext() { }
public DemoContext(ILoggerFactory loggerFactory)
{
_loggerFactory = loggerFactory;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseLoggerFactory(_loggerFactory);
}
}
using (var context = new DemoContext(_loggerFactory))
{
var Employees = context.Employee.ToList();
}
Or
I suggest a few other ways of viewing the SQL generated is to use reflection to create an ObjectQuery object and then call the ToTraceString() method to actually store the query results.
using (var context = new EntityContext())
{
var query = context.Customers.Where(c => c.Id == 1);
var sql = ((System.Data.Objects.ObjectQuery)query).ToTraceString();
}
Use SQL Logging
Using The DbContext.Database.Log property can be set to a delegate for any method that takes a string.
Log SQL to the Console.
using (var context = new EntityContext())
{
context.Database.Log = Console.Write;
}
Log SQL to Visual Studio Output panel.
using (var context = new EntityContext())
{
context.Database.Log = s => System.Diagnostics.Debug.WriteLine(s);
}
It's a bit tricky to log EF Core's SQL in an ASP.NET MVC web app. Unlike Entity Framework, EF Core lacks the easy-to-use DBContext.Database.Log property. As #DharmaTurtle mentioned, you can use LoggerFactory.Create, and this can work, but it does create a separate ILoggerFactory than the one that the rest of the app uses for logging (one that is apparently not using appsettings.json for options.)
The following approach is required if you want to use the same log factory for DBContext that the rest of the ASP.NET MVC web app uses:
Create a derived class of DbContext (or, if this was already done, modify the existing class appropriately). Note: this example will only log SQL in Debug builds, not Release builds.
public class DbContextWithLogging : Microsoft.EntityFrameworkCore.DbContext
{
ILoggerFactory _loggerFactory;
IConfiguration _configuration; // Provides access to appsettings.json
public DbContextWithLogging(ILoggerFactory loggerFactory, IConfiguration configuration)
=> (_loggerFactory, _configuration) = (loggerFactory, configuration);
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
#if DEBUG
builder.UseLoggerFactory(_loggerFactory);
// This line causes parameter values to be logged:
builder.EnableSensitiveDataLogging();
#endif
}
}
Note: this approach is not compatible with calling AddDbContext in Startup.ConfigureServices, so if there is already a call to AddDbContext, disable/remove it. In my case, the existing derived class of DbContext had a constructor that accepted (DbContextOptions<BarreleyeDbContext> options), which I removed.
In your Startup.ConfigureServices method, configure logging (e.g. to print to console) and enable the custom DbContext:
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging((ILoggingBuilder builder) => {
builder.AddConsole();
});
// In ASP.NET Core apps, a Scope is created around each server request.
// So AddScoped<X, Y>() will recreate class Y for each HTTP request.
services.AddScoped<DbContext, DbContextWithLogging>();
... // leave the rest as before
}
Whatever uses DbContext (e.g. controllers, or 'repositories' in the Repository pattern) should obtain it automagically via constructor injection.
EF Core uses LogLevel.Information when printing the SQL, so you need the Information level to be enabled. If you look at your appsettings.json file, you'll want to see something like this:
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
},
In particular, EF Core log filtering can be controlled with a key like
"Microsoft.EntityFrameworkCore": "Information",
but if this key is missing, the "Microsoft" key is used instead.
This might not work! Look for a second file called appsettings.Development.json - watch out, Visual Studio might hide this file "inside" appsettings.json. If appsettings.Development.json exists, its contents override appsettings.json (at the granularity of individual keys).
Once it's working, you'll see log info that looks like this (yes, SELECT statements are logged as well as INSERT, UPDATE and DELETE):
info: Microsoft.EntityFrameworkCore.Database.Command[20101]
Executed DbCommand (0ms) [Parameters=[#__p_0='297'], CommandType='Text', CommandTimeout='30']
SELECT e.id, e.config, e.end_date, e.entity_name, e.entity_type, e.location, e.start_date
FROM entities AS e
WHERE e.id = #__p_0
LIMIT 1

Connection string for PostgreSQL

I am trying to write my connection string in my appsettings.json file and bring it into my startup file through an extension class but I keep getting a
Value cannot be null. Parameter name: connectionString.
I have been using various examples but can't seem to see this new setup with ASP.NET Core 2.2 startup class.`And I'm using PostgreSql
appsetting.json file
{
"PostgreConnectionString": {
"DefaultConnection": "User ID=1;Password=1234;Host=localhost;Port=5432;Database=Demo;Pooling=true;Integrated Security=true;",
"Logging": {
"LogLevel": {
"Default": "Warning"
}
},
"AllowedHosts": "*"
}
}
ServiceExtension.cs
public static void ConfigurePostgreSQL(this IServiceCollection services, IConfiguration config)
{
var connectionString = config["PostgreConnectionString:DefaultConnection"];
services.AddDbContext<RepositoryContext>(options => options.UseNpgsql(config.GetConnectionString(connectionString))); --Error in this line value cannot be null
}
Startup.cs
services.ConfigurePostgreSQL(Configuration);
For .NET Core I use:
_connectionString = config.GetConnectionString("PostgreConnectionString");
config
{
"ConnectionStrings": {
"PostgreConnectionString": "string"
}
}
it works
the problem is the nesting brackets ! Appsettings.json generate a ConnectionStrings object imbricated in "Logging" object.
Put ConnectionStrings out and your Configuration.GetConnectionStrings will works !