Unable to invoke a DBContext constructor from repository - c#

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

Related

How to get multiple implementations of the same service interface each in separate scopes

I have an asp.net core 2.2 web application. There is an Interface which is implemented by multiple classes.
services.AddTransient<IHandler, HandlerA>();
services.AddTransient<IHandler, HandlerB>();
Each implementation of IHandler injects scoped EF DbContext and if the same DbContext is shared between different threads, then periodically a floating exception will occur when trying to access the same entity. So I get each handler in a separate scope.
using (var scope = _serviceProvider.CreateScope())
{
var handlers = scope.ServiceProvider.GetServices<IHandler>();
await Task.WhenAll(handlers.Select(async h =>
{
using var internalScope = scope.ServiceProvider.CreateScope();
var handler = internalScope.ServiceProvider
.GetServices<IHandler>()
.First(f => f.Name == h.Name);
await handler.Handle(cancellationToken);
}));
}
This solution seems to work, but I'm not sure if it's optimal. Maybe there is a better way to get multiple implementations of the same service interface in separate scopes?
You do not need to access same entity using different DBContext.
Define services and inject in controllers explained above link. Each service will access DB using same db context through Manager Classes.
You can use service this way as you seeking best practices. And use dependency injection to access the services from controllers.
Define Service, Domain classes and interfaces.
public class Service : Attribute
{
}
//Domain Classes
public class Entity
{
string str {get; set;}
int numb {get; set;}
}
//DBContext
public class DbContext : IdentityDbContext
{
public DbContext(DbContextOptions<DbContext> options)
: base(options)
{
}
public DbSet<Entity> Entities { set; get; }
}
//Interfaces
interface IHandler
{
string method1(Entity entity);
int method2();
}
Implement Interfaces using manager classes
public class HandlerA: IHandler
{
private readonly DbContext _dbContext;
public HandlerA(DbContext dbContext)
{
_dbContext = dbContext;
}
string method1(Entity entity)
{
//access db or business stuff
_dbContext.Entities.Add(entity);
_dbContext.SaveChanges();
}
int method2(){}
}
public class HandlerB: IHandler
{
string method1(Entity entity)
{
//access db or business stuffs
_dbContext.Entities.Remove(entity);
_dbContext.SaveChanges();
}
int method2(){}
int method3(){}
}
//Services
[Service]
public class DoService1Stuff()
{
private readonly HandlerA _handlerA;
public DoService1Stuff(HandlerA handlerA)
{
_handlerA= handlerA;
}
//Implement your task
public Do(Entity entity)
{
_handlerA.method1(entity);
}
}
[Service]
public class DoService2Stuff()
{
private readonly HandlerB _handlerB;
public DoService2Stuff(HandlerB handlerB)
{
_handlerB= handlerB;
}
//Implement your task
public Do(Entity entity)
{
_handlerA.method1(entity);
}
}
Register services through dependency injection in startup.cs
services.AddTransient<IHandler, HandlerA>();
services.AddTransient<IHandler, HandlerB>();
Access services in controllers
[HttpPost]
public async Task<IActionResult> DoStuff1([FromServicec] DoService1Stuff doService1Stuff)
{
var entity= new Entity
{
str="hello",
numb=2020;
};
return Ok(doService1Stuff.Do(entity))
}
//Implement second service as needed.

Dependency injection between two ASP.NET Core projects

I'm currently developing a web application with ASP.NET Core and handling the database with Entity Framework Core. I have two projects in my VS Solution; WebApp (the main application) and DatabaseHandler (the EF Core handler). I have installed Entity Framework Core with the Pomelo package, since I'm using a MySQL database.
I've been following the Microsoft documentation to setup EF Core, connection strings and all that, and it works fine. I'm able to make migrations, make updates and do stuff with the database. I'm however not sure if I'm doing it correctly, since the latest EF Core tutorials use dependency injection and I'm not familiar with it.
Right now I'm passing the DbContext object as an argument from WebApp to DatabaseHandler, since I want all database-related stuff to only exist in DatabaseHandler. This works, but is it possible to call functions from another project and also share the DbContext object without passing it as an argument? I'm probably not explaining it well, I hope my code explains it better.
WebApp/Startup.cs:
This is where I load the connection string from appsettings.json.
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContextPool<DataContext>(
options => options.UseMySql(Configuration.GetConnectionString("DefaultConnection")
));
services.AddRouting(options => options.LowercaseUrls = true);
services.AddControllersWithViews();
}
WebApp/HomeController.cs:
This is where I call the GetAllChallenges() function from the DatabaseHandler project, and I also pass the DataContext object as an argument. This is what I'm trying to avoid!
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly DataContext db;
public HomeController(ILogger<HomeController> logger, DataContext _db)
{
_logger = logger;
db = _db;
}
public IActionResult Challenges()
{
List<Challenge> ChallengesList = DatabaseHandler.HandleChallenges.GetAllChallenges(db);
return View(ChallengesList);
}
}
DatabaseHandler/DataContext.cs:
This is where I initialize the entity classes and so on.
public class DataContext : DbContext
{
public DataContext(DbContextOptions<DataContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { }
// Tables
public DbSet<User> Users { get; set; }
public DbSet<Challenge> Challenges { get; set; }
// Data seeding
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Seed();
}
}
DatabaseHandler/HandleChallenges.cs:
This is where I have all my database functions. The results are returned back to the controller within the WebApp project.
public class HandleChallenges
{
public static List<Challenge> GetAllChallenges(DataContext db)
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
I have looked into dependency injection, but I'm not sure how I can use this between two projects. Is there a less complicated way of achieving this, perhaps without using DI at all? I'm satisfied as long as I don't need to pass the DataContext object as an argument every time I need to call a function from DatabaseHandler.
Can someone help me understand? Thanks a lot in advance!
You could use Options pattern, which I have already used many times. Its working very well despite of database you use. Thanks to dependency injection you are able to access if from multiple projects. Reading documentation about Option pattern (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/options?view=aspnetcore-3.1) is useful but I will also provide you with my own example :
First you create model to store you connection string, dbName etc. Remember to add it in a library outside your main project(eg. Web Api) :
public class NameOfYourProject_ApiDbSettings : IIMTTApiDbSettings
{
public NameOfYourProject_ApiDbSettings()
{
}
public string CollectionName { get; set; }
public string ConnectionString { get; set; }
public string DatabaseName { get; set; }
}
public interface I_NameOfYourProject_ApiDbSettings
{
string CollectionName { get; set; }
string ConnectionString { get; set; }
string DatabaseName { get; set; }
}
Secondly you make it available for all you projects :
services.Configure<NameOfYourProjectApiDbSettings>(options =>
{
options.ConnectionString
= Configuration.GetSection("NameOfYourProjectDbSettings:ConnectionString").Value;
options.DatabaseName
= Configuration.GetSection("NameOfYourProjectDbSettings:DatabaseName").Value;
});
Then you can use it in multiple projects. (Rememebr to add referance to you model -> point 1. I keep the model always with repository) I will give you my example where I use MongoDb :
private readonly IMongoDatabase _database = null;
public SomeObjectContext(IOptions<IMyProjectDbSettings> settings)
{
var client = new MongoClient(settings.Value.ConnectionString);
if (client != null)
_database = client.GetDatabase(settings.Value.DatabaseName);
}
public IMongoCollection<MyModel> MyModels
{
get
{
return _database.GetCollection<MyModel>("MyModels");
}
}
You need to extract an interface from the class (note the method is no longer static) and add a constructor for the context:
public interface IHandleChallenges
{
List<Challenge> GetAllChallenges();
}
public class HandleChallenges : IHandleChallenges
{
public HandleChallenges(DataContext context)
{
db = context;
}
private DataContext db;
public List<Challenge> GetAllChallenges()
{
var Data = db.Challenges;
List<Challenge> ChallengesList = Data.ToList();
return ChallengesList;
}
}
Then register it as a service:
services.AddScoped<IHandleChallenges, HandleChallenges>();
Your controller now receives this class in it's constructor instead of the context:
private IHandleChallenges _challengeHandler;
public HomeController(ILogger<HomeController> logger, IHandleChallenges challengeHandler)
{
_logger = logger;
_challengeHandler = challengeHandler;
}
And calls it from the action:
public IActionResult Challenges()
{
List<Challenge> ChallengesList = _challengeHandler.GetAllChallenges();
return View(ChallengesList);
}

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)
{
}
}

Injecting DbContext into LoggerProvider throws StackOverflowException in .NET Core

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.

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

Categories

Resources