user868386
user868386

Reputation: 411

EntityFramework, Inherited Classes without Discriminator

Is it possible to use POCOs and EF to work with the following situation? I would like to have a Vehicles table, with some bool fields such as IsFast, IsVan, IsTruck, and then have a Vehicles class, with a FastVehicle, Van, Truck class that inherit from Vehicles class, without having their own tables, and without having a discriminator field?

Something like:

public class Vehicle
{
    public int Id {get; set;}
    public bool IsFast {get; set;}
    public bool IsTruck {get; set;}
    public bool IsVan {get; set;}
}

public class FastVehicle : Vehicle
{
    public FastVehicle(){
        IsFast = true;
    }
}

public class Van: Vehicle{
    public Van(){
        IsVan = true;
    }
}

public class Truck : Vehicle
{
    public Truck(){
        IsTruck = true;
    }
}

And then maybe in my DbContext have something like:

public virtual DbSet<Vehicle> Vehicles {get; set;}
public virtual DbSet<Van> Vans => (DbSet<Van>) Vehicles.Where(v => IsVan);
public virtual DbSet<Truck> Trucks => (DbSet<Truck>) Vehicles.Where(v => IsTruck);
public virtual DbSet<FastVehicle> FastVehicles => (DbSet<FastVehicle>) Vehicles.Where(v => IsFastVehicle);

Is something like this possible? Or is it a bad idea for some reason? How would I go about overriding OnModelCreating?

Upvotes: 3

Views: 12399

Answers (5)

Ogglas
Ogglas

Reputation: 70146

For EF Core 5.0 < you can use Table-per-type configuration.

https://learn.microsoft.com/en-us/ef/core/modeling/inheritance#table-per-type-configuration

Example:

internal class MyContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<RssBlog> RssBlogs { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>().ToTable("Blogs");
        modelBuilder.Entity<RssBlog>().ToTable("RssBlogs");
    }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
}

public class RssBlog : Blog
{
    public string RssUrl { get; set; }
}

EF will create the following database schema for the model above.

CREATE TABLE [Blogs] (
    [BlogId] int NOT NULL IDENTITY,
    [Url] nvarchar(max) NULL,
    CONSTRAINT [PK_Blogs] PRIMARY KEY ([BlogId])
);

CREATE TABLE [RssBlogs] (
    [BlogId] int NOT NULL,
    [RssUrl] nvarchar(max) NULL,
    CONSTRAINT [PK_RssBlogs] PRIMARY KEY ([BlogId]),
    CONSTRAINT [FK_RssBlogs_Blogs_BlogId] FOREIGN KEY ([BlogId]) REFERENCES [Blogs] ([BlogId]) ON DELETE NO ACTION
);

Warning

In many cases, TPT shows inferior performance when compared to TPH. See the performance docs for more information.

https://learn.microsoft.com/en-us/ef/core/performance/modeling-for-performance#inheritance-mapping

Upvotes: 1

user11898854
user11898854

Reputation: 1

Currently in new EF the design is very dodgy. They use Discriminator field. If you want the old school way of using separate tables that inherit from base table, then you are looking for Table per Type implementation.

Here is a tutorial about how to implement that: https://weblogs.thinktecture.com/pawel/2018/05/entity-framework-core-inheritance-table-per-type-tpt-is-not-supported-is-it-part-2-database-first.html

Upvotes: 0

pixparker
pixparker

Reputation: 3521

I found a solution to generate database without "Discriminator" column. This solution is good for situations that we want to preserve database schema and tables and also we want to add more fields in some entities. Imagine you want to have a database for business A and you want to use this dbcontext model for business B, then you can think a kind of inheritance in database and business level together.

OK

lets go to solution:

assume that we have

public class SampleDataContext : DbContext

{
   public DbSet<SampleUser> Users { get; set; }
}

[Table("Users")]
public class SampleUser
{
  public int Id { get; set; }
  public string Name { get; set; }
  public string Family { get; set; }
  public string Username { get; set; }
  public override string ToString() => $"{Username} : {Name} {Family}";
}

I want to create a new data context with extra fields on user entity. also new data context must be completely compatible with my current business logic layer.

I suggest to use interfaces as below:

public interface IDbContext
{
    int SaveChanges();
}


public interface IDbSet<TE, in TId>
{
    TE Add();
    IQueryable<TE> All();
    TE Get(TId id);
    void Delete(TId id);
}

and change SampleDataContext like this:

public class SampleDataContext : DbContext,IUserDbContext
{

    public DbSet<SampleUser> Users { get; set; }


    private IDbSet<SampleUser, int> _userSet;
    public IDbSet<SampleUser, int> UserSet
        {
            get { return _userSet ?? (_userSet = new UserInterface() { Db = this }); }
            set { _userSet = value; }
        }
    public class UserInterface : IDbSet<SampleUser, int>
        {
            public SampleDataContext Db { get; set; }

            public SampleUser Add() => Db.Users.Add(new SampleUser());

            public IQueryable<SampleUser> All() => Db.Users;

            public SampleUser Get(int id) => Db.Users.Find(id);

            public void Delete(int id) => Db.Users.Remove(Get(id));
        }
}

also add an interface for user business :

public interface IUserDbContext : IDbContext
{
    IDbSet<SampleUser, int> UserSet { get; set; }
}

then my logic business accept IUserDbContext instead of SampleDataContext.

public static List<SampleUser> ListUsers(IUserDbContext db)
{
    return db.UserSet.All().ToList();
}

by this approach I can have many databases that have free structures and implement IUserDbContext so my use business logic can work with any of these data contexts.

for example I want a new data context base that is compatible with current user business logic and also has extra fields on User entity. I do like below:

public class MyDataContext : DbContext, IUserDbContext
{
    public DbSet<MyUser> Users { get; set; }


    private IDbSet<SampleUser, int> _userSet;
    public IDbSet<SampleUser, int> UserSet
        {
            get { return _userSet ?? (_userSet = new UserInterface() { Db = this }); }
            set { _userSet = value; }
        }
    public class UserInterface : IDbSet<SampleUser, int>
        {
            public MyDataContext Db { get; set; }

            public SampleUser Add() => Db.Users.Add(new MyUser());

            public IQueryable<SampleUser> All() => Db.Users;

            public SampleUser Get(int id) => Db.Users.Find(id);

            public void Delete(int id) => Db.Users.Remove(Db.Users.Find(id)??new MyUser());
        }
}


[Table("Users")]
public class MyUser : SampleUser
{
    public string Color { get; set; }

    public override string ToString()
    {
        return base.ToString() + $"/ color : {Color}";
    }
}

I hope this was useful or give you any good idea.

Upvotes: 2

RobsionKarls
RobsionKarls

Reputation: 136

EF can map this inheritance automatically, its easy and good for performance, you dont need to worry about creating this bool properties.

An entire class hierarchy can be mapped to a single table. This table includes columns for all properties of all classes in the hierarchy. The concrete subclass represented by a particular row is identified by the value of a type discriminator column. You don’t have to do anything special in Code First to enable TPH. It's the default inheritance mapping strategy:

enter image description here

enter image description here

more info at http://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-1-table-per-hierarchy-tph

Upvotes: -1

Antinoid
Antinoid

Reputation: 23

If you dont have a discriminator or table for each class its not possible to distinguish your entities.

You can actually omit the bool properties if you don't need them in your domain model, because Entity Framework uses Table per Hierarchy as default to map inheritance. It will automatically create a discriminator column for you. If you add a truck object to your DbSet it will fill the discriminator colomn accordingly.

http://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-1-table-per-hierarchy-tph

Upvotes: 2

Related Questions