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.
Related
I would like to
Move my OnModelCreating to the base class and
Call modelBuilder.Entity<T> without explicitly specifying the T. I would just like to say, the 'current class' (meaning in this example UserType as it is UserType on which the index needs creating)
public class BaseLookupEntity : BaseEntity
{
[Key()]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public override int Id { get; set; }
//...
}
public class UserType: BaseLookupEntity
{
internal static void OnModelCreating(ModelBuilder modelBuilder)
{
// unique
modelBuilder.Entity<UserType>()
.HasIndex(c => c.Enum)
.IsUnique();
}
}
class someBaseClass
{
public void Foo<TInferrConcreteDerivedTypeCallingThisMethodHere>()
}
No, thats not possible, and if you understand how generics works you'd see why. Generic types are not resolved at runtime (excluding dynamic and reflection scenarios), all generic type parameters are resolved at compile time. Therefore, there is no way the compiler can know what the real type of TInferrConcreteDerivedTypeCallingThisMethodHere is from within SomeBaseClass without running the code.
Inheritance is a red herring here, the same issue arises with the classical example of statically unknown types:
void Foo<T>(T t) { ... }
object o = GetSomeRuntimeObjectIDontKnowTheTypeOf();
Foo(o); T is inferred
Can you guess what T is inferred to? You can narrow it down to two options: Foo<RuntimeTypeOfUnkownObject> or Foo<object>? If you have doubts, run it and figure it out.
Also, as far as type inference goes, a rule of the thumb, whenever you have a generic method with a signature similar to:
void Foo<T>(/*no arguments from which T can be inferred*/)
or even
T Foo<T>(/*no arguments from which T can be inferred*/)
T can not or will not be inferred.
You would want to make your base class generic as well in that case
public class BaseLookupEntity<T> : BaseEntity where T : BaseLookupEntity<T>
{
[Key()]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public override int Id { get; set; }
internal static void OnModelCreating(ModelBuilder modelBuilder)
{
// unique
modelBuilder.Entity<T>()
.HasIndex(c => c.Enum)
.IsUnique();
}
}
public class UserType: BaseLookupEntity<UserType>
{
}
Using generics, you can't.
Generics are evaluated at compile time, but as your base class will have multiple sub classes, the type will only be known at runtime.
There is an overload for ModelBuilder.Enity that accepts a System.Type object, which can be used at runtime, so you could use that in your base class.
Personally i would do this in your DbContext and list all the base classes individually e.g.
private void MapLookupEntitity<TLookup>(DbModelBuilder modelBuilder) where TLookup : BaseLookupEntity
{
// unique
modelBuilder.Entity<TLookup>()
.HasIndex(c => c.Enum)
.IsUnique();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
MapLookupEntitity<UserType>(modelBuilder);
MapLookupEntitity<AnotherType>(modelBuilder);
// map some more..
}
There is in-built support for decoupled (from DB context) configurations. Your configuration class must implement IEntityTypeConfiguration<T> interface.
Then you can easily apply configurations inside your DbContext's OnModelCreating method using ApplyConfiguration<T> method.
Your example, could look like:
public class CustomDbContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration<UserTypeConfiguration>(new UserTypeConfiguration());
}
}
public class UserTypeConfiguration : IEntityTypeConfiguration<UserType>
{
public void Configure(EntityTypeBuilder<UserType> builder)
{
builder
.HasIndex(c => c.Enum)
.IsUnique();
}
}
Note: I can't try your inheritance chain, but it should work out of the box, if you do:
This is not a how-to question, but rather a why&how it works. Here's the thing, when one has in his assembly two namespaces with two contexts implementing different parts of TPT tree but sharing the base tables, adding new entity fails when calling the SaveChanges() on the context with UpdateException: Error retrieving values from ObjectStateEntry. See inner exception for details. being thrown. The inner exception states that
(41,10) : error 3032: Problem in mapping fragments starting at lines
41, 47:EntityTypes app.a.ChildB,
app.a.ChildA are being mapped to the same
rows in table xBase. Mapping conditions can be used to
distinguish the rows that these types are mapped to.
The first question what these line numbers are, where are they refer to?
Now, given the code:
namespace app
{
public abstract class xBase
{
//...
}
}
namespace app.a
{
public class ChildA : xBase
{
//...
}
public class AContext : DbContext
{
public DbSet<xBase> Base { get; set; }
public DbSet<ChildA> Children { get; set; }
public AContext()
: base("name=AppAContext")
{
this.Configuration.LazyLoadingEnabled = false;
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("PlayGround");
modelBuilder.Configurations.Add(new xBaseConfiguration());
modelBuilder.Configurations.Add(new ChildAConfiguration());
}
public class xBaseConfiguration: EntityTypeConfiguration<xBase>
{
public xBaseConfiguration()
{
//...
this.Map(m => {
m.ToTable("xBase");
});
}
}
public class ChildAConfiguration : EntityTypeConfiguration<ChildA>
{
public ChildAConfiguration ()
{
//...
this.Map(m => {
m.ToTable("AChildren");
});
}
}
}
}
namespace app.b
{
public class ChildB : xBase
{
//...
}
public class BContext : DbContext
{
public DbSet<xBase> Base { get; set; }
public DbSet<ChildB> Children { get; set; }
public BContext()
: base("name=AppBContext")
{
this.Configuration.LazyLoadingEnabled = false;
}
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("PlayGround");
modelBuilder.Configurations.Add(new xBaseConfiguration());
modelBuilder.Configurations.Add(new ChildBConfiguration());
}
public class xBaseConfiguration: EntityTypeConfiguration<xBase>
{
public xBaseConfiguration()
{
//...
this.Map(m => {
m.ToTable("xBase");
});
}
}
public class ChildBConfiguration : EntityTypeConfiguration<ChildB>
{
public ChildBConfiguration()
{
//...
this.Map(m => {
m.ToTable("BChildren");
});
}
}
}
}
N.B.:Both contexts use different names but point to the very same db instance.
Then when one tries to add, f.e. a new ChildA the aforementioned exception is thrown, like in this example:
using (var db = new AContext())
{
var child = new AChild();
//...
db.Children.Add(child);
db.SaveChanges(); //<--At this point the exception is thrown
}
In case one excludes from the assembly either of the parts of the tree, the code that operates with the second part works as desired.
Now, it can be related to this behavior or not, but for some reason, as we can see in the exception's message, the framework thinks that both parts of the hierarchy reside in the same namespace, which is not the case, originally they were, so I started with separating them, but nothing has changed, then I separated the connection strings used by two contexts, but still got same behavior. It worth mentioning that the exception is being thrown even if only one of the contexts is instantiated and as soon as the first child is inserted and SaveChanges() is called. So how and why one context is aware of the existence of other entities in another namespace?
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...
}
}
I have the following method:
protected override void OnModelCreating(ModelBuilder builder) {
builder.Map<Country>();
}
And I created the following extension:
public static class CountryMapper {
public static void Map<T>(this ModelBuilder builder) where T : Country {
builder.Entity<T>().HasKey(x => x.Code);
}
}
This works but I would like to have a generic base class:
public class CountryMapper : EntityMapper<Country> {
// Here override the map extension ??
}
Basically I would like to apply Map as I am but assuring all Mappers are implemented the same way.
EntityMapper is a class made by you?
and Country can be modified?
I'd add an interface like IEntity that expose a GetKey method like
public interface IEntity {
object GetKey();
}
then in country (and every class you need to map), implement that interface, e.g your country could looks like
public class Country : IEntity{
public string Code { get; set; }
...
public object GetKey(){
return this.Code;
}
...
}
then your Map Extension could be generic and based on this interface, like
public static void Map<T>(this ModelBuilder builder) where T : IEntity {
builder.Entity<T>().HasKey(x => x.GetKey());
}
note that i wrote it without having a chance to test, but this should point you to the right direction, there is even a little chance this is already working :)
P.S.
if you don't like to have that GetKey method to be easily accessed by anyone (or seen when using visual studio) you can implement it as an explicit interface
should be
public class Country : IEntity{
public string Code { get; set; }
...
object IEntity.GetKey(){
return this.Code;
}
...
}
and then extension, semething like
public static void Map<T>(this ModelBuilder builder) where T : IEntity {
builder.Entity<T>().HasKey(x => ((IEntity)x).GetKey());
}
I have a base class for all entities:
public class BaseClass
{
public int SomeProperty {get; set;}
}
public class SomeEntity : BaseClass
{
...
}
I want to ignore this property in some cases. Could I do in the OnModelCreating method something like this:
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<int>()
.Where(p => p.Name == "SomeProperty")
.Ignore();
}
?
You could try:
modelBuilder.Entity<SomeEntity>().Ignore(p => p.SomeProperty);
It will cause SomeProperty not to be mapped to SomeEntity.
EDIT: If this property should never be mapped to database you can add NotMapped annotation in your BaseClass:
public class BaseClass
{
[NotMapped]
public int SomeProperty {get; set;}
}
This will be the same as ignoring this property in all extending classes.
Could you override it?
public class SomeEntity : BaseClass
{
[NotMapped]
public override int SomeProperty { get; set; }
...
}
A late entry here - but in case it's useful...
Having recently encountered similar requirements, I went with this:-
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Types<EntityBase>()
.Configure(config => config.Ignore(x => x.SomeBaseClassPropertyToIgnore));
}
}
This will apply the given configuration to all entity types that inherit from EntityBase. The same technique can be used to configure entity types based on an interface they implement (probably a better approach anyway).
Advantages are:-
No need to write and maintain the same config code for multiple concrete entities.
No need for [NotMapped] attribute, which is less flexible and adds potentially unwanted dependencies.
Note that the targeted types can be filtered further if necessary:-
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Types<EntityBase>().Where(t => t != typeof(SpecialExceptionEntity)).Configure(...);
}
Refs:-
https://msdn.microsoft.com/en-us/library/dn235653(v=vs.113).aspx
https://msdn.microsoft.com/en-us/library/dn323206(v=vs.113).aspx