Injecting DbContext into LoggerProvider throws StackOverflowException in .NET Core - c#

I'm using .net core 2.2 with entityframework core. I want to write logs in database using entityframework. So I'm trying to inject DbContext to LoggerProvider.
//Main function
new WebHostBuilder().ConfigureLogging((hostingContext, logging) =>
{
logging.ClearProviders();
logging.AddDatabase(hostingContext.Configuration);
}).UseStartup<Startup>();
//Extension method
public static ILoggingBuilder AddDatabase(this ILoggingBuilder builder, IConfiguration configuration)
{
builder.AddConfiguration();
builder.Services.AddDbContext<LoggingContext>(options => options.UseSqlServer(configuration.GetConnectionString("DevelopmentConnection"), x => x.MigrationsHistoryTable("__LoggingMigrationHistory", "dbo")));
builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped<ILoggerProvider, DatabaseLoggerProvider>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped<IConfigureOptions<LoggerOptions>, LoggerConfigurationOptions>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Scoped<IOptionsChangeTokenSource<LoggerOptions>, LoggerProviderOptionsChangeTokenSource<LoggerOptions, DatabaseLoggerProvider>>());
return builder;
}
//LoggerOptions
public class LoggerOptions
{
public string LogLevel { get; set; }
}
//LoggerConfigurationOptions
public class LoggerConfigurationOptions : ConfigureFromConfigurationOptions<LoggerOptions>
{
public LoggerConfigurationOptions(ILoggerProviderConfiguration<DatabaseLoggerProvider> providerConfiguration) : base(providerConfiguration.Configuration)
{
}
}
//Logging Context
public class LoggingContext : DbContext
{
public LoggingContext(DbContextOptions<LoggingContext> options) : base(options) //In base constructor exception is thrown
{
}
}
//Logger Provider
[Microsoft.Extensions.Logging.ProviderAlias("Database")]
public class DatabaseLoggerProvider : ILoggerProvider
{
public DatabaseLoggerProvider(IOptionsMonitor<LoggerOptions> Settings, LoggingContext context) //I cannot inject context here
{
}
}
Problem is DbContext constructor throws StackOverflowException. Can anyone tell me where I'm doing wrong?

Whoever is using LoggingContext should create a new instance, perhaps in constructor or base class constructor.

Related

Unable to invoke a DBContext constructor from repository

I am looking to create a signalR Hub to get the SQL record updates in real time, using SQLDependency.
I used the EFCore Scaffolding database to create models as well as the DBContext, and using the repository pattern to work on retrieving data from the DB.
private Func<DBAContext> _contextFactory;
public Repository(Func<DBAContext> ContextFactory)
{
this._contextFactory = ContextFactory;
}
public someMethod()
{
using (var context = _contextFactory())
{
return context.Account.LastOrDefault();
}
}
Here's the issue: on invoking context, I get "Constructor on Type DBContext not found.
DBAContext.cs
public partial class DBAContext : DbContext
{
public DBAContext(DbContextOptions<DBAContext> options)
: base(options)
{
}
public virtual DbSet<Account> Account { get; set; }
}
.
.
.//OnConfiguring
.//Autogenerated onModelCreating
Here is where the error occurs:
public static void AddDbContextFactory<TDataContext>(this IServiceCollection services, string
connectionString) where TDataContext : DbContext
{
services.AddSingleton<Func<TDataContext>>((ctx) =>
{
var options = new DbContextOptionsBuilder()
.UseSqlServer(connectionString)
.Options;
return () => (TDataContext)Activator.CreateInstance(typeof(TDataContext), options);
});
}
}
Activator.CreateInstance is unable to resolve a constructor on the DBAContext class.
Startup.cs
services.AddDbContextFactory<DBAContext>
(Configuration.GetConnectionString("DefaultConnection"));
If anyone could explain me the issue, I'd really appreciate it
Thanks in Advance

C#: Inherit from DbContext in Net Core, Constructor: No database provider has been configured for this DbContext

I currently have PropertyApplication DbContext as below,
public partial class PropertyContext : DbContext
{
public PropertyContext()
{
}
public PropertyContext(DbContextOptions<PropertyContext> options)
: base(options)
{
}
public virtual DbSet<Address> Address { get; set; }
public virtual DbSet<BoundaryChangeEvent> BoundaryChangeEvent { get; set; }
I would like to inheritance from this PropertyDbContext. Is this being done correctly in the constructor? Attempting to make unit test pass below, it overrides save changes to bring in auditing user information. Just curious if specifically the constructor statements below look correct? Or should I try to attempt option 2 below with AuditablePropertyContext options?
public class AuditablePropertyContext : PropertyContext
{
private int _user;
public AuditablePropertyContext()
{
}
public AuditablePropertyContext(DbContextOptions<PropertyContext> options, UserResolverService userService)
: base(options)
{
_user = userService.GetUser();
}
public void ApplyCreatedBy()
{
var modifiedEntities = ChangeTracker.Entries<ICreatedByUserId>().Where(e => e.State == EntityState.Added);
foreach (var entity in modifiedEntities)
{
entity.Property("CreatedByUserId").CurrentValue = _user;
}
}
public override int SaveChanges()
{
ApplyCreatedBy();
return base.SaveChanges();
}
}
Option 2:
I was receiving error trying to conduct this,
public AuditablePropertyContext(DbContextOptions<AuditablePropertyContext> options, UserResolverService userService)
: base(options)
{
_user = userService.GetUser();
}
Error:
Error CS1503 Argument 1: cannot convert from 'Microsoft.EntityFrameworkCore.DbContextOptions IPTS.PropertyManagement.Infrastructure.Auditable.Data.AuditablePropertyContext' to 'Microsoft.EntityFrameworkCore.DbContextOptions IPTS.PropertyManagement.Infrastructure.Data.PropertyContext '
*Sometimes company utilizes SQL Server, sometimes InMemory, or SQLite
Unit Test is failing:
services.AddSingleton(a =>
{
var mock = new Mock<IUserResolverService>();
mock.Setup(b => b.GetUser()).Returns(5);
return mock.Object;
});
services.AddDbContext<PropertyContext>(
options => options.UseInMemoryDatabase("Ipts").UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll),
ServiceLifetime.Singleton);
services.AddSingleton<DbContext, PropertyContext>();
services.AddDbContext<AuditablePropertyContext>(
options => options.UseInMemoryDatabase("Ipts").UseQueryTrackingBehavior(QueryTrackingBehavior.TrackAll),
ServiceLifetime.Singleton);
services.AddSingleton<AuditablePropertyContext>();
services.RegisterMappingProfiles(new ApplicationServicesMappingProfile(),
new PropertyManagementDataMappingProfile());
return services;
}
Unit Test: Error
Message:
System.InvalidOperationException : 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<TContext> object in its constructor and passes it to the base constructor for DbContext.
Stack Trace:
DbContextServices.Initialize(IServiceProvider scopedProvider, IDbContextOptions contextOptions, DbContext context)
DbContext.get_InternalServiceProvider()
DbContext.get_DbContextDependencies()
Change the constructor PropertyContext class to the following code:
public PropertyContext(DbContextOptions options)
: base(options)
{
}
then change the constructor AuditablePropertyContext class to the following code:
public AuditablePropertyContext(DbContextOptions options, UserResolverService userService)
: base(options)
{
_user = userService.GetUser();
}
notice: Delete the default constructor in both classes when you don't need it.
You could also provide the specialized DbContextOptions<Repo> only on the concrete subtype.
eg
public abstract class BaseRepo: DbContext
{
public BaseRepo(DbContextOptions options) : base(options)
{
}
}
public sealed class Repo : BaseRepo
{
public Repo(DbContextOptions<Repo> options) : base(options)
{
}
}

How to inject dynamic DbContext object into repository using Autofac

I have an .net core web api application where I'm using entity framework core with service layer, unit of work and repository layer pattern. For DI I'm using Autofac.
The application has multiple clients and each client has its own database and the schema for all these databases is same. With each API call I'll get the client specific connection string, using which I have to create a DbContext and use it for all its operations.
On Startup class I have registered my dbcontext ClientDbContext and all other classes. When the unit-of-work class is called I am creating my new DbContext based on the connection string. I want the repository to use this instance, but the repository is still using the initial ClientDbContext instance which was created at startup.
How can I make the repository use the new DbContext instance?
Unit of Work:
public class UnitOfWork : IUnitOfWork
{
public ClientDbContext ClientDbContext { get; private set; }
public UnitOfWork ()
{
}
public void SetDbContext(string connectionString)
{
if(ClientDbContext == null)
{
//creating new db context instance here
ClientDbContext = MembershipRepository.CreateDbContext(connectionString);
}
}
//property injection
public IGenericRepository<SomeEntity, ClientDbContext> SomeEntityGenericRepository { get; }
}
Generic Repository:
public class GenericRepository<TEntity, TDbContext> : IGenericRepository<TEntity, TDbContext> where TEntity : class
where TDbContext : DbContext
{
private readonly TDbContext _context;
private readonly DbSet<TEntity> _dbset;
public GenericRepository(TDbContext context)
{
// need to get updated context here, but getting the initial one
_context = context;
_dbset = _context.Set<TEntity>();
}
}
Autofac module called in Startup.cs:
builder.Register(a => new ClientDbContext()).InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(GenericRepository<,>)).As(typeof(IGenericRepository<,>)).InstancePerLifetimeScope();
//Register Unit of Work here
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerLifetimeScope().PropertiesAutowired();
//Register Services here
builder.RegisterType<SomeService>().As<ISomeService>().InstancePerLifetimeScope();
Can anyone please help me out on how to achieve the above requirement?
Is there any way I can make Autofac use my new created dbcontext object?
Instead of
builder.Register(a => new ClientDbContext()).InstancePerLifetimeScope();
you could use
builder.Register(c => c.Resolve<IUnitOfWork>().ClientDbContext)
.InstancePerLifetimeScope();
By the way I'm not sure what is the responsibility of your IUnitOfWork. Another way of doing this would be to have a class that would provide information about the current user :
public interface IClientContext
{
public String ClientIdentifier { get; }
}
Then a DbContextFactory that would create the DbContext based on the IClientContext
public interface IDbContextFactory
{
IDbContext CreateDbContext();
}
public class DbContextFactory
{
public DbContextFactory(IClientContext clientContext)
{
this._clientContext = clientContext;
}
private readonly IClientContext _clientContext;
public IDbContext CreateDbContext()
{
// get the connectionstring from IClientContext and return the IDbContext
}
}
The concrete implementation of IClientContext depends on the way you can get this information, it could be from current HttpContext or any other way it's up to you.
It seems that at some point you call SetDbContext you can keep this way by creating a XXXClientContextProvider where XXX is relative to the way you get this information.
public class XXXClientContextProvider
{
private IClientContext _clientContext;
public IClientContext GetClientContext()
{
if(this._clientContext == null)
{
throw new Exception("client context is null. You should do X or Y");
}
return this._clientContext;
}
public void SetClientContext(String clientId)
{
if(this._clientContext != null)
{
throw new Exception("client context has already been set");
}
this._clientContext = new StaticClientContext(clientId);
}
}
and then register everything like this :
builder.Register(c => c.Resolve<IClientContextProvider>().GetClientContext())
.As<IClientContext>()
.InstancePerLifetime();
builder.Register(c => c.Resolve<IDbContextFactory>().CreateDbContext())
.As<IDbContext>()
.InstancePerLifetime();

ASP.NET Core 2 Unable to resolve service for type Microsoft EntityFrameworkCore DbContext

When I run my asp.net core 2 projects I get the following error message:
InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'ContosoUniversity.Service.Class.StudentService'.
Here is my project structure:
-- solution 'ContosoUniversity'
----- ContosoUniversity
----- ContosoUniversity.Model
----- ContosoUniversity.Service
IEntityService (related code) :
public interface IEntityService<T> : IService
where T : BaseEntity
{
Task<List<T>> GetAllAsync();
}
IEntityService (related code) :
public abstract class EntityService<T> : IEntityService<T> where T : BaseEntity
{
protected DbContext _context;
protected DbSet<T> _dbset;
public EntityService(DbContext context)
{
_context = context;
_dbset = _context.Set<T>();
}
public async virtual Task<List<T>> GetAllAsync()
{
return await _dbset.ToListAsync<T>();
}
}
Entity :
public abstract class BaseEntity {
}
public abstract class Entity<T> : BaseEntity, IEntity<T>
{
public virtual T Id { get; set; }
}
IStudentService :
public interface IStudentService : IEntityService<Student>
{
Task<Student> GetById(int Id);
}
StudentService :
public class StudentService : EntityService<Student>, IStudentService
{
DbContext _context;
public StudentService(DbContext context)
: base(context)
{
_context = context;
_dbset = _context.Set<Student>();
}
public async Task<Student> GetById(int Id)
{
return await _dbset.FirstOrDefaultAsync(x => x.Id == Id);
}
}
SchoolContext :
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
}
And finally here is my Startup.cs class :
public class Startup
{
public Startup(IConfiguration configuration, IHostingEnvironment env, IServiceProvider serviceProvider)
{
Configuration = configuration;
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);
Configuration = builder.Build();
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolContext>(option =>
option.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<IStudentService, StudentService>();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseBrowserLink();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
What should I do to resolve this problem?
StudentService expects DbContext but the container does not know how to resolve it based on your current startup.
You would need to either explicitly add the context to the service collection
Startup
services.AddScoped<DbContext, SchoolContext>();
services.AddScoped<IStudentService, StudentService>();
Or update the StudentService constructor to explicitly expect a type the container knows how to resolve.
StudentService
public StudentService(SchoolContext context)
: base(context)
{
//...
}
I encountered a similar error i.e.
An unhandled exception occurred while processing the request.
InvalidOperationException: Unable to resolve service for type 'MyProjectName.Models.myDatabaseContext' while attempting to activate 'MyProjectName.Controllers.MyUsersController'.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.GetService(IServiceProvider sp, Type type, Type requiredBy, bool isDefaultParameterRequired)
What I later figured out was... I was missing the following line i.e. adding my database context to services:
services.AddDbContext<yourDbContext>(option => option.UseSqlServer("Server=Your-Server-Name\\SQLExpress;Database=yourDatabaseName;Trusted_Connection=True;"));
Here goes my ConfigureServices method defined in Startup class:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential
//cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDbContext<yourDbContext>(option =>
option.UseSqlServer("Server=Your-Server-Name\\SQLExpress;Database=yourDatabaseName;Trusted_Connection=True;"));
}
...
...
}
Basically, when you generated model classes from database, all your database tables were mapped into respective Model classes by creating the "New Scaffolded Item" and choosing the appropriate database context during the scaffolding procedure.
Now, you need to manually register your database context as a service to the services parameter of ConfigureServices method.
Btw, rather than hard coding your connection string, you'll ideally pick it up from the configuration data. I have attempted to keep things simple here.
if dbcontext inherited from system.data.entity.DbContext then it woud be added like that
services.AddScoped(provider => new CDRContext());
services.AddTransient<IUnitOfWork, UnitOfWorker>();
services.AddTransient<ICallService, CallService>();
This error is thrown when the options argument is null or cannot be retrieved using GetConnectionString().
I had this error because my appsettings.json file that defines my ConnectionStrings had an extra curly bracket } at the end.
Stupid, but frustrating.

Advanced dependency injection in ASP.NET Core

I have following interfaces, abstract classes etc.
public interface IAggregateRootMapping<T> : IAggregateDefinition where T : AggregateRoot
{
IEnumerable<Expression<Func<T, object>>> IncludeDefinitions { get; }
}
public abstract class AggregateRootMapping<T> : IAggregateRootMapping<T> where T : AggregateRoot
{
public abstract IEnumerable<Expression<Func<T, object>>> IncludeDefinitions { get; }
}
public class OrderAggregateRootMapping : AggregateRootMapping<Order>
{
public override IEnumerable<Expression<Func<Order, object>>> IncludeDefinitions
{
get
{
return new Expression<Func<Order, object>>[] {
order => order.Supplier
};
}
}
}
I use those in another class like this:
public class Repository<TAggregateRoot> : IRepository<TAggregateRoot> where TAggregateRoot : AggregateRoot
{
private readonly AggregateRootMapping<TAggregateRoot> _aggregateRootMapping;
public Repository(AggregateRootMapping<TAggregateRoot> aggregateRootMapping)
{
_aggregateRootMapping = aggregateRootMapping;
}
Do something...
}
How do I use the dependency injection of ASP.NET Core so that on runtime the matching class is injected?
For example if the AggregateRoot type class is Order than for the Repository class the OrderAggregateRootMapping class should be injected.
How do I use the ServiceCollection in ConfigureServices of the Startup class in .NET Core to accomplish this?
The dependency injection that comes by default is very basic. If you want to start wiring up rules based on generics, you will need to use a different implementation.
But, what you're after is still possible if you're willing to code the dependencies one by one.
In your Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<AggregateRootMapping<Order>, OrderAggregateRootMapping>();
services.AddScoped<Repository<Order>>();
// Add framework services.
services.AddMvc();
}
And then you can use your Repository class by injecting it into a controller, for example.
In ValuesController.cs
[Route("api/[controller]")]
public class ValuesController : Controller
{
private Repository<Order> _repository;
public ValuesController(Repository<Order> repository)
{
_repository = repository;
}
}
ValuesController will then receive an instance of Repository<Order> which will have been created with a OrderAggregateRootMapping.

Categories

Resources