Entity Framework Code First - Using interfaces on domain models - c#

Let's say you have a Car Park, each car park hold x amount of Cars and each car can have different properties. Like this:
public class CarPark
{
public int Id { get; set; }
public ICollection<ICar> Cars { get; set; }
}
public interface ICar
{
string Model { get; set; }
}
public class Volvo : ICar
{
public string Model { get; set; }
public string Color { get; set; }
}
public class Bmw : ICar
{
public string Model { get; set; }
public int Wheels { get; set; }
}
How do you deal with that kind of scenario when the models are different?

Assuming you mean how you have to deal with EF messing up the mapping of your models due to the Interface: you can manually configure the translation of your models in the OnModelCreation method of your DbContext.
Look here for more information: https://learn.microsoft.com/en-us/ef/core/modeling/

I found a solution, instead of using a interface you create an abstract class, i adjusted the code above to show how it's done:
public class CarPark
{
public int Id { get; set; }
public virtual ICollection<Car> Cars { get; set; }
}
public abstract class Car
{
public int Id { get; set; }
public string Model { get; set; }
}
[Table("Volvo")]
public class Volvo : Car
{
public string Color { get; set; }
}
[Table("Bmw")]
public class Bmw : Car
{
public int Wheels { get; set; }
}
Because of the table annotation each car will end up in a separate table, this strategy is called TPT - Table Per Type, if the attribute is removed all cars will end up in the same table, that strategy is called TPH - Table Per Hierarchy.
The example is pretty bad but imagine each car is a payment provider with unique settings or something similar.
More info:
https://www.entityframeworktutorial.net/code-first/inheritance-strategy-in-code-first.aspx

Related

EF Core Table-Per-Hierarchy for class with generic

In EF Core 3.1.15 I manage a model with a generic. I would like to store the entities in the same table basis Table-Per-Hierarchy approach (TPH pattern). Below is the model abstracted. The resulting database creates 1 table for Part and descendants with a discriminator (as expected), but instead of 1 table for BaseComputer and descendants it creates a separate table for Computers and a separate table for Laptops (not expected).
namespace EFGetStarted
{
public class BloggingContext : DbContext
{
public DbSet<Computer> Computers { get; set; }
public DbSet<Laptop> Laptops { get; set; }
public DbSet<Part> Parts { get; set; }
}
public abstract class BaseComputer<T> where T : Part
{
[Key]
public int Id { get; set; }
public List<T> Parts { get; set; }
}
public class Computer : BaseComputer<Part>
{
public string ComputerSpecificProperty { get; set; }
}
public class Laptop : BaseComputer<LaptopPart>
{
public string LaptopSpecificProperty { get; set; }
}
public class Part
{
[Key]
public int Id { get; set; }
public string PartName { get; set; }
}
public class LaptopPart : Part
{
public string LaptopSpecificPartProperty { get; set; }
}
}
I tried explicitly specifying the entity as TPH:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseComputer<Part>>()
.HasDiscriminator()
.HasValue<Computer>("Computer")
.HasValue<Laptop>("Laptop");
}
But this fails with the following message:
The entity type 'Laptop' cannot inherit from 'BaseComputer' because 'Laptop' is not a descendant of 'BaseComputer'.
Questions: Is it possible for me to design this model in a TPH pattern? If not, is it because "Laptop is not a descendant of BaseComputer<Part>"? And if that's the case, why is not a considered a descendant and what should I change in the class to make it a descendant?

how to make a business layer working with different Models?

I am designing the architecture of a .Net project in C#.
I will use EF.
I want to set a MVC pattern architecture.
Although I will start with a first Model implementation, I might have to use other Models that will be QUITE similar to the original one.
I obviously want to implement a single Business layer that would ideally work with both Model implementations.
So I suppose I will have to intensively rely on interfaces at some point.
USUALLY, I build my entities and context this way:
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
... other properties
}
public class Bar
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Foo Fooo { get; set; }
... other properties
}
...
public class MyContext : DbContext
{
...
}
Now, in my case, I will have let's say 2 different models.
On top of the precedently written Foo and Bar classes, I will have:
public class Foo2
{
public int Id { get; set; }
public string Name { get; set; }
... other properties
}
public class Bar2
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Foo Fooo { get; set; }
... other properties
}
...
public class MyContext2 : DbContext
{
...
}
Now how to set a single business layer that can deal either with MyContext or MyContext2 ?
I suppose the the fist step is to create interfaces for For Foo / Foo2 and Bar / Bar2:
public interface IFoo
{
int Id { get; set; }
string Name { get; set; }
}
public interface IBar
{
int Id { get; set; }
string Name { get; set; }
IFoo Fooo { get; set; }
}
Then what????

Creating different tables using using same base abstract class

I have 2 models which have exactly same fields, but I chose to make different models for them because I needed two different tables, one for each.
Earlier everything was working fine when I had two different tables for each model, but then I started using abstract base class because the code inside both the models were same.
Now I have a single table comprised of all the data that I save.
How can I create different tables for those two models.
public abstract class baseGrammar
{
[Key]
public int Id { get; set; }
[Required]
public string question { get; set; }
[Required]
public string ans { get; set; }
public string ruleId { get; set; }
public string ruleApplicable { get; set; }
[ForeignKey("ruleId")]
public virtual ruleTable RuleTable { get; set; }
}
The one shown above is my abstract base class.
public class article : baseGrammar
{
}
public class adjective : baseGrammar
{
}
Just if someone intrested in ruleTable model.
public class ruleTable
{
[Key]
public string ruleId { get; set; }
public string topic { get; set; }
public string rule { get; set; }
public string example { get; set; }
public virtual ICollection<baseGrammar> BaseGrammar { get; set; }
}
Am also adding context class so as to provide better description
public class english : DbContext
{
public english() : base("name=localServerEng")
{
Database.SetInitializer<DbContext>(null);
Database.SetInitializer<english>(new UniDBInitializer<english>());
}
public virtual DbSet<adjective> adjectiveDb { get; set; }
public virtual DbSet<adverb> adverbDb { get; set; }
public virtual DbSet<alternativeVerb> alternativeVerbDb { get; set; }
public virtual DbSet<antonyms> antonymsDb { get; set; }
public virtual DbSet<article> articleDb { get; set; }
private class UniDBInitializer<T> : DropCreateDatabaseIfModelChanges<english>
{
}
public System.Data.Entity.DbSet<StructureSSC.Areas.AreaEnglish.Models.baseGrammar> baseGrammars { get; set; }
}
Screenshot of SQL Server showing 1 table comprising of all columns instead of different tables
This set up will give you 2 tables: (1) adjectives (2) articles
The context should be like this:
public class SomeContext : DbContext
{
public SomeContext()
: base("name=SomeContext")
{
}
public virtual DbSet<article> Articles { get; set; }
public virtual DbSet<adjective> Adjectives { get; set; }
}
public abstract class baseGrammar
{
//... common properties/columns
}
public class article : baseGrammar
{
}
public class adjective : baseGrammar
{
}
Please note the naming convention. In .NET class names and property names should follow Pascal Notation. Therefore, they should be:
BaseGrammar
Article
Adjective
RuleApplicable // other properties should follow same convention

Entity Framework Include Record with certain value in a Navigation Property

I am using Entity Framework 6 Code First and I'm configuring the mapping of my domain model with Fluent API. I don't see how to create a navigation properties for a Table which is a little tricky.
I have several objects which can make noise, I would like to record that noise in a NoiseRecord Table.
I need some kind of conditional mapping, something like that :
modelBuilder.Entity<NoiseRecord>().HasRequired(n=>n.Origine.OrigineType()=="Car").WithMany(c=>c.NoiseRecords);
That would be the mapping of the Car Navigation Property to avoid that, for example, it includes record related to Planes.
Here is my code
public interface INoisy
{
int ID {get; set;}
string OriginType()
...
//And other useful things not related to persistence
}
public class Car : INoisy
{
...
ICollection<NoiseRecord> NoiseRecords { get; set; }
string OrigineType()
{
return "Car";
}
}
public class Plane : INoisy
{
...
ICollection<NoiseRecord> NoiseRecords {get; set;}
string OrigineType()
{
return "Plane";
}
}
And a couple of other classes implement INoisy also.
Below is the NoiseRecord Table.
public class NoiseRecord
{
public int RecordID {get; set;}
public INoisy NoiseOrigine {get; set;}
public double NoiseMagnitude {get; set;}
}
I'm looking for a way to achieve that with Fluent API.
Thank you !
First of all, it is not possible to use interfaces as navigation properties. But you could use an abstract base class for your noise origins
public abstract class NoiseOrigin
{
public NoiseOrigin()
{
this.NoiseRecords = new Collection<NoiseRecord>();
}
public int Id { get; set; }
public ICollection<NoiseRecord> NoiseRecords { get; set; }
}
public class Car : NoiseOrigin {}
public class Plane : NoiseOrigin { }
public class NoiseRecord
{
public int Id { get; set; }
public int OriginId { get; set; }
public NoiseOrigin Origin { get; set; }
public double NoiseMagnitude { get; set; }
}
Your fluent API mapping whould look like this
public class NoiseModelContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Car>().Map(p => p.Requires("Type").HasValue("Car"));
modelBuilder.Entity<Plane>().Map(p => p.Requires("Type").HasValue("Plane"));
}
public DbSet<NoiseOrigin> NoiseOrigins { get; set; }
public DbSet<NoiseRecord> NoiseRecords { get; set; }
}
To get all car noise records your query will look like
using (var db = new NoiseModelContext()) {
var records = db.NoiseRecords.Where(p => p.Origin is Car);
// or like this - the result is the same.
var records2 = db.NoiseOrigins.OfType<Car>().SelectMany(p => p.NoiseRecords);
}

Entity Framework one-to-many with table-per-hierarchy creates one foreign key column per subclass

I have a Garage which contains Cars and Motorcycles. Cars and motorcycles are Vehicles. Here they are:
public class Garage
{
public int Id { get; set; }
public virtual List<Car> Cars { get; set; }
public virtual List<Motorcycle> Motorcycles { get; set; }
public Garage()
{
Cars = new List<Car>();
Motorcycles = new List<Motorcycle>();
}
}
public abstract class Vehicle
{
public int Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
public class Car : Vehicle
{
public int GarageId { get; set; }
public virtual Garage Garage { get; set; }
// some more properties here...
}
public class Motorcycle : Vehicle
{
public int GarageId { get; set; }
public virtual Garage Garage { get; set; }
// some more properties here...
}
Why do Car and Motorcycle each have a GarageId and Garage property? If I push those properties up to the Vehicle superclass, EF complains and tells me navigation properties must reside in concrete classes.
Moving on, here's my DbContext:
public class DataContext : DbContext
{
public DbSet<Garage> Garages { get; set; }
public DbSet<Vehicle> Vehicles { get; set; }
public DbSet<Car> Cars { get; set; }
public DbSet<Motorcycle> Motorcycles { get; set; }
public DataContext()
: base("GarageExample")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
}
And here's a short program to play with my toys:
class Program
{
static void Main(string[] args)
{
Database.SetInitializer<DataContext>(new DropCreateDatabaseAlways<DataContext>());
using (var db = new DataContext())
{
var car1 = new Car { Make = "Subaru", Model = "Legacy" };
var car2 = new Car { Make = "Porche", Model = "911" };
var bike1 = new Motorcycle { Make = "Suzuki", Model = "GS500" };
var bike2 = new Motorcycle { Make = "Kawasaki", Model = "Ninja" };
var garage = new Garage();
garage.Cars.Add(car1);
garage.Cars.Add(car2);
garage.Motorcycles.Add(bike1);
garage.Motorcycles.Add(bike2);
db.Garages.Add(garage);
db.SaveChanges();
}
}
}
The program runs, and produces the following Vehicles table:
Id Make Model GarageId GarageId1 Discriminator
1 Subaru Legacy 1 null Car
2 Porche 911 1 null Car
3 Suzuki GS500 null 1 Motorcycle
4 Kawasaki Ninja null 1 Motorcycle
With both Car and Motorcycle having their own GarageId and Garage properties, it seems that each subclass is creating its own foreign key to garage. How do I tell EF (via the fluent api, if possible) that Car.Garage and the Motorcycle.Garage are the same thing, and should use the same column?
This is the Vehicles table I want, of course:
Id Make Model GarageId Discriminator
1 Subaru Legacy 1 Car
2 Porche 911 1 Car
3 Suzuki GS500 1 Motorcycle
4 Kawasaki Ninja 1 Motorcycle
Use attribute [Column("GarageId")] on GarageId property on both car and motorcycle class.
The only way I know to get a single foreign key column and the database schema you want is giving up the navigation collections per derived type in Garage and use a single collection for the base type instead:
public class Garage
{
public int Id { get; set; }
public virtual List<Vehicle> Vehicles { get; set; }
public Garage()
{
Vehicles = new List<Vehicle>();
}
}
public abstract class Vehicle
{
public int Id { get; set; }
public string Make { get; set; }
public string Model { get; set; }
public int GarageId { get; set; }
public virtual Garage Garage { get; set; }
}
public class Car : Vehicle
{
// some more properties here...
}
public class Motorcycle : Vehicle
{
// some more properties here...
}
Of course you are losing the comfortable type filter with lazy or eager loading when you only want to load Cars or Motorcycles of a Garage and you have to either load all Vehicles of a Garage or use projections or explicit loading to load derived types.
In my opinion it's perfectly valid what you are trying to do, but somehow it is not supported with Entity Framework, or mapping to FK columns hasn't been implemented in a way that this scenario can be supported.
public class Garage
{
public int Id { get; set; }
public virtual List<Car> Cars { get; set; }
public virtual List<Motorcycle> Motorcycles { get; set; }
public Garage()
{
Cars = new List<Car>();
Motorcycles = new List<Motorcycle>();
}
}
public abstract class Vehicle
{
public int Id { get; set; }
public int GarageId { get; set; }
public string Make { get; set; }
public string Model { get; set; }
}
public class Car : Vehicle
{
[ForeignKey("GarageId")]
public virtual Garage Garage { get; set; }
// some more properties here...
}
public class Motorcycle : Vehicle
{
[ForeignKey("GarageId")]
public virtual Garage Garage { get; set; }
// some more properties here...
}
Have you looked at this yet?
Mapping the Table-Per-Hierarchy (TPH) Inheritance
In the TPH mapping scenario, all types in an inheritance hierarchy are
mapped to a single table. A discriminator column is used to identify
the type of each row. When creating your model with Code First, TPH is
the default strategy for the types that participate in the inheritance
hierarchy. By default, the discriminator column is added to the table
with the name “Discriminator” and the CLR type name of each type in
the hierarchy is used for the discriminator values. You can modify the
default behavior by using the fluent API.
modelBuilder.Entity<Course>()
.Map<Course>(m => m.Requires("Type").HasValue("Course"))
.Map<OnsiteCourse>(m => m.Requires("Type").HasValue("OnsiteCourse"));
Straight from here.

Categories

Resources