C# EF 6 - implementing a generic DBContext constructor - c#

I am trying to create a ContextFactory, for an ASP.NET framework 4.8 application but I have this error message:
"CS0314: The type 'TContext' cannot be used as type parameter 'TContext' in the generic type or method 'IContextFactory'. There is no boxing conversion or type parameter conversion from 'TContext' to 'System.Data.Entity.DbContext'."
Maybe someone know how to solve this problem?
My Interfase Class:
public interface IContextFactory<TContext> where TContext : DbContext
{
TContext CreateDefault();
}
Interfase implementation Class:
public class ContextFactory<TContext> : IContextFactory<TContext>
where TContext : new()
{
public TContext CreateDefault()
{
TContext tContext = new TContext();
return tContext;
}
}
My Context Class:
public class SqlDbContext : DbContext
{
public SqlDbContext() : base("name=SqlConnection")
{}
public DbSet<Role> Roles { get; set; }
public DbSet<User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Role>()
.HasMany(e => e.Users)
.WithMany(e => e.Roles)
.Map(m => m.ToTable("UsersInRoles").MapLeftKey(new[] { "Rolename", "ApplicationName" }).MapRightKey("LoginName"));
}
}

Your class definition must restate the generic type restriction from the interface where TContext : DbContext or have a more restrictive one that fulfills the interface generic type restriction.
You are receiving the error because your class currently only restricts the generic type to being something that can be instantiated, not to a DbContext.
public class ContextFactory<TContext> : IContextFactory<TContext>
where TContext : DbContext

I solved this problem with this changes:
My Interfase Class:
public interface IContextFactory<TContext>
{
TContext CreateDefault();
}
Interfase implementation Class:
public class ContextFactory<TContext> : IContextFactory<TContext>
where TContext : new()
{
public TContext CreateDefault()
{
TContext tContext = new TContext();
return tContext;
}
}

Related

Autofac register assembly types as closed types of

The problem: I have to register multiple concrete implementations of generic base class using Autofac. Base class implements generic interface. But the trouble comes when one of the concrete implementations extends its constructor with additional parameters that base class does not have.
public interface IGenericHandler<in TModel, in TContext, TResult>
where TModel : class
where TContext : class
where TResult : class
{
Task<TResult> ExecuteAsync(TModel model, TContext context);
}
public abstract class HandlerBase<TModel> : IGenericHandler<TModel, ConcreteContext, ConcreteResult>
where TModel : class
{
protected readonly DbContext dbContext;
protected readonly ILifetimeScope lifetimeScope;
public HandlerBase(DbContext dbContext, ILifetimeScope lifetimeScope)
{
this.dbContext = dbContext;
this.lifetimeScope = lifetimeScope;
}
protected abstract Task<ConcreteResult> ExecuteAsync(TModel model, ConcreteContext);
}
public class ConcreteHandler1 : HandlerBase<ConcreteModel1>
{
ConcreteHandler1(DbContext dbContext, ILifetimeScope lifetimeScope) : base(dbContext, lifetimeScope)
{
}
protected override async Task<ConcreteResult> ExecuteAsync(ConcreteModel1 model, ConcreteContext context)
{
/* implementation */
}
}
public class ConcreteHandler2 : HandlerBase<ConcreteModel2>
{
private readonly IDependency dependency;
ConcreteHandler2(IDependency dependency, DbContext dbContext, ILifetimeScope lifetimeScope) : base(dbContext, lifetimeScope)
{
this.dependency = dependency;
}
protected override async Task<ConcreteResult> ExecuteAsync(ConcreteModel2 model, ConcreteContext context)
{
/* implementation */
}
}
public interface IDependency
{
}
public class Dependency : IDependency
{
}
// DI registration
public class HandlerModule : Module
{
protected override void Load(ContainerBuilder builder)
{
base.Load(builder);
builder.RegisterType<Dependency>().As<IDependency>();
builder.RegisterAssemblyTypes(typeof(ConcreteHandler1).Assembly).AsClosedTypesOf(typeof(IGenericHandler<,,>));
}
}
ConcreteHandlers are placed in the same folder while HandlerBase is in another and that is the reason why we are registering assembly type of the concrete one and not of the base handler.
Question is how to register ConcreteHandler2 while keeping the registration of the assembly types based on the generic interface?

EF core Using a generic factory to register a DB context

I am out to sea with this.
I received this answer which was corrected here by Nkosi.. however whilst it indicated how to rewrite the factory I am not up to the task of implementing it.. I need some gaps filled in.
As I understand it, this is what I need to do..
define the interface:
public interface IContextFactory<TContext> where TContext : DbContext
{
TContext Create(string connectionString);
}
Create the factory:
public class ContextFactory<TContext> : IContextFactory<TContext>
where TContext : DbContext
{
public TContext Create(DbContextOptions<TContext> options)
{
return (TContext)Activator.CreateInstance(typeof(TContext), options);
}
}
register the factory as a singleton in startup.
This is what I did and I dont know if this is correct?
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
Next how the heck do I use this factory?
In the first answer, it was suggested I use this:
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
private JobsLedgerAPIContext _context;
public string ConnectionString { get; set; }
public EntityBaseRepository(IContextFactory<JobsLedgerAPIContext> factory)
{
_context = factory.CreateDbContext(ConnectionString);
}
public virtual IQueryable<T> GetAll()
{
return _context.Set<T>().AsQueryable();
}
public virtual int Count()
{
return _context.Set<T>().Count();
}
}
Nkosi identified that you cannot have the constraint "new()" and a parameterized constructor. He rewrote it. How do I change the above code to reflect the fact the factory now does not have a parameterized constructor but still take in the connection string given Nkosi's factory?
ClientRepository inherits the above.
public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
private new JobsLedgerAPIContext _context;
public ClientRepository(JobsLedgerAPIContext context) : base(context)
{
_context = context;
}
Does this constructor need to be changed.
And finally how do I Inject the ClientRepository now that I have to supply a connection string?

Error: 'T' must be a non-abstract type with a public parameterless constructor

I created new .net core 2.1 project. And I created my classes like below. But, I take error in MyRepos.cs.
'MyDbContext' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'TContext' in the generic type or method 'UnitOfWork'
UnitOfWork.cs
public class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext : DbContext, new()
{ }
IUnitOfWork.cs
public interface IUnitOfWork<U> where U : DbContext
{ }
MyRepos.cs
public class MyRepos : UnitOfWork<MyDbContext>, IMyRepos
{ }
IMyRepos.cs
public interface IMyRepos : IUnitOfWork<MyDbContext>
{ }
MyDbContext.cs
public class MyDbContext : DbContext
{
public MyDbContext(DbContextOptions options) : base(GetOptions())
{ }
public static DbContextOptions GetOptions()
{
return SqlServerDbContextOptionsExtensions.UseSqlServer(new DbContextOptionsBuilder(), "myConnectionString").Options;
}
}
If you add a parameterless constructor to MyDbContext, I think that should fix the issue.
That's what the new() constraint implies.

EF Core Fluent API Configurations with Inheritance

EF CORE Fluent Api Configuration in separate files are Working fine with simple classes Ref #1 && Ref # 2. The problem comes when entities are Inherited from KeyedEntity or AuditableEntity
class abstract KeyedEntity<TValue> {
public TValue Id {get; set;}
}
class abstract AuditableEntity<TValue> : KeyedEntityBase<TValue>{
public DateTime DateCreated {get; set;}
public DateTime DateModified {get; set;}
}
Mapper Goes Something like this
public class KeyedEntityMap<TEntity, TId>
: IEntityTypeConfiguration<TEntity> where TEntity
: KeyedEntityBase<TId> where TId : struct
{
public void Configure(EntityTypeBuilder<TEntity> builder)
{
// Primary Key
builder.HasKey(t => t.Id);
// Properties
builder.Property(t => t.Id).HasColumnName("id").ValueGeneratedOnAdd();
}
}
public class AuditableEntityMap<TEntity, TId>
: IEntityTypeConfiguration<TEntity> where TEntity
: AuditableEntity<TId> where TId : struct
{
public void Configure(EntityTypeBuilder<TEntity> builder)
{
// Properties
builder.Property(t => t.DateCreated).HasColumnName("DateCreated");
builder.Property(t => t.DateModified).HasColumnName("DateModified");
}
}
Now the Problem Occurs with the Entity that inherits from AuditableEntity. I need to register Map from that Particular Enitity class along with AuditableEntityMap class and KeyedEntityMap class.
Now I can either forget about Map Inheritance and merge all the complex inheritance Maps in the entity class, which I don't want to do and respect DRY . The problem with complex inheritance is its not registering my entity maps
There are several ways you can achieve DRY for base entity configuration.
Bit the closest to your current design is to simply follow the entity hierarchy in the configuration classes:
public class KeyedEntityMap<TEntity, TId> : IEntityTypeConfiguration<TEntity>
where TEntity : KeyedEntityBase<TId>
where TId : struct
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
// ^^^
{
// Primary Key
builder.HasKey(t => t.Id);
// Properties
builder.Property(t => t.Id).HasColumnName("id").ValueGeneratedOnAdd();
}
}
public class AuditableEntityMap<TEntity, TId> : KeyedEntityMap<TEntity, TId>
// ^^^
where TEntity : AuditableEntity<TId>
where TId : struct
{
public override void Configure(EntityTypeBuilder<TEntity> builder)
// ^^^
{
base.Configure(builder); // <<<
// Properties
builder.Property(t => t.DateCreated).HasColumnName("DateCreated");
builder.Property(t => t.DateModified).HasColumnName("DateModified");
}
}
and then for specific entity that needs additional configuration:
public class Person : AuditableEntity<int>
{
public string Name { get; set; }
}
you would register
public class PersonEntityMap : AuditableEntityMap<Person, int>
{
public override void Configure(EntityTypeBuilder<Person> builder)
{
base.Configure(builder);
// Properties
builder.Property(t => t.Name).IsRequired();
// etc...
}
}

Using Automapper with generics

I have a base class Repository which contains base functionality for a set of classes that inherit from it such as UserRepository or DepartmentRepository.
I'm using automapper to map between my entity framework objects and my domain objects.
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class {
protected readonly DbContext Context;
public Repository(DbContext context) {
Context = context;
}
public TEntity Get(int id) {
return Context.Set<TEntity>().Find(id);
}
}
public class UserRepository : Repository<User>, IUserRepository {
public UserRepository(DbContext context) : base(context) {
}
public User GetUserByNTId(string NTId) {
return Mapper.Map<User>(DbContext.Users.SingleOrDefault(u => u.NTId == NTId));
}
}
GetUserByNTId will work because I can use automapper in the return statement. But Get will not work because it deals with TEntity and I don't know how you can tell automapper to examine the type of TEntity and look for a matching mapping.
How can I change the return statement of the Get function so that it will work for all my derived repositories and still use automapper? Or do I just have to take all my general functions and push them down into the derived classes and eliminate the Repository base class?
It's a very bad idea to for mixing AutoMapper and other responsibilities when querying from a Repository pattern. It's also of course a violation of the Single Responsibility Principle.
A naive non tested implementation will be:
public class ExampleOfGenericRepository<TEntity> : Repository<TEntity>
where TEntity : class
{
public ExampleOfGenericRepository(DbContext context)
: base(context)
{
}
public TEntity GetById(int id)
{
return Mapper.Map<TEntity>(Get(id));
}
}

Categories

Resources