C# Tests with EF InMemory Database System.InvalidOperationException - c#

I'm trying to use EF Core InMemory Database and XUnit in my integration tests, but unfortunately, I'm getting this Exception:
System.InvalidOperationException : Relational-specific methods can only be used when the context is using a relational database provider.
This is the Class that uses the WebApplicationFactory:
public class TestFactory<TProgram, TDbContext> : WebApplicationFactory<TProgram>
where TProgram : class where TDbContext : DbContext
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureTestServices(services =>
{
services.RemoveDbContext<TDbContext>();
services.AddDbContext<TDbContext>(options =>
{
options.UseInMemoryDatabase(Guid.NewGuid().ToString());
});
});
}
}
This is the base Class used in the Test Classes:
public class TestFactoryBase : IClassFixture<TestFactory<Program, CDbContext>>
{
public HttpClient _client;
public CDbContext _dbContext;
public TestFactoryBase(TestFactory<Program, CDbContext> factory) {
_client = factory.CreateClient();
_dbContext = factory.Services.GetService<CDbContext>();
}
}
This is the Test Class:
public class RolesControllerTest : TestFactoryBase
{
private IRoleRepository _rolesRepository;
public RolesControllerTest(TestFactory<Program, CDbContext> factory) : base(factory)
{
_rolesRepository = CreateRoleRepository(_dbContext);
}
// Tests Here!
}
How can I solve this exception?

Related

How to use Autofac IComponentContext as service locator?

Hi following is my custom module for Autofac DI.
public class AutofacBusinessModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType<BaseService>().As<IComponentContext>().InstancePerRequest();
builder.RegisterGeneric(typeof(GenericRepository<>)).As(typeof(IGenericRepository<>));
builder.RegisterType<TrainingModuleContext>().As<IDataContext>().InstancePerLifetimeScope();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerRequest();
builder.RegisterType<RegionRepository>().As<IRegionRepository>().InstancePerLifetimeScope();
builder.RegisterType<CourseRepository>().As<ICourseRepository>().InstancePerLifetimeScope();
builder.RegisterType<TrainingRepository>().As<ITrainingRepository>().InstancePerLifetimeScope();
builder.RegisterType<TrainingService>().As<ITrainingService>().InstancePerLifetimeScope();
base.Load(builder);
}
}
This is my base service class
public abstract class BaseService
{
protected IComponentContext IComponentContext;
protected BaseService(IComponentContext componentContext)
{
this.ComponentContext = componentContext;
}
}
This is my training Service calss.
public class TrainingService : BaseService, ITrainingService { private IUnitOfWork _unitOfWork => ComponentContext.Resolve<IUnitOfWork>();
// private readonly IUnitOfWork _unitOfWork;
public TrainingService(IComponentContext componentContext) : base(componentContext)
{
//_unitOfWork = unitOfWork;
//this.componentContext = componentContext;
}
public async Task<IReadOnlyList<Training.Domain.Entities.Training>> GetAll()
{
var data = await _unitOfWork.GetRepository<Training.Domain.Entities.Training>().GetAllAsync();
return data;
}
public async Task AddTraining(Training.Domain.Entities.Training training)
{
await _unitOfWork.GetRepository<Training.Domain.Entities.Training>().AddAsync(training);
_ = await _unitOfWork.SaveAsync();
}
}
But on build I am getting always error like
System.ArgumentException: 'The type 'TrainingAPI.Services.BaseService' is not assignable to service 'Autofac.IComponentContext'.'
Please help me what mistake I am doing.

Error saying.No database provider has been configured for this DbContext

I am facing an error while accesing data from database table saying 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.
I have used generic repository pattern. I have my DbContext i.e BlazorContext and model class in seperate class library project.
Any help will be grate.
Below is my connection string in appsettings.json
"ConnectionStrings": {
"myconn": "server=DESKTOP-VM2VP34; database=BlazorDB;Trusted_Connection=True;"
},<br><br>
Below is my startup.cs
services.AddDbContext<BlazorContext>(item => item.UseSqlServer(Configuration.GetConnectionString("myconn")));
Below is my DbContext i.e BlazorContext
namespace Blazor.Model.Models
{
public class BlazorContext:DbContext
{
public BlazorContext()
{
}
public BlazorContext(DbContextOptions<BlazorContext> options)
: base(options)
{
Database.EnsureCreated();
}
public DbSet<Person> Persons { get; set; }
}
}
Below is my generic repository implementation where it show error
namespace Blazor.Repository.Implementation
{
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
protected BlazorContext _entities;
protected readonly DbSet<T> _dbset; // error in this line
public GenericRepository(BlazorContext context)
{
_entities = context;
_dbset = context.Set<T>();
}
public IEnumerable<T> GetAll()
{
return _dbset.AsEnumerable();
}
}
}
Below is my GenericUnitOfWork
namespace Blazor.Repository.Implementation
{
public sealed class GenericUnitOfWork : IGenericUnitOfWork, IDisposable
{
private BlazorContext entities = null;
public GenericUnitOfWork()
{
entities = new BlazorContext();
}
public Dictionary<Type, object> repositories = new Dictionary<Type, object>();
public IGenericRepository<T> Repository<T>() where T : class
{
if (repositories.Keys.Contains(typeof(T)) == true)
{
return repositories[typeof(T)] as IGenericRepository<T>;
}
var t = typeof(T);
IGenericRepository<T> repo = new GenericRepository<T>(entities);//error in this line
repositories.Add(typeof(T), repo);
return repo;
}
}
}
Your connection string template is not correct for the MS SQL server. Try to use something like this:
"Data Source=localhost;Initial Catalog=BlazorDB;Integrated Security=SSPI;Persist Security Info=True;"
If it doesn't work then you use not MS SQL server and so in your startup you have to replace "item => item.UseSqlServer" with other provider.

EF Core - Using DbSet dynamically with generics

I have an interface that is being attached in my DI
builder.RegisterType<TaskTempo>().As<ITaskTempo>();
This is being done because ITaskTempo is dynamic and could be one of multiple objects, but sharing same interface.
Anyway, I have DbContext where I map the DbSet with entity manually
// DI register
builder.RegisterType<MapperFor890>().As<IEntityMapper>();
// ...
// DbContext
public partial class MyDBContext : DbContext
{
private readonly IEntityMapper _entityMapper;
// Please not that I do not want to manually add all of diff implementations of ITaskTempo here
// public virtual DbSet<TaskTempo1> TaskTempo { get; set; }
public MyDBContext(DbContextOptions<MyDBContext> options, IEntityMapper mapper)
: base(options)
{
_entityMapper = mapper;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Map Entities
_entityMapper.Map(modelBuilder, this);
}
}
// ...
// EntityMapper
public class MapperFor890 : IEntityMapper
{
public void Map(ModelBuilder modelBuilder, DbContext context)
{
modelBuilder.Entity<TaskTempo1>(entity =>
{
// Map here the properties
});
}
}
Then, I am able to fetch data from EfRepository:
// Global Repository
public class EfRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly DbContext Context;
protected readonly ILogger<EfRepository<TEntity>> Logger;
public EfRepository(DbContext context,
ILogger<EfRepository<TEntity>> logger)
{
Context = context;
Logger = logger;
}
public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
{
Logger.LogDebug($"Finding '{typeof(TEntity).Name}' with predicate");
return Context.Set<TEntity>().Where(predicate);
}
}
// TaskTempo Repository
public class TaskTempoRepository<TTaskTempo> : EfRepository<TTaskTempo>, ITaskScanRepository<TTaskTempo> where TTaskTempo : class, ITaskTempo
{
public MyDBContext DbContext => Context as MyDBContext;
public TaskScanRepository(MyDBContext context, ILogger<TaskScanRepository<TTaskTempo>> logger) : base(context,logger)
{
}
}
// DI injection
builder.RegisterType<TaskTempo1>().As<ITaskTempo>();
builder.RegisterType<TaskTempoRepository<ITaskTempo>>().As<ITasTempoRepository<ITaskTempo>>();
Once I fetch
_taskTempoRepository.Find(t => t.Id = 3);
I am getting the following error:
Cannot create a DbSet for 'ITaskTempo' because this type is not included in the model for the context

System.ArgumentNullException when GetDbContext in constructor

I'm trying to use Entity Framework Core with ASP.NET Boilerplate .NET Core, but I don't want to use Repository built-in functions.
There is a problem with my DB context; it keeps returning:
System.ArgumentNullException: 'Value cannot be null.'
for the DbContext instance as shown below:
public class MainProjectsAppService : ApplicationService
{
private readonly DecentralizationDbContext _ctx;
public MainProjectsAppService(IDbContextProvider<DecentralizationDbContext> dbContextProvider)
{
_ctx = dbContextProvider.GetDbContext();
}
public void CustomizedCreateMainProject(MainProject mainProject)
{
MainProject customizedMainProject = new MainProject
{
...
};
_ctx.MainProjects.Add(customizedMainProject);
_ctx.SaveChanges();
}
}
Below is the DbContext class code:
namespace Decentralization.EntityFrameworkCore
{
public class DecentralizationDbContext : AbpZeroDbContext<Tenant, Role, User, DecentralizationDbContext>
{
/* Define a DbSet for each entity of the application */
public DbSet<MainProject> MainProjects { get; set; }
public DecentralizationDbContext(DbContextOptions<DecentralizationDbContext> options)
: base(options)
{
}
}
}
Do not call dbContextProvider.GetDbContext() in the constructor.
Define a getter instead:
public class MainProjectsAppService : ApplicationService
{
private readonly IDbContextProvider<DecentralizationDbContext> _dbContextProvider;
private DecentralizationDbContext _ctx => _dbContextProvider.GetDbContext();
public MainProjectsAppService(IDbContextProvider<DecentralizationDbContext> dbContextProvider)
{
_dbContextProvider = dbContextProvider;
}
}
Reference: aspnetboilerplate/aspnetboilerplate#4809

Repository pattern with multiple databases

I am using repository pattern on EF Core and Autofac in a windows service.
I have a service that needs to connect with the some dozen databases which have the same schema (same dbcontext) but only different data.
How can I achieve this in my service using Autofac? Belo
public class ReportRepository : IReportRepository
{
private readonly ReportDbContext dbContext;
public ReportRepository(ReportDbContext dbContext)
{
this.dbContext = dbContext
}
public SomeModel GetData()
{
return dbContext.SalesData;
}
}
public class ReportService : IReportService
{
private readonly IReportRepository reportRepositoryEUServer;
public ReportService(IReportRepository reportRepositoryEUServer)
{
this.reportRepositoryEUServer = reportRepositoryEUServer
}
public SomeModelDto GenerateReport()
{
var euData = reportRepositoryEUServer.GetData();
// I need to call other servers (e.g LATAM) here and get the data and aggregate them with euData
}
}
Create base context including all settings, dbsets etc:
public abstract class BaseContext : DbContext
{
public BaseContext(DbContextOptions options)
: base(options)
{ }
public DbSet<object> FirstSet { get; set; }
...
}
inherit from BaseContext for both DBs
public class LATAMContext : BaseContext
{
public LATAMContext(DbContextOptions<LATAMContext> options) : base(options)
{
}
}
public class EUContext : BaseContext
{
public EUContext(DbContextOptions<EUContext> options) : base(options)
{
}
}
and register both in Startup.cs
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddDbContext<LATAMContext>(options => options.UseSqlServer(Configuration.GetConnectionString("LATAMConnectionString")));
services.AddDbContext<EUContext>(options => options.UseSqlServer(Configuration.GetConnectionString("EUConnectionString")));
// Autofac
var builder = new ContainerBuilder();
// needed only if you plan to inject ICollection<BaseContext>
builder.RegisterType<LATAMContext>().As<BaseContext>();
builder.RegisterType<EUContext>().As<BaseContext>();
builder.Populate(services);
return new AutofacServiceProvider(builder.Build());
}
add connection strings in appsettings.json
"ConnectionStrings": {
"LATAMConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true",
"EUConnectionString": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
}
and now you can inject both contexts
public class ReportRepository : IReportRepository
{
private readonly LATAMContext latamDbContext;
private readonly EUContext euDbContext;
public ReportRepository(LATAMContext latamDbContext, EUContext euDbContext)
{
this.latamDbContext = latamDbContext;
this.euDbContext = euDbContext;
}
}
or if you plan to inject collection of contexts
public class ReportRepository : IReportRepository
{
private readonly ICollection<BaseContext> dbContexts;
public ReportRepository(ICollection<BaseContext> dbContexts)
{
this.dbContexts = dbContexts;
}
}
to access specific context
var _euContext = dbContexts.FirstOrDefault(x => x is EUContext) as EUContext;
var _latamContext = dbContexts.FirstOrDefault(x => x is LATAMContext) as LATAMContext;

Categories

Resources