Instantiate multiple DbContext manually without dependency injection - c#

I want to create an ASP.Net Core web service which will select rows from one SQL Server database and insert them into X SQL server database(s). All databases have the same structure (same model).
I don't want to inject DbContext because I don't know how many context I will have to use and it will be difficult to maintain.
Is it possible to manually create a DbContext in a controller or a manager like :
MyContextClass dbContext = new MyContextClass("myConnectionString");
Thank you

Yes, it is possible to just create a new DbContext.
However when using DI, you should write and inject something like a DbContextFactory class that you can use to create a new context and that itself gets the DbContextOptions from your configuration.
public class ContextFactory<TContext>
where TContext : DbContext
{
private readonly Func<TContext> _createContext;
public ContextFactory(Func<TContext> createContext)
{
_createContext = createContext ?? throw new ArgumentNullException(nameof(createContext));
}
TContext CreateForRead()
{
var context = Create();
context.ChangeTracker.AutoDetectChangesEnabled = false;
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return context;
}
TContext CreateForWrite() => Create();
private TContext Create()
{
var context = _createContext();
if (context == null)
throw new NullReferenceException($"{nameof(_createContext)} must not return null.");
return context;
}
}
For easier use, create an extension class:
public static class ServiceCollectionDataExtensions
{
public static void AddDatabase<TDbContext>(this IServiceCollection services, string connectionString)
where TDbContext : DbContext
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (string.IsNullOrEmpty(connectionString))
throw new ArgumentNullException(nameof(connectionString));
services.AddDbContext<TDbContext>(c => c.UseSqlServer(connectionString), ServiceLifetime.Transient);
services.AddScoped(provider => new ContextFactory<TDbContext>(() => ActivatorUtilities.CreateInstance<TDbContext>(provider, provider.GetRequiredService<DbContextOptions<TDbContext>>())));
}
}
And then inside your public void ConfigureServices(IServiceCollection services) add your connection string from your configuration:
services.AddDatabase<MyDbContext>(Configuration.GetConnectionString("MyDatabase"));

Related

What is the best way to isolate .ToListAsync()?

I have a Blazor App which uses EntityFrameworkCore and a Generic repository.
In my Services I query the repository, but when I try to call .ToListAsync(); - it requires that I add - using Microsoft.EntityFrameworkCore;
This means it will create a dependency to EF. I don't want to bind my service to EF.
.ToListAsync() is an extension method.
What is the best way to isolate the Service from it? I achieved it through an additional wrapper class - AsyncConverter. Is there a better way?
public class AsyncConverter : IAsyncConverter
{
public Task<List<TSource>> ConvertToListAsync<TSource>(IQueryable<TSource> source, CancellationToken cancellationToken = default)
{
return source.ToListAsync(cancellationToken);
}
}
public class EfRepository<TEntity> : IRepository<TEntity>
where TEntity : class
{
public EfRepository(ApplicationDbContext context)
{
this.Context = context ?? throw new ArgumentNullException(nameof(context));
this.DbSet = this.Context.Set<TEntity>();
}
protected DbSet<TEntity> DbSet { get; set; }
protected ApplicationDbContext Context { get; set; }
public virtual IQueryable<TEntity> All() => this.DbSet;
}
public class ItemsDataService : IItemsDataService
{
private readonly IRepository<Item> _itemsRepository;
private readonly IMapper _mapper;
private readonly IAsyncConverter _asyncConverter;
private readonly IStringLocalizer<Errors> _localizer;
public ItemsDataService(IRepository<Item> itemsRepository,
IMapper mapper,
IAsyncConverter asyncConverter,
IStringLocalizer<Errors> localizer = null)
{
_itemsRepository = itemsRepository;
_mapper = mapper;
_asyncConverter = asyncConverter;
_localizer = localizer;
}
public async Task<Response<IEnumerable<ItemNameDto>>> GetItemsNamesAsync(string searchWord, string userId)
{
try
{
searchWord.ThrowIfNull();
userId.ThrowIfNullOrEmpty();
var query = _itemsRepository.All()
.Where(x => x.UserId == userId);
var names = new List<ItemNameDto>();
if (!string.IsNullOrEmpty(searchWord))
{
query = query.Where(x => x.Name.ToLower().Contains(searchWord.ToLower()));
}
var queryProjection = _mapper.ProjectTo<ItemNameDto>(query); **/*.ToListAsync(); - This would add a dependency to EntityFrameworkCore. That it why I introduced AsyncConverter*/**
names = await _asyncConverter.ConvertToListAsync(queryProjection);
var response = ResponseBuilder.Success<IEnumerable<ItemNameDto>>(names);
return response;
}
catch (Exception ex)
{
var response = ResponseBuilder.Failure<IEnumerable<ItemNameDto>>("An error occured while getting items names.");
return response;
}
}
My usual approach is to put all the query-building code in my repositories, and have them return a materialized result. So I can mock the repository layer when unit-testing my services class.
Trying to unit-test the data access layer can be fraught, since it's inherently designed to be an integration layer. Queries that work one way when you are using in-memory objects may have different behavior when they're translated by Entity Framework.
If you prefer building your queries in the service layer, Ian Kemp's approach can work, basically putting your ConvertToListAsync method into the EfRepository class and its interface instead of having a whole new service to inject.

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

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

How to I access the DbContext of EF core from another project when used in ASP.NET core?

I followed the pattern to use EF Core with ASP.NET core and all is well. But recently I created a 'Calculation' project and want to make database calls from it.
The problem is I don't know how to create a new DbContextOptions. In my code that is done with
services.AddDbContext<RetContext>(options => options
.UseLazyLoadingProxies()
.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
But in a new .NET core class I need to provide it manually. How do I do this ? My code is like this:
public static class LoadData
{
public static IConfiguration Configuration { get; }
public static RefProgramProfileData Load_RefProgramProfileData(string code)
{
// var optionsBuilder = new DbContextOptionsBuilder<RetContext>();
// optionsBuilder.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
//How do I make an optionsbuilder and get the configuration from the WEB project?
UnitOfWork uow = new UnitOfWork(new RetContext(optionsBuilder));
var loadedRefProgramProfileData = uow.RefProgramProfileDataRepository
.Find(x => x.ProgramCode == code).FirstOrDefault();
return loadedRefProgramProfileData;
}
}
You may instantiate your DbContext like this:
var builder = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json");
var configuration = builder.Build();
var optionsBuilder = new DbContextOptionsBuilder<RetContext>();
optionsBuilder.UseSqlServer(configuration.GetConnection("DefaultConnection"));
_context = new RetContext(optionsBuilder.Options);
However, the ideal is to use dependency injection. Let's say you have a class CalculationService in your other project. For that, you need to register that class as a service that can be injected:
services.AddScoped<CalculationService>();
Then your class can receive DbContext (or any other services) through DI:
public class CalculationService
{
private RetContext _context;
public CalculationService(RetContext context)
{
_context = context;
}
}
Naturally, you won't be able to instantiate your class manually like this:
var service = new CalculationService();
Instead, you'd need to make whatever class needs to use your CalculationService to also receive it through DI and make that class injectable as well.

DbContext and scoped dependency

I have a simple DbContext looking like:
public class MyDbContext : DbContext
{
private readonly IUserContext _userContext;
public MyDbContext(IUserContext userContext) : base("DefaultConnectionString")
{
_userContext = userContext;
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Configuration>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// ... Here I need to creates some filters using the IUserContext dependency
base.OnModelCreating(modelBuilder);
}
}
This DbContext is wired using Func<T> factory, using the guidelines in the Simple Injector documentation: container.RegisterFuncFactory<DbContext, MyDbContext>(Lifestyle.Scoped);
public static void RegisterFuncFactory<TService, TImpl>(
this Container container, Lifestyle lifestyle = null)
where TService : class
where TImpl : class, TService
{
lifestyle = lifestyle ?? Lifestyle.Transient;
var producer = lifestyle.CreateProducer<TService, TImpl>(container);
container.RegisterSingleton<Func<TService>>(producer.GetInstance);
}
But apperently, such simple case is not possible with the DbContext because of this message:
The target context 'MyDbContext' is not constructible. Add a default
constructor or provide an implementation of IDbContextFactory.
I dont really like the idea of the IDbContextFactory, so the only solution I can come up with is to remove the dependency on the MyDbContext, set it as a property, modify the RegisterFuncFactory method and manually initialize the context:
internal static void RegisterFuncFactory<TService, TImpl>(this Container container, Func<TImpl> instanceProducer, Lifestyle lifestyle = null) where TService : class where TImpl : class, TService
{
lifestyle = lifestyle ?? Lifestyle.Transient;
var producer = lifestyle.CreateProducer<TService>(instanceProducer, container);
container.Register<Func<TService>>(() => producer.GetInstance, Lifestyle.Singleton);
}
container.RegisterFuncFactory<DbContext, MyDbContext>(() => new MyDbContext
{
UserContext = container.GetInstance<IUserContext>()
}, Lifestyle.Scoped);
While not elegant it works, but is there another and "better" way of doing what I need? I like the explicitly of the dependency on the context, but seem not possible.
UPDATE
The error is coming from:
'System.Data.Entity.Migrations.Infrastructure.MigrationsException'
occurred in EntityFramework.dll but was not handled in user code
On this code the return statement of the Query method here:
internal sealed class EntityFrameworkRepository<TEntity> : IEntityWriter<TEntity>, IEntityReader<TEntity> where TEntity : Entity
{
private readonly Func<DbContext> _contextProvider;
public EntityFrameworkRepository(Func<DbContext> contextProvider)
{
_contextProvider = contextProvider;
}
public IQueryable<TEntity> Query()
{
var context = _contextProvider();
return context.Set<TEntity>().AsNoTracking();
}
// Methods removed for brevity
}
Add a second (default) constructor. This way EF migrations can use this constructor when run from the command line, while you can let your application use the second constructor.
You loose Simple Injector's auto-wiring capabilities on your DbContext when you add this second constructor, but this shouldn't be a problem; you can simply wire your context as follows:
IUserContext userContext = new AspNetUserContext();
container.RegisterSingleton<IUserContext>(userContext);
var contextProducer = Lifestyle.Scoped.CreateProducer<DbContext>(
() => new MyDbContext(userContext),
container);
container.RegisterSingleton<Func<DbContext>>(contextProducer.GetInstance);
This answer is only to display for further users what I ended up with. #Steven answer is the right answer.
In order to be able to inject dependencies into the the DbContext while supporting migrations, we have to use two constructors. One for the migrations and one for the application.
public class MyDbContext : DbContext
{
private readonly IUserContext _userContext;
// For migrations
public MyDbContext() : base("DefaultConnectionString")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Configuration>());
}
// For applications
public MyDbContext(IUserContext userContext) : base("DefaultConnectionString")
{
_userContext = userContext;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// ... Code removed for brevity
base.OnModelCreating(modelBuilder);
}
}
This is then wired in the composition root like:
public static void RegisterEntityFramework<TContext>(this Container container, Func<TContext> context) where TContext : DbContext
{
if (container == null) throw new ArgumentNullException(nameof(container));
var contextProducer = Lifestyle.Scoped.CreateProducer<DbContext>(context, container);
container.RegisterSingleton<Func<DbContext>>(() => contextProducer.GetInstance);
}
var userContext = new AspNetHttpUserContext();
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
container.RegisterSingleton<IUserContext>(userContext);
container.RegisterEntityFramework(() => new WayFinderDbContext(userContext));
container.Verify();

Categories

Resources