Reputation: 2550
I have a legacy DB I'd like to writing an application for monitoring some stuff in it.
The DB has a set of tables which is repeated for each 'project' in it.
For example if I have 2 projects, I would have project_1_table and a similar project_2_table.
Even though the 2 tables serve the same purpose for each project, there are some minor differences.
I'm looking for a way to create a model that will have a base class holding all the common elements of those tables, and then 2 inherited classes which would map the actual content.
To clarify: I have no control over the DB, and cannot change it. Looking for the best way to use EF in this situation.
How can this be done (either in the Model Editor or via CodeFirst)?
project_1_table project_2_table
recordID recordID
title title
project1field project2field
BaseEntity
ProjectType
RecordID
Title
(Mapped inherited Entities)
Project1Entity: BaseEntity Project2Entity: BaseEntity
ProjectType = 1 ProjectType = 2
RecordID RecordID
Title Title
Project1Field Project2Field
I'm new to EF, and it seems to elude me.
Is this possible? If so, how?
Upvotes: 0
Views: 2226
Reputation: 22595
If you model it like this:
public abstract class BaseEntity
{
[Key]
public RecordID { get; set; }
public string Title { get; set; }
}
public class Project1Entity: BaseEntity
{
public string Project1Field { get; set; }
}
public class Project2Entity: BaseEntity
{
public string Project2Field { get; set; }
}
Then entity framework will create a single table like this:
BaseEntity
RecordID
Title
Project1Field
Project2Field
Discriminator
The "Discriminator" column means that entity framework knows what type of object to instantiate when retrieving from the database - and should make your ProjectType
field redundant. (If you have a BaseEntity
then you can test for type in C# using if(entity is Project1Entity)
)
This strategy is known as Table Per Hierarchy inheritance .It is the default strategy for inheritance in EF and may suit your purposes better if your inheritance is not too complex.
If you don't like the single table and prefer the normalised form then you can change this by adding table attributes to the subclasses:
[Table("Project1")]
public class Project1Entity: BaseEntity
{
public string Project1Field { get; set; }
}
[Table("Project2")]
public class Project2Entity: BaseEntity
{
public string Project2Field { get; set; }
}
This will result in tables like this:
BaseEntity
RecordID
Title
Project1
Project1Field
Project2
Project2Field
So now it is normalised but requires an inner join everytime. This strategy is known as Table Per Type
The third option is table per concrete type. There is a full write up there if you really feel that suits you best and there are guidelines for choosing your strategy. Here are some quotes that I think are worth reading:
By default, choose TPH only for simple problems. For more complex cases (or when you’re overruled by a data modeler insisting on the importance of nullability constraints and normalization), you should consider the TPT strategy. But at that point, ask yourself whether it may not be better to remodel inheritance as delegation in the object model (delegation is a way of making composition as powerful for reuse as inheritance). Complex inheritance is often best avoided for all sorts of reasons unrelated to persistence or ORM
I recommend TPC (only) for the top level of your class hierarchy, where polymorphism isn’t usually required, and when modification of the base class in the future is unlikely
Upvotes: 1
Reputation: 5489
Something similar along the lines of your requirement is possible by using Table per Concrete Type inheritance model.
Entities
public abstract class BaseEntity {
public int RecordID { get; set; }
public abstract int ProjectType { get; }
public string Title { get; set; }
}
public class Project1Entity : BaseEntity {
public override int ProjectType {
get { return 1; }
}
public string Project1Field { get; set; }
}
public class Project2Entity : BaseEntity {
public override int ProjectType {
get { return 2; }
}
public string Project2Field { get; set; }
}
DBContext
public class DataContext : DbContext {
public DbSet<Project1Entity> Project1Entities { get; set; }
public DbSet<Project2Entity> Project2Entities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<BaseEntity>().HasKey(o => o.RecordID);
modelBuilder.Entity<Project1Entity>().Map(m => {
m.MapInheritedProperties();
m.ToTable("project_1_table");
});
modelBuilder.Entity<Project2Entity>().Map(m => {
m.MapInheritedProperties();
m.ToTable("project_2_table");
});
}
}
However there are several limitations
RecordID
for new records, so you have to provide value of the primary key yourselfRecordID
for items in project_1_table and project_2_table doesn't collide. If they do, you need to specify another primary key - e.g. add column ProjectType
to the database and use the composite primary key {RecordID, ProjectType}
Version with composite key
Entities
public abstract class BaseEntity {
public int RecordID { get; set; }
public int ProjectType { get; set; }
public string Title { get; set; }
}
public class Project1Entity : BaseEntity {
public string Project1Field { get; set; }
}
public class Project2Entity : BaseEntity {
public string Project2Field { get; set; }
}
DBContext
public class DataContext : DbContext {
...
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
...
modelBuilder.Entity<BaseEntity>().HasKey(o => new { o.RecordID, o.ProjectType });
...
}
}
Upvotes: 2