EF 6.1 Fluent API: Ignore property of the base class - c#

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

Related

C# generics: How to pass current class's type as a para

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:

The property 'PropertyName' could not be mapped, because it is of type 'List<decimal>'

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

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
}

Create a Base class for T generic

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

Ignoring a property in all inherited objects in EF code first

I have the following abstract base class for all my entities in my EF code first model:
public abstract class BaseEntity
{
public Id {get; set;}
public bool IsDeleted {get; set;}
...
}
I wrote following code in my DbContext's OnModelCreating() method to Ignore the IsDeleted
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Types().Configure(c => c.Ignore("IsDeleted"));
...
}
but when it cause following error:
You cannot use Ignore method on the property 'IsDeleted' on type 'MyEntity' because this type inherits from the type 'BaseEntity' where this property is mapped. To exclude this property from your model, use NotMappedAttribute or Ignore method on the base type.
I'm using .NET 4 so I couldn't use [NotMappedAttribute] to ignore IsDeleted, so I used following code:
public partial class BaseEntity_Mapping : EntityTypeConfiguration<BaseEntity>
{
public BaseEntity_Mapping()
{
this.HasKey(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Ignore(t => t.IsDeleted);
}
}
and updated the OnCreatingModel() method to:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Configurations.Add(new BaseEntity_Mapping());
modelBuilder.Types().Configure(c => c.Ignore("IsDeleted"));
...
}
but the problem exists yet.
Is there any Idea?
This is a confirmed bug in the Entity Framework 5. See this link.
It should have been fixed in EF6.
As a workaround: make IsDeleted readonly and provide a SetIsDeleted(bool value) function.

Categories

Resources