EF Core - Using DbSet dynamically with generics - c#

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

Related

C# Tests with EF InMemory Database System.InvalidOperationException

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?

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;

How to use Generic CRUD in Entity Framework using DI

I wanna to try generic but I get some problem.
and this is my step
step 1. I create a database Model also inheritance class
public class DBRepo { }
public partial class UserAccount : DBRepo
{
public int Id { get; set; }
public string Account { get; set; }
public string Pwd { get; set; }
}
step 2. I wish all CRUD action can using this interface. so I do this
public class DBServices
{
public interface IDBAction<TEntity> where TEntity : DBRepo
{
void InsertData(TEntity entity);
}
public class dbCRUD<TEntity> : IDBAction<TEntity> where TEntity : DBRepo
{
private readonly CoreContext _db;
private DbSet<TEntity> dbSet;
public dbCRUD(CoreContext _db)
{
this._db = _db;
this.dbSet = _db.Set<TEntity>();
}
public void InsertData(TEntity _entity)
{
this.dbSet.Add(_entity);
this._db.SaveChanges();
}
}
}
and then I usine ServiceProvider like
ServiceProvider provider = new ServiceCollection()
.AddSingleton<IDBAction<DBRepo>>()
.BuildServiceProvider();
provider.GetService<IDBAction<DBRepo>>().InsertData(_ua);
and I'll get this error
Cannot instantiate implementation type ....
so I change to try other way like.
in Constructor
private readonly IDBAction<DBRepo> dBAction;
public HomeController( IDBAction<DBRepo> _dBAction)
{
this.dBAction = _dBAction;
}
....
this.dBAction.InsertData(_ua);
sure.I get error again
InvalidOperationException: Unable to resolve service for type...
have some can teach me how to fix the problem?
-> Update
I try to change like but it's failed
ServiceProvider provider = new ServiceCollection()
.AddScoped<IDBAction<DBRepo>, dbCRUD<DBRepo>>()
.AddScoped<CoreContext>()
.BuildServiceProvider();
error same this
Unable to resolve service for type...
it's my DBContext
public virtual DbSet<UserAccount> UserAccount { get; set; }
public CoreContext(DbContextOptions<CoreContext> options)
: base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer(#"Connection String");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserAccount>(entity =>
{
entity.Property(e => e.Account).IsRequired();
entity.Property(e => e.Pwd)
.IsRequired()
.HasMaxLength(20);
});
}
You are registering it in the wrong way. You must provide the implementation of your (generic) and also the DbContext must be registered.
ServiceProvider provider = new ServiceCollection()
.AddSingleton<IDBAction<DBRepo>, dbCRUD<DBRepo>>()
.BuildServiceProvider();
provider.GetService<IDBAction<DBRepo>>().InsertData(_ua);
Also registering it as a singleton will cause problems with change-tracking of entity-framework. So you should register it like this:
ServiceProvider provider = new ServiceCollection()
.AddScoped<IDBAction<DBRepo>, dbCRUD<DBRepo>>()
.AddScoped<CoreContext>()
.BuildServiceProvider();
provider.GetService<IDBAction<DBRepo>>().InsertData(_ua);
You could also implement a real generic like this
public interface IEntity
{
Guid Id { get; set; }
}
public class DbAction<TEntity> : IDbAction<TEntity> where TEntity: class, IEntity, new()
{
public void InsertData(TEntity entity)
{
...
}
}
Now register it as a generic
ServiceProvider provider = new ServiceCollection()
.AddScoped(typeof(IDbAction<>), typeof(DbAction<>))
.AddScoped<CoreContext>()
.BuildServiceProvider();
provider.GetService<IDBAction<DBRepo>>().InsertData(_ua);
Small example, but would work.
Edit:
DbContextOptions must of course be passed to the DbContext for the IoC to work.
.AddDbContext<CoreContext>(options => options.UseSqlServer("my-conntection-string")); // change provider if necessary, this will only work with MS SQL Server

API controller is not effected by dependency resolver, Make sure that the controller has a parameterless public constructor

I am Creating a web api using Repository pattern and 3 tier architecture. i also made a IOC Container (Autofac) for hadling dependencies. But i am keep getting error, in tried everything. The Error is: An error occurred when trying to create a controller of type 'StudentsController'. Make sure that the controller has a parameterless public constructor.
Here is my Code:
My Context Class:
public class StudentContext: DbContext
{
public StudentContext(): base("name=StudentDB")
{
//Database.SetInitializer<StudentContext>(null);
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new StudentMap());
}
public DbSet<Student> Students { get; set; }
}
My Repository Class:
public class Repository<T> : IRepository<T> where T : class
{
public readonly StudentContext context;
public IDbSet<T> entities;
public Repository(StudentContext _context)
{
this.context = _context;
entities = _context.Set<T>();
}
public IQueryable<T> Table()
{
return this.Entities;
}
public void Insert (T entity)
{
this.Entities.Add(entity);
this.Save();
}
private IDbSet<T> Entities
{
get
{
if (entities == null)
entities = context.Set<T>();
return this.entities;
}
}
public void Save()
{
context.SaveChanges();
}
}
My Services Class is as:
public class StudentService: IStudentService
{
public IRepository<Student> _StudentRepository;
public StudentService(IRepository<Student> sturepo)
{
this._StudentRepository = sturepo;
}
public IQueryable<Student> GetAllStudents()
{
return _StudentRepository.Table();
}
public void InsertStudent(Student std)
{
_StudentRepository.Insert(std);
}
}
My API Controller:
public class StudentsController : ApiController
{
public IStudentService studentservice;
public StudentsController(IStudentService stuservce)
{
this.studentservice = stuservce;
}
public IList<Student> GetStudents()
{
List<Student> student = studentservice.GetAllStudents().ToList();
return student.ToList();
}
}}
Autofac Container Class:
public class AutofacConfig
{
public static void ConfigureContainer()
{
var builder = new ContainerBuilder();
// Get your HttpConfiguration.
var config = GlobalConfiguration.Configuration;
builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly()).Where(t => !t.IsAbstract && typeof(ApiController).IsAssignableFrom(t))
.InstancePerMatchingLifetimeScope();
// Register your Web API controllers.
builder.RegisterApiControllers(Assembly.GetExecutingAssembly());
// OPTIONAL: Register the Autofac filter provider.
builder.RegisterWebApiFilterProvider(config);
// OPTIONAL: Register the Autofac model binder provider.
builder.RegisterWebApiModelBinderProvider();
// Set the dependency resolver to be Autofac.
var container = builder.Build();
config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
}
}
Calling Autofac in Global.Ascx:
protected void Application_Start()
{
AutofacConfig.ConfigureContainer();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
}

Categories

Resources