I want to make a generic entity mapper that maps all entities to dbContext and EF. Currently, after I do Add-Migration init
, I receive an error:
Cannot create an instance of Models.DataMapping.EntityMapper`1[TEntity] because Type.ContainsGenericParameters is true.
I do not understand what this error exacly means and why if type ContainsGenericParameters
is true it should cause a crash. Any ideas what is wrong and how could I fix it?
This is my code:
namespace Models.DataMapping
{
public interface IEntityMapper
{
void Map(ModelBuilder modelBuilder);
}
}
namespace Models.DataMapping
{
public abstract class EntityMapper<TEntity> :
IEntityMapper where TEntity : class
{
public void Map(ModelBuilder modelBuilder)
{
EntityTypeBuilder<TEntity> entityTypeBuilder = modelBuilder.Entity<TEntity>();
MapEntity(entityTypeBuilder);
}
protected virtual void MapEntity(EntityTypeBuilder<TEntity> entityTypeBuilder)
{
}
}
}
namespace Models.DataMapping
{
public static class EntityMappersProvider
{
public static IReadOnlyCollection<IEntityMapper> GetLocalEntityMappers() {
return Assembly.GetExecutingAssembly()
.GetTypes()
.Where(t => typeof(IEntityMapper).IsAssignableFrom(t) && t.IsClass)
.Select(t => (IEntityMapper)Activator.CreateInstance(t))
.ToArray();
}
}
}
Including provider in dbContext.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
IReadOnlyCollection<IEntityMapper> entityMappers = EntityMappersProvider.GetLocalEntityMappers();
foreach (IEntityMapper mapper in entityMappers) {
mapper.Map(modelBuilder);
}
}
Example of Entity relationships realization.
namespace Models.DataMapping.EntitiesMapping
{
public class CourseMapper : EntityMapper<Course>
{
protected override void MapEntity(EntityTypeBuilder<Course> entityTypeBuilder)
{
entityTypeBuilder.ToTable("Courses");
entityTypeBuilder.HasKey(a => a.Id);
//....
}
}
}
Also what I have noticed that if I remove abstract
keyword in EntityMapper
and add t.IsAbstract
in EntityMappersProvided
empty migration gets created..
For me it looks like you are trying to reinvent IEntityTypeConfiguration
which allows to move entity configuration code to separate classes :
public class BlogEntityTypeConfiguration : IEntityTypeConfiguration<Blog>
{
public void Configure(EntityTypeBuilder<Blog> builder)
{
builder
.Property(b => b.Url)
.IsRequired();
}
}
And provides convenience method to find all such configuration in assembly, which is available since EF Core 2.2:
modelBuilder.ApplyConfigurationsFromAssembly(typeof(BlogEntityTypeConfiguration).Assembly);
As for your error - it happens because your code tries to instantiate an open generic type (EntityMapper<TEntity>
) with Activator.CreateInstance
:
public class OpenGeneric<T>{}
Activator.CreateInstance(typeof(OpenGeneric<>)); // throws Cannot create an instance of Generic`1[T] because Type.ContainsGenericParameters is true.
So you need to filter it out, for example by adding && !t.IsAbstract && ! t.ContainsGenericParameters
in your Where
clause.