How to force EF Code First to ignore inheritance? - c#

I've got a base class MyBase which is a part of my data model. I also have classes MyChild1, MyChild2 derived from it in related assemblies.
I want the children to be stored in database and loaded just like MyBase. Also I don't want my entity configuration to know anything about children classes.
Is there any way to force EF to ignore that inheritance and user only base class?
Thanks in advance

Here is your case:
public abstract class BaseModel
{
public int Id { get; set; }
public string Name { get; set; }
}
public partial class Child1 : BaseModel
{
public string Name1 { get; set; }
}
public partial class Child2 : BaseModel
{
public string Name2 { get; set; }
}
I guess your data should be similar like this (I am not sure your detail requirements, here is just an example):
public partial class Example<T> where T: BaseModel
{
public int Id { get; set; }
public T Data { get; set; } // so here could be any Child of BaseModel
}
public partial class Example: Example<BaseModel>
{
}
Use ModelBuilder.Ignore<>() Method to let EFCore ignore your children and base.
User PropertyBuilder.HasConversion Method to convert your data to/from database.
Here is sample code:
public class MyDbContext : DbContext
{
public virtual DbSet<Example> Examples { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
//Let EFCore ignore all models that you don't want it to be a table
builder.Ignore<BaseModel>();
builder.Ignore<Child1>();
builder.Ignore<Child2>();
builder.Entity<Example>(entity =>
{
entity.Property(p => p.Data).HasConversion(
x => JsonConvert.SerializeObject(x) //convert TO a json string
, x => JsonConvert.DeserializeObject<BaseModel>(x)//convert FROM a json string
);
});
}
}

Related

Code first Sharing Primary Key between tables

I am using Entity Framework code first to try and create a database whereby I have several tables that share unique primary keys.
I have the following structure to my domain classes, and would hope that FirstClass, SecondClass, ThirdClass would all be created as tables that link to a BaseTable where the shared keys are stored. I have included the SharedClass class in this example since I also have this strutcure in my project.
[Table("BaseTable")]
public abstract class BaseClass {
public int Id { get; set; }
}
public abstract class SharedClass : BaseClass {
public string SharePropertyOne { get; set; }
public string SharePropertyOne { get; set; }
}
[Table("FirstClass")]
public abstract class FirstClass : SharedClass {
public string SharePropertyOne { get; set; }
public string SharePropertyOne { get; set; }
}
[Table("SecondClass")]
public abstract class SecondClass : SharedClass {
public string SharePropertyOne { get; set; }
public string SharePropertyOne { get; set; }
}
[Table("ThirdClass")]
public abstract class ThirdClass : SharedClass {
public string SharePropertyOne { get; set; }
public string SharePropertyOne { get; set; }
}
What is currently happening is that FirstClass, SecondClass and ThirdClass are all being created but with their own primary keys and no BaseTable is being created.
I am not sure if I am missing something after looking through several questions on here that are asking something similar.
The EF inheritance is not activated by default and even by applying Table data annotation on a base class.
It can be activated by either adding a DbSet to the derived DbContext:
public DbSet<BaseClass> BaseClass { get; set; }
which also allows you to use polymorphic queries, or with fluent configuration - the bare minimum needed is:
modelBuilder.Entity<BaseClass>();

Does EF Core support an abstract base entity which is generic?

I remember there were problems with generic entities in previous EF. How about EF Core? I can't find docs related to this matter.
For example:
public abstract class Parent<TEntity> {
public int EntityId { get; set; }
public TEntity Entity { get; set; }
}
public class Child : Parent<Foo> {
}
public class OtherChild : Parent<Bar> {
}
// config for child entities includes this:
config.HasKey(c => c.EntityId);
Though this throws stating that child entities do not define a primary key, when they clearly do!
I can fix this by making Parent non-generic.
Are there official docs for this? Am I doing something wrong, or is this the expected behavior?
I can use this model in ef-core 1.1.0:
public abstract class Parent<TEntity>
{
public int EntityId { get; set; }
public TEntity Entity { get; set; }
}
public class Child : Parent<Foo>
{
}
public class OtherChild : Parent<Bar>
{
}
public class Foo
{
public int Id { get; set; }
}
public class Bar
{
public int Id { get; set; }
}
With this mapping in the context:
protected override void OnModelCreating(ModelBuilder mb)
{
mb.Entity<Child>().HasKey(a => a.EntityId);
mb.Entity<Child>().HasOne(c => c.Entity).WithMany().HasForeignKey("ParentId");
mb.Entity<OtherChild>().HasKey(a => a.EntityId);
mb.Entity<OtherChild>().HasOne(c => c.Entity).WithMany().HasForeignKey("ParentId");
}
Which leads to this fantastic model:

TPT inheritance with Abstract base doesn't reference children in related table

I'm struggling with TPT inheritance in MVC - EF.
I have an AAnimal abstract class and two classes that inherit it, Zebra and Lion. There is a Cage class which holds an AAnimal.
My problem is that because AAnimal is abstract, the EF cannot create an instance of it when I load all Cages. So what I want is a way to override this behavior and make it understand whether it needs to load a Zebra or a Lion.
Zebra and Lion have a Primary key which is also foreign key to Animal table. This is done by the EF (TPT inheritance model).
public abstract class AAnimal
{
[Key]
public int AnimalId { set; get; }
public string Name { set; get; }
}
public class Lion : AAnimal {}
public class Zebra : AAnimal {}
public class Εmployee
{
[Key]
public int EmployeeId { set; get; }
public string Name { set; get; }
}
public class Cage
{
[Key]
public int CageId { set; get; }
[ForeignKey("CagedAnimal")]
public int CagedAnimalId { set; get; }
public AAnimal CagedAnimal { set; get; }
[ForeignKey("CageEmployee")]
public int CageEmployeeId { set; get; }
public Employee CageEmployee { set; get; }
}
// Model mapping
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<AAnimal>().ToTable("Animal");
modelBuilder.Entity<Lion>().ToTable("Lion");
modelBuilder.Entity<Zebra>().ToTable("Zebra");
modelBuilder.Entity<Εmployee>().ToTable("Εmployee");
}
// Load all cages
public ActionResult Index()
{
var allCages = db.Cages.ToList();
}
At this point all cages are loaded, all fields have values except the CagedAnimal which is null. Even the CagedAnimalId has value.
How can I tell the EF to follow the same procedure while saving data, in order to load entities?
Note that this is just an example. Also, TPT inheritance model has been selected over other inheritance models.

Entity Framework Many to many through containing object

I was curious if it is possible to map an intermediate table through a containing object.
public class Subscriber : IEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
private ChannelList _subscribedList { get; set; }
public int NumSubscribedChannels { get { return _subscribedList.Count(); } }
}
public class HelpChannel : IEntity
{
[Key]
public int Id { get; set; }
public string name { get; set; }
public string category { get; set; }
public int group { get; set; }
}
I need to have a subscriber table, channel table and an intermediate table to link a subscriber to his/her channels.
Is it possible to map the list that is within the ChannelList object to the Subscriber Model?
I figured that's probably not possible and that I would need to just have a private List for EF to map. But I wasn't sure if EF will do that for private variables. Will it?
I'm hoping that is does because if it has to be public to maintain the encapsulation.
You can map private properties in EF code-first. Here is a nice description how to do it. In your case it is about the mapping of Subscriber._subscribedList. What you can't do is this (in the context's override of OnModelCreating):
modelBuilder.Entity<Subscriber>().HasMany(x => x._subscribedList);
It won't compile, because _subscribedList is private.
What you can do is create a nested mapping class in Subscriber:
public class Subscriber : IEntity
{
...
private ICollection<HelpChannel> _subscribedList { get; set; } // ICollection!
public class SubscriberMapper : EntityTypeConfiguration<Subscriber>
{
public SubscriberMapper()
{
HasMany(s => s._subscribedList);
}
}
}
and in OnModelCreating:
modelBuilder.Configurations.Add(new Subscriber.SubscriberMapping());
You may want to make _subscribedList protected virtual, to allow lazy loading. But it is even possible to do eager loading with Include:
context.Subscribers.Include("_subscribedList");

EF4 (CPT5) ForeignKeyAttribute in base class - Assert Failure entityType != null

Getting an error when trying to set a ForeignKeyAttribute in a base class
class User { }
abstract class FruitBase
{
[ForeignKey("CreateById")]
public User CreateBy{ get; set; }
public int CreateById{ get; set; }
}
class Banana : FruitBase { }
class DataContext : DbContext
{
public DbSet<Banana> Bananas { get; set; }
}
If I move the FruitBase code into the banana, all is well, but I don't want to, as there will be many many fruit and I want to remain relatively DRY if I can
Is this a know issue that will be fixed by March?
Does anyone know a work around?
The problem caused by the fact that in your DbContext you put DbSet<Banana> instead of DbSet<FruitBase>. The following object model works as expected:
public class User
{
public int UserId { get; set; }
}
public abstract class FruitBase
{
public int Id { get; set; }
public int CreateById { get; set; }
[ForeignKey("CreateById")]
public User CreateBy { get; set; }
}
public class Banana : FruitBase { }
public class DataContext : DbContext
{
public DbSet<FruitBase> Fruits { get; set; }
}
You have to be aware that by doing this, you are essentially creating a Polymorphic Association and as of CTP5, not all of the inheritance mapping strategies allow polymorphic association. Here it works fine because you've used Table per Hierarchy (TPH).
Update: Use Table per Type (TPT) Strategy:
Polymorphic Associations work with TPT as well:
public class StackoverflowTestContext : DbContext
{
public DbSet<FruitBase> Fruits { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Banana>().ToTable("Bananas");
}
}
as discussed above, this is TPC (Table per Concrete Type), the answer/work around here is to take the ForeignKeyAttribute out of the base
class User{}
abstract class AuditObjectBase{ // was FruitBase
// [ForeignKey("CreateById")]
public abstract User CreateBy{ get; set; } // made abstract
public int CreateById{ get; set; } // both get and set required public
}
class ItemOne : AuditObjectBase{ // Was Banana
// added
[ForeignKey("CreateById")]
public override User CreateBy{ get; set; }
}
class ItemTwo : AuditObjectBase{ // Added
[ForeignKey("CreateById")]
public override User CreateBy{ get; set; }
}
class DataContext : DbContext{
DbSet<ItemOne> ItemOnes{ get; set; }
DbSet<ItemTwo> ItemTwos{ get; set; }
}
Not completely DRY but at least when you create an object which uses the AuditObjectBase it will force you to implement the Foreign Key property, some nice comments to remind you of the attribute and away you go
Basically it seams, attributes should be added to the class declared in the DbContext as an Entity, in this case has a DbSet property.

Categories