EF Core Fluent API Configurations with Inheritance - c#

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...
}
}

Related

How to pass DbSet as generic parameter?

I have a custom validator for FluentValidation to validate whether Guid exist in the Database.
public static class TestCustomValidator
{
public static IRuleBuilderOptions<T, Guid> MustExistInDatabase<T>(this IRuleBuilder<T, Guid> ruleBuilder, ApplicationContext context)
return ruleBuilder.Must(id => context.Product.Find(id) != null).WithMessage("'{PropertyName}' {PropertyValue} not found.");
}
Then I will call it like this
RuleFor(r => r.ProductId).NotEmpty().MustExistInDatabase(context);
However, this part context.Product.Find(id) != null is currently only for Product and I have a lot of tables so I was hoping I could do something like
RuleFor(r => r.ProductId).NotEmpty().MustExistInDatabase<Product>();
RuleFor(r => r.CustomerId).NotEmpty().MustExistInDatabase<Customer>();
And it would automatically look for the Product or Customer table. Any idea how to do this?
You can define a base class as follows and inherit entities from it
public abstract class BaseEntity
{
public Guid Id { get; set; }
}
public class Product : BaseEntity
{
//other properties
}
public class Customer : BaseEntity
{
//other properties
}
Then change your extension method as below
public static IRuleBuilderOptions<T, Guid> MustExistInDatabase<T>(this IRuleBuilder<T, Guid> ruleBuilder, ApplicationContext context) where T : BaseEntity
{
return ruleBuilder.Must(id => context.Set<T>().Find(id) != null).WithMessage("'{PropertyName}' {PropertyValue} not found.");
}
and use as follows :
RuleFor(r => r.ProductId).NotEmpty().MustExistInDatabase<Product>(dbContext);
RuleFor(r => r.CustomerId).NotEmpty().MustExistInDatabase<Customer>(dbContext);

C# EF 6 - implementing a generic DBContext constructor

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;
}
}

Generic Entity mapping in EF

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.

Generic entity configuration class in EF Core 2

I'm trying to create a generic configuration class for my entities but i'm stuck.
I have an abstract class called EntityBase:
public abstract class EntityBase
{
public int Id { get; set; }
public int TenantId { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime UpdatedOn { get; set; }
}
And many other classes that inherit from EntityBase, in which i have to configure the DateTime properties in each one with the same code. This way:
void EntityTypeConfiguration<MyEntity>.Configure(EntityTypeBuilder<MyEntity> builder)
{
builder.HasIndex(e => e.TenantId);
builder.Property(e => e.CreatedOn)
.ValueGeneratedOnAdd()
.HasDefaultValueSql("GETDATE()");
// Other specific configurations here
}
I would like to be able to call somthing like: builder.ConfigureBase() and avoid the code duplication. Any ideas?
There are several way you can accomplish the goal. For instance, since you seem to be using IEntityTypeConfiguration<TEntity> classes, you could create a base generic configuration class with virtual void Configure method and let your concrete configuration classes inherit from it, override the Configure method and call base.Configure before doing their specific adjustments.
But let say you want to be able to exactly call builder.ConfigureBase(). To allow that syntax, you can simply move the common code to a custom generic extension method like this:
public static class EntityBaseConfiguration
{
public static void ConfigureBase<TEntity>(this EntityTypeBuilder<TEntity> builder)
where TEntity : EntityBase
{
builder.HasIndex(e => e.TenantId);
builder.Property(e => e.CreatedOn)
.ValueGeneratedOnAdd()
.HasDefaultValueSql("GETDATE()");
}
}
with sample usage:
void IEntityTypeConfiguration<MyEntity>.Configure(EntityTypeBuilder<MyEntity> builder)
{
builder.ConfigureBase();
// Other specific configurations here
}

EF 6.1 Fluent API: Ignore property of the base class

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

Categories

Resources