Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Querying by base type in EF4 Code-First

I have the following types:

public abstract class Vehicle {
    public int Id { get; set; }
    public double TopSpeed { get; set; }
}

public class Car : Vehicle {
    public int Doors { get; set; }
}

public class Motorcycle : Vehicle { 
    public string Color { get; set; }
}

And I have a code-first DBContext:

public MyDbContext: DbContext { 
    public DbSet<Car> Cars { get; set; }
    public DbSet<Motorcycle> Motorcycles { get; set; }
}

This works great if I query for a car or moto directly...

var dbContext = new MyDbContext();
var cars = dbContext.Set<Car>().Where(x=> x.TopSpeed>10); // <-- THIS WORKS

But if I want a list of all vehicles, whether car or moto, I would like to do this:

var dbContext = new MyDbContext();
var vehicles = dbContext.Set<Vehicle>().Where(x=> x.TopSpeed>10); // <-- THIS DOES NOT WORK

When I try the above code, I get an exception:

System.InvalidOperationException : The entity type Vehicle is not part of the model for the current context.

That makes perfect sense... I didn't add Vehicle to the context. I added car and moto. At this point, I'm not sure what to do. I tried adding Vehicle to my context, but that combined the tables for car and moto into one Vehicle table. I definitely want a separate table for cars and motos (and possibly a table for the vehicle base properties as well). What's the best way to do this?

like image 234
Byron Sommardahl Avatar asked May 02 '12 19:05

Byron Sommardahl


1 Answers

Have a Vehicles Properties of type DBSet of Vehicle in your MyDbContext class.

public MyDbContext: DbContext { 
    public DbSet<Car> Cars { get; set; }
    public DbSet<Motorcycle> Motorcycles { get; set; }
    public DbSet<Vehicle> Vehicles { set; get; }
}

Now you can access all vehicles with the criteria like this

var vehicles = dbContext.Set<Vehicle>().Where(x=> x.TopSpeed>10);

Keep in mind that you should have a Key Property in your entity classes ( ID or {ClassName}ID). Otherwise it is going to give you a run time error! (Yes the code will compile.)

public abstract class Vehicle
{
    public int ID { set; get; }
    public double TopSpeed { get; set; }
}

EDIT : As per the comment

By Default Entity Framework will do a Table Per Hierarchy.All Data in the hierarchy is saved in a single table and it use the Discriminator column to identify which record belongs to which subtype. So As the result you will have one Vehicle table with columns same as properties of All your classes in the hierarchy with an extra column called "Discriminator". For a Record for Car, It will have the "Car" value inside the Discriminator column.

enter image description here

If you want to create Single table per each type, We will go for Table Per Type. Entity Framework will create a Table for the base class and seperate table for all child classes.

To make this happen, You can use Fluent API to override the configuration.

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Car>().ToTable("Cars");
        modelBuilder.Entity<Motorcycle>().ToTable("Motorcycles");

        base.OnModelCreating(modelBuilder);
    }

And the output is

enter image description here

Now You should be able to query Your Vehicle Entries like this

 var vehicles = dbContext.Set<Vehicle>().Where(x => x.TopSpeed > 150).ToList();

And the result is here

enter image description here

Notice that the result contains both MotorCycle type and Car type.

Check this link to decide what Inheritance Strategy to use.

like image 115
Shyju Avatar answered Oct 29 '22 19:10

Shyju