Fluent API EF inheritance and mapping - c#

I've a system with several self referencing entities with one-to-many relationship (parent-child). I'd like to use the common base class for all of those entities:
public class SelfReferencing
{
public SelfReferencing Parent {get; set;}
public ICollection<SelfReferencing> Children {get; set;}
}
and inherit the particular entity from SelfReferencing.
Fluent API mapping requires the reference Properties to be of the defining type, when trying to do following:
modelBuilder.Entity<ConcreteSelfReferencing>()
.HasMany(e => e.Children)
.WithOptional(e => e.Parent);
So, can you help me to find a possibility to make use of inheritance and get the entities mapped?
THX

Note: The example below is known as Table-Per-Hierarchy (TPH) - i.e. all contained in one table. Click on this link for Table-Per-Type (TPT), which has different tables for each type.
When using base types and inherited types, you have to tell EF how to determine the association for a specific inherited type.
Taking your code:
public abstract class SelfReferencing
{
public SelfReferencing Parent { get; set; }
public ICollection<SelfReferencing> Children { get; set; }
}
public class ConcreteSelfReferencing : SelfReferencing
{
}
EF now has to work out whether the sub-class is a ConcreteSelfReferencing or any other type of sub-class. This is determined by a discriminator on the table itself, to which the column is not part of your mapping.
To take another example, similar to I've used in the past:
public abstract class Policy
{
public int Id { get; set; }
public string PolicyNumber { get; set; }
}
public class InsurancePolicy : Policy
{
}
public class CarPolicy : Policy
{
}
The table was structured like this:
| Id | PolicyNumber | Type | ..... |
1 CAR0001 C
2 ISN0001 I
To get EF to result them correctly, you would have:
public class MyContext : DbContext
{
public MyContext() : base()
{
}
public DbSet<Policy> Policies { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
var policyMap = modelBuilder.Entity<Policy>();
// Set up discriminators
policyMap.Map<InsurancePolicy>(p => o.Requires("Type").HasValue("I"))
.Map<CarPolicy>(p => o.Requires("Type").HasValue("C"));
// Notice that `Type` is only used for the discriminators, not an actual
// mapped property
policyMap.HasKey(x=>x.Id);
policyMap.Property(x=>x.PolicyNumber);
}
}
And from your code, you can either do the filtering yourself, or put the filtering in the DbContext. Here is an example from a separate class.
public class PolicyRepository
{
private MyContext context = new MyContext();
public PolicyRepository()
{
}
public IQueryable<InsurancePolicy> GetInsurancePolicies()
{
return this.context.Policies.OfType<InsurancePolicy>();
}
public IQueryable<CarPolicy> GetCarPolicies()
{
return this.context.Policies.OfType<CarPolicy>();
}
}

Related

EF Core - Reuse the same join table entity for several relationships

I want to reuse the same many-to-many relationship table (FileInEntity) for several other objects (Course, Lecture, Game), since they all can have files. Since we have to manually create the many-to-many relationships by creating a join entity, I want to reuse the join entity for the objects (Course, Lecture, Game).
If we look at the table structure, I would like to have the following:
Course: Id, ...
Lecture: Id, ...
Game: Id, ...
FileInEntity: EntityId (this can be either Course.Id, Lecture.Id or Game.Id), FileId
File: Id, ...
(File is base class type with two derived types: Image and Audio)
When I try this approach in .NET Core, I receive the following error message:
Entity type 'FileInEntities' is in shadow-state. A valid model requires
all entity types to have corresponding CLR type.
Is this even possible?
This is my setup:
ModelBase.cs
public class ModelBase
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
}
Course.cs
[Table("Courses")]
public class Course : ModelBase
{
private ICollection<FileInEntity> IconsInCourse { get; set; } = new List<FileInEntity>();
[NotMapped]
public File Image => IconsInCourse.Select(e => e.File).FirstOrDefault();
}
Lecture.cs
// Same as Course.cs
Game.cs
// Same as Course.cs
FileInEntity.cs
[Table("FilesInEntities")]
public class FileInEntity
{
public Guid FileId { get; set; }
public Guid EntityId { get; set; }
public virtual ModelBase Entity { get; set; }
public virtual File File { get; set; }
}
File.cs
[Table("Files")]
public class File : ModelBase
{
// This is the property for which the error occured
private ICollection<FileInEntity> FileInEntities { get; set; } = new List<FileInEntity>();
public IEnumerable<ModelBase> Entities => FileInEntities.Select(e => e.Entities);
}
FilesInEntitiesMap.cs (Relationship Configuration)
builder.HasOne(p => p.Entity)
.WithMany()
.HasForeignKey(k => k.EntityId);
builder.HasOne(p => p.File)
.WithMany()
.HasForeignKey(k => k.FileId);
FileMap.cs
// This is the key to which the error references to
builder.HasMany("FileInEntities")
.WithOne("Entity")
.HasForeignKey("EntityId");
You won't be able to use the base class ModelBase as the object in the mapping class because c# wont know the actual type coming back from the db. You can look at table per hierarchy inheritance, but I'm still not sure you would be able to use that in a mapping table either. Here is a good article
If your Course.cs, Lecture.cs, and Game.cs are the same and the only difference is type, you could combine them into one class and add an enum property to set the type.
public enum EntityType{
Game = 1,
Lecture = 2,
Course = 3
}
public class MyEntity : ModelBase{
private ICollection<FileInEntity> Icons { get; set; } = new List<FileInEntity>();
[NotMapped]
public File Image => Icons.Select(e => e.File).FirstOrDefault();
public EntityType EntityType {get;set;} //course, lecture, or game
}
When you care about the type just use a where clause.
Be sure to use Fluent Api in DbContext's OnModelCreating to determine One to One relationship for this tables. (and be sure again correct reference properties are selected)
Missing parts of your codes.
public class ModelBase
{
[Key]// add for primary key
//set none always for primary keys (because guid has no auto increment)
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public Guid Id { get; set; }
}
[Table("Files")]
public class File : ModelBase
{
//make it public
public ICollection<FileInEntity> FileInEntities { get; set; } = new List<FileInEntity>();
[NotMapped] //set not mapped
public IEnumerable<ModelBase> Entities => FileInEntities.Select(e => e.Entities);
//do it same changes for `Lacture.cs`, `Game.cs` and `Course.cs`...
}

Using a single Entity Framework Core DbContext to manage multiple database schemas with homonymous tables

In a .NET Core 2.1 library I need to access to a MySQL database organized in multiple schemas with tables that can have the same name across those schemas. I can't make any changes to the DB since it comes from another company.
For most of the tables I need a read-only access and I'd like to use a single EF Core DbContext.
Actually I get this error message during initialization:
InvalidOperationException: Cannot use table 'tbl_panel' for
entity type 'Db2Panels' since it is being used for entity
type 'Db1Panels' and there is no relationship between their
primary keys.
I think that the crux of the matter mainly resides in the configuration methods, which should be called not just once but N times, one for each instance of the entity with different schema (db_machine_1.tbl_panel, db_machine_2.tbl_panel, etc.).
How can I reach my goal?
This is my actual implementation.
Database schemas
// db_machine_1 schema
db_machine_1.tbl_panel
db_machine_1.tbl_basket
db_machine_1.tbl_unit
// db_machine_2 schema
db_machine_2.tbl_panel
db_machine_2.tbl_basket
db_machine_2.tbl_discard
// Other db_machine_X schemas with similar structure...
DbContext configuration
public class MyDbContext : DbContext
{
// Schema: db_machine_1
public DbSet<Panel> Db1Panels { get; set; }
public DbSet<Basket> Db1Baskets { get; set; }
public DbSet<Unit> Db1Units { get; set; }
// Schema: db_machine_2
public DbSet<Panel> Db2Panels { get; set; }
public DbSet<Basket> Db2Baskets { get; set; }
public DbSet<Discard> Db2Discards { get; set; }
// Other schemas DbSet<X> objects...
// Arrays to access the specific DbSet by using the schema number:
// Panels[1] -> Db1Panels, Panels[2] -> Db2Panels, ...
public DbSet<Panel>[] Panels { get; }
public DbSet<Basket>[] Baskets { get; }
// Other arrays for other DbSet<X> objects...
public MyDbContext(DbContextOptions<MyDbContext> options)
: base(options)
{
// Arrays initialization
List<DbSet<Panel>> dbPanelList = new List<DbSet<Panel>>();
dbPanelList.Add(Db1Panels);
dbPanelList.Add(Db2Panels);
Panels = dbPanelList.ToArray();
List<DbSet<Basket>> dbBasketList = new List<DbSet<Basket>>();
dbBasketList.Add(Db1Baskets);
dbBasketList.Add(Db2Baskets);
Baskets = dbBasketList.ToArray();
// Initialization for other DbSet<X> objects...
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyAllConfigurations<MyDbContext>();
modelBuilder.ApplyAllConversions();
}
}
Objects
public class Panel
{
public long Id { get; set; }
public string SN { get; set; }
// Other properties...
}
public class Basket
{
public long Id { get; set; }
public string Description { get; set; }
// Other properties...
}
Configurations
public class PanelConfiguration : IEntityTypeConfiguration<Panel>
{
public void Configure(EntityTypeBuilder<Panel> builder)
{
builder.ToTable("tbl_panel");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.HasColumnName("ID_Record");
builder.Property(e => e.SN)
.HasColumnName("Serial")
.HasMaxLength(20);
// Other properties configuration...
}
}
public class BasketConfiguration : IEntityTypeConfiguration<Basket>
{
public void Configure(EntityTypeBuilder<Basket> builder)
{
builder.ToTable("tbl_basket");
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.HasColumnName("ID_Record");
builder.Property(e => e.Description)
.HasColumnName("Desc")
.HasMaxLength(100);
// Other properties configuration...
}
}
// Other IEntityTypeConfiguration implementations for other tables...
// This extension method is used to automatically load all Configurations
// of the various entities
public static class ModelBuilderExtensions
{
public static void ApplyAllConfigurations(this ModelBuilder modelBuilder)
{
var applyConfigurationMethodInfo = modelBuilder
.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public)
.First(m => m.Name.Equals("ApplyConfiguration", StringComparison.OrdinalIgnoreCase));
var ret = typeof(T).Assembly
.GetTypes()
.Select(t => (t, i: t.GetInterfaces().FirstOrDefault(i => i.Name.Equals(typeof(IEntityTypeConfiguration<>).Name, StringComparison.Ordinal))))
.Where(it => it.i != null)
.Select(it => (et: it.i.GetGenericArguments()[0], cfgObj: Activator.CreateInstance(it.t)))
.Select(it => applyConfigurationMethodInfo.MakeGenericMethod(it.et).Invoke(modelBuilder, new[] { it.cfgObj }))
.ToList();
}
}
UPDATE about base class arrays
After creating base abstract classes and derived ones, I'd like to merge all the derived class objects into a single array to be able to access the specific DbSet by using the schema number. See also above code of DbContext constructor.
I'm having problems with casting...
List<DbSet<Panel>> dbPanelList = new List<DbSet<Panel>>();
dbPanelList.Add((DbSet<Panel>)Db1Panels.Select(g => g as Panel)); // NOT WORKING! Cast Exception
dbPanelList.Add((DbSet<Panel>)Db2Panels.Cast<DbSet<Panel>>()); // NOT WORKING! Cast Exception
Panels = dbPanelList.ToArray();
Is this possible somehow?
I think you can't get away from having two different EF objects for the different tables, and you probably shouldn't as they may diverge at some point in the future.
At a minimum you need two classes Db1Panel and Db2Panel . I assume that actually the "Db" prefix is meant to meant a different schema, not actually a different database.
However that shouldn't be a big problem as there are other ways within C# of making them behave in similar fashions. Two options that spring to mind are having them inherit from the same base class, or have them implement an interface:
public abstract class PanelBase
{
public long Id { get; set; }
// other properties
}
[Table("tbl_panel", Schema = "Db1")]
public class Db1Panel : PanelBase{}
[Table("tbl_panel", Schema = "Db2")]
public class Db2Panel : PanelBase{}
If you chose to implement the interface you would need to repeat the properties in each class, but refactoring tools make this quite easy.
public interface IPanel
{
public long Id { get; set; }
}
[Table("tbl_panel", Schema = "Db1")]
public class Db1Panel : IPanel
{
public long Id { get; set; }
}
[Table("tbl_panel", Schema = "Db2")]
public class Db2Panel : IPanel
{
public long Id { get; set; }
}
Or depending on the size of your application you could consider having another namespace of domain objects and just map the database objects into it:
You should be able to use the Table attribute. There's a parameter Schema that allows you to set the schema name. See here for documentation. In your case you'd get something like
[Table("Table1", Schema="Schema1")]
public class Entity1Schema1
{
public string Property1 {get;set;}
}
[Table("Table1", Schema="Schema2")]
public class Entity1Schema2
{
public string Property1 {get;set;}
}
And then of course you can use interfaces or base classes to refactor your code as #ste-fu already mentioned.

Fluent Nhibernate sublass mapping HasMany with where clause with column from Parent Class

I have three entity classes: Entity, Special and Regular with hierarchy as below
public class Entity
{
public Guid Id { get; set; }
public bool IsDeleted { get; set; }
}
public class Special : Entity
{
public IEnumerable<Regular> Regulars { get; set; }
}
public class Regular : Entity
{
}
To map this entities I use ClassMap and SubclassMap
In my subclass I try to map collection Regulars from entity Special with Where clause as following:
public class SpecialMap : SubclassMap<Special>
{
public SpecialMap()
{
HasMany(x => x.Regulars).Where("IsDeleted = 0");
}
}
but it not works because when I try to use this collection the error Invalid column name 'IsDeleted' is show. It seems that nhibernate tries to find IsDeleted column in table Regular, but there is no columns with such, in table Entity only. What can I do with that?
PS. Probably I've explained not correct, it's my English level:)
here is an identical to my issue NHibernate explicit fluent column mapping but without useful answers
public class RegularMap : SubclassMap<Regular>
{
public RegularMap()
{
Table("Regular")
References(x => x.Special,"SpecialID");
}
}
public class SpecialMap : SubclassMap<Special>
{
public SpecialMap()
{
HasMany(x => x.Regulars).KeyColumn("SpecialID").Where("IsDeleted = 0");
}
}

EF 4.3.1 Include inherited navigation properties in LinqToEntities query

I'm trying to setup a simple inheritance scenario with EF 4.3.1 using code first approch and fluent configuration.
I've created an abstract base type 'A' with a one-to-one navigation property and an inherited class 'AA' also with a one-to-one navigation property has following :
public abstract class A
{
public Guid ID { get; set; }
public B ChildB { get; set; }
}
public class AA : A
{
public C ChildC { get; set; }
}
public class B
{
public Guid ID { get; set; }
public A Parent { get; set; }
}
public class C
{
public Guid ID { get; set; }
public AA Parent { get; set; }
}
public class AConfiguration : EntityTypeConfiguration<A>
{
public AConfiguration()
{
this.HasRequired(o => o.ChildB)
.WithRequiredPrincipal(o => o.Parent);
this.Map(o =>
{
o.ToTable("A");
});
}
}
public class AAConfiguration : EntityTypeConfiguration<AA>
{
public AAConfiguration()
{
this.HasRequired(o => o.ChildC)
.WithRequiredPrincipal(o => o.Parent);
this.Map(o =>
{
o.ToTable("AA");
});
}
}
public class BConfiguration : EntityTypeConfiguration<B>
{
public BConfiguration()
{
this.HasRequired(o => o.Parent)
.WithRequiredDependent(o => o.ChildB);
this.Map(o =>
{
o.ToTable("B");
});
}
}
public class CConfiguration : EntityTypeConfiguration<C>
{
public CConfiguration()
{
this.HasRequired(o => o.Parent)
.WithRequiredDependent(o => o.ChildC);
this.Map(o =>
{
o.ToTable("C");
});
}
}
public class DataContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add<A>(new AConfiguration());
modelBuilder.Configurations.Add<AA>(new AAConfiguration());
modelBuilder.Configurations.Add<B>(new BConfiguration());
modelBuilder.Configurations.Add<C>(new CConfiguration());
}
public DbSet<AA> AASet { get; set; }
public DbSet<B> BSet { get; set; }
public DbSet<C> CSet { get; set; }
}
When I try to get my data back with the first level of navigation property, it works as expected :
... dataContext.AASet.Include("ChildB") ...
But when I try to include the navigation property of the inherited type like following :
... dataContext.AASet.Include("ChildC") ...
I get an EntityCommandCompilationException at runtime with the following inner exception message :
The ResultType of the specified expression is not compatible with the
required type. The expression ResultType is
'Transient.reference[...A]' but the required type is
'Transient.reference[...AA]'. Parameter name: arguments[0]
Has anybody encountered a similar issue ?
I am probably missing something but I can't see what's wrong with this sample.
What can I do to get my model works as expected ?
No, you don't miss anything. Actually you ran into an old Entity Framework bug. Your second query can be written like this:
var result = dataContext.ASet.OfType<AA>().Include("ChildC").ToList();
(when you replace your DbSet AASet by ASet).
For this type of eager loading of one-to-one mapped children on inherited types this article applies: http://weblogs.asp.net/johnkatsiotis/archive/2010/04/28/huge-ef4-inheritance-bug.aspx
The bug has been reported long time ago here: https://connect.microsoft.com/VisualStudio/feedback/details/544639/ef4-inheritance-defined-using-queryview-doesnt-work-properly-with-association
The bug still exists in EF 4.3.1. But Microsoft has announced in this thread that the bug is fixed in .NET 4.5 ( = EF 5.0).
The code would work if the relationship is one-to-many instead of one-to-one. Lazy or explicit loading would work as well (also with one-to-one relationship), I believe:
var result = dataContext.ASet.OfType<AA>().ToList();
foreach (var item in result)
dataContext.Entry(item).Reference(a => a.ChildC).Load();
But this will generate multiple queries. If you don't have performance problems with multiple queries I would prefer the last workaround - until you can migrate to EF 5.0.

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?
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.
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
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
Notice that the result contains both MotorCycle type and Car type.
Check this link to decide what Inheritance Strategy to use.
Have you tried giving Vehicle its own context with just a DBSet? I think that might do it for you.
Using a DataAnnotation.Schema.Table("TableName") entry, on the inherited class, triggers the creation of a new table for the inherited type (table-per-type) and eliminates the Discriminator field in the parent type table, without the need for the Fluent API code.

Categories