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
}
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:
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 got this problem when I try to create the database with EntityFramework Core:
The property 'Rating.RatingScores' could not be mapped, because it is of type 'List' which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Here is the class:
public class Rating
{
public int Id { get; set; }
public List<decimal> RatingScores { get; set; }
public decimal Score
{
set => Score = value;
get => Math.Round(RatingScores.Sum() / RatingScores.Count, 1);
}
}
If the Rating class has multiple RatingScores you have a one-to-many relationship and the RatingScores property needs its own table, you therefore need to create a new class.
Class RatingScore
{
public int Id { get; set; }
public decimal RtSc { get; set; }
}
Then the Rating property will look like this:
public List<RatingScore> MyRatingScores { get; set; }
However if each Rating has one RatingScore, your property should not be a collection.
public RatingScore MyRatingScore { get; Set; }
When you really need to put multiple values in single column can use below way
Let's say you want to create only one table for below class
public class SomeClass
{
public Guid ID { get; set; }
public IEnumerable<int> Values { get; set; }
}
First create a converter, which will control .net values to db values and vice versa
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
public class IntListToStringValueConverter : ValueConverter<IEnumerable<int>, string>
{
public IntListToStringValueConverter() : base(le => ListToString(le), (s => StringToList(s)))
{
}
public static string ListToString(IEnumerable<int> value)
{
if (value==null || value.Count()==0)
{
return null;
}
return value.Join(',');
}
public static IEnumerable<int> StringToList(string value)
{
if (value==null || value==string.Empty)
{
return null;
}
return value.Split(',').Select(i => Convert.ToInt32(i)); ;
}
}
And DbContext should have below method
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
.....
var IntValueConverter = new IntListToStringValueConverter();
modelBuilder
.Entity<SomeClass>()
.Property(e => e.Values)//Property
.HasConversion(IntValueConverter);
}
Done!! IT should work
Ok. Here is my error and I just found what the problem is.
The property 'LogEntry.Timestamp' could not be mapped because it is of type 'Instant', which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Actually its fairly clear what it is trying to say.
I have overridden OnModelCreating but I missed a trivial thing.
Here is my dbcontext class.
public class ExtendedElsaMigrationsDbContext : SqlServerContext
{
public ExtendedElsaMigrationsDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// I should not comment out this base method call.
// base.OnModelCreating(modelBuilder);
// modelBuilder.Entity<User>();
modelBuilder.ConfigureExtendedElsaDbContext();
}
}
Note that I had commented out the base method call. And that is what is causing the problem.
The overridden method should be as follows.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// //modelBuilder.Entity<User>();
modelBuilder.ConfigureExtendedElsaDbContext();
}
Summary: Don't forget to call the base method when you are overriding a method. Many times base method don't do much, so we ignore them. But some times they do. So always call the base methods.
For EntityFramework Core. This solution will help you to save proper data in DB as well as, whenever the result comes back from DB, it will convert to List.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<WebhookSubscription>()
.Property(p => p.Webhooks)
.HasConversion(v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject<List<string>>(v));
}
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