EF Core TPH Discriminator ignores base entities - entity-framework-core

I have two DbContext. A BaseDbContext and one that inherits from the BaseDbContextcalled FemaleDbContext.
public class BaseDbContext : DbContext
{
public BaseDbContext(DbContextOptions options) : base(options) { }
public virtual DbSet<Person> Person { get; set; }
public virtual DbSet<House> House { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>().ToTable("Person", "dbo");
modelBuilder.Entity<House>().ToTable("House", "dbo");
modelBuilder.Entity<Person>().HasOne(e => e.House).WithMany(e => e.Persons);
modelBuilder.Entity<House>().HasMany(e => e.Persons).WithOne(e => e.House);
}
}
The goal is to extend the Person entity with another property. I do not want to use shadow properties because its too dynamic. So I am trying to make it work using TPH. Here is my other context:
public class FemaleDbContext : BaseDbContext
{
public DbSet<Female> Female { get; set; }
public FemaleDbContext(DbContextOptions<FemaleDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Female>().HasBaseType<Person>();
base.OnModelCreating(modelBuilder);
}
}
As you can see, my sub-context should use the Female entity instead of the Person. The problem is that when I run this.Context.Female.ToList() on my SubDbContext, only entities with the value Female inside the Discriminator field inside my database are returned. Entities with the value Person in that table are returned. But I want to get every entity.
Also, here are my entities:
public class Person
{
public int Id { get; set; }
public string Firstname { get; set; }
public string Middlename { get; set; }
public string Lastname { get; set; }
}
public class Female : Person
{
public bool? IsPregnant { get; set; }
}
How can I configure my DbContext that this.Context.Female.ToList() returns both Females and Persons. Note that this.Context.Person.ToList() already returns everything, not only Persons

Related

How to configure a one-to-one relationship with identity class and two db context

Let me try to explaine. I have 2 classes, one class called ApplicationUser that inherit from IdentityUser to insert some properties and one class called Company. I would like to create a one-to-one relationship between both to allow me to access the user and navigate to his companies. I am using 2 different dbContext, one for the identity and one for the rest of entities so i don't know how and which dbContext should i configure it. Could you please assist me?
THAT IS MY APPLICATIONUSER CLASS:
`
public class ApplicationUser : IdentityUser
{
public string Fullname { get; set; }
public override string Email { get => base.Email; set => base.Email = value; }
public string UplandUsername { get; set; }
public DateTime Nascimento { get; set; }
public decimal Saldo { get; set; }velEnum UplandLevel { get; set; }
public string FotoPerfil { get; set; }
public Company Company { get; set; } // RELATION HERE
}
`
THAT IS MY COMPANY CLASS:
`
public class Company : BaseEntity
{
public string Name { get; set; }
public string Country { get; set; }
public string Department { get; set; }
public string Usuario_Id { get; set; } // REFERENTIAL USER ID
public ApplicationUser User { get; set; } // RELATION HERE
}
`
THAT IS MY DBCONTEXT OF IDENTITY:
`
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
{
}
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
}
`
THAT IS MY DBCONTEXT OF THE REST OF ENTITIES, INCLUDING COMPANY:
`
public class MagnatumDbContext : DbContext
{
public MagnatumDbContext(DbContextOptions<MagnatumDbContext> options) : base(options) { }
public DbSet<Company> Companies { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var property in modelBuilder.Model.GetEntityTypes()
.SelectMany(e => e.GetProperties()
.Where(p => p.ClrType == typeof(string))))
property.SetColumnType("varchar(100)");
modelBuilder.ApplyConfigurationsFromAssembly(typeof(MagnatumDbContext).Assembly);
base.OnModelCreating(modelBuilder);
}
}
`
I would like to know how to configure this one to one relationship and where to configure it (applicationDBContext or MagnatumDbContext).

Invalid column name when using Entity Framework Core Table Per Hierarchy Inheritance

I am new to EF Core and am trying to use TPH Inheritance with Entity Framework Core
I have the following classes defined
public class WorkItem {
public Guid Id { get; set; }
public string WorkItemType { get; set; }
public string Description { get; set; }
}
public class Job : WorkItem {
public string BillingNotes { get; set; }
}
In my context, I have
public class JobContextNew : DbContext {
public virtual DbSet<WorkItem> WorkItem { get; set; }
public virtual DbSet<Job> Job { get; set; }
public JobContextNew(DbContextOptions<JobContextNew> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<WorkItem>(entity => entity.Property(e => e.Id).ValueGeneratedNever());
modelBuilder.Entity<WorkItem>()
.HasDiscriminator(workitem => workitem.WorkItemType)
.HasValue<Job>(nameof(Job));
}
}
If I omit the field in Job, it will pull the data just fine but when I add the BillngNotes back in I get the following error: Invalid column name 'BillingNotes
Can anyone tell me what I might be doing wrong?

Mapping POCO class which has a (one-to-one) reference to another POCO class with AutoMapper EF Core

My apologies for (perhaps) not using the right terms in the title and this post.
The problem is as follows:
I have a POCO class which has a reference to another table (which is read only). This table has a one-to-one relationship with the other table.
I have set this upo as follow:
public class Commodity
{
public Commodity()
{
}
public long CommodityID { get; set; }
public long CommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
public OmsCommodityMaterial OmsCommodityMaterial { get; set; }
}
The OmsCommodityMaterial property is the referenced table. This referenced table is also a POCO class which has some other fields, and a porperty back to my own (Commodity) table so I can make a one-to-one relationship with Fluent:
public class OmsCommodityMaterial : OmsBaseClass
{
public OmsCommodityMaterial()
{
}
public long? CommodityMaterialID { get; set; }
public long? CommodityID { get; set; }
public string Name { get; set; }
public long? SortOrder { get; set; }
public Commodity Commodity { get; set; }
}
Fluent (for the one-to-one relation) is set up as follows:
public class MyContext : IdentityDbContext<ApplicationUser>
{
public virtual DbSet<Commodity> Commodity { get; set; }
// Oms classes:
public virtual DbSet<OmsCommodityMaterial> OmsCommodityMaterial { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Commodity>(entity =>
{
entity.Property(e => e.CommodityID)
.HasColumnName("CommodityID")
.ValueGeneratedOnAdd();
entity.Property(e => e.CommodityMaterialID)
.HasColumnName("CommodityMaterialID");
entity.Property(e => e.SpecficWeight)
.HasColumnName("SpecficWeight")
.HasColumnType("decimal(18, 2)");
entity.HasOne(a => a.OmsCommodityMaterial)
.WithOne(b => b.Commodity)
.HasForeignKey<Commodity>(b => b.CommodityMaterialID);
});
}
}
In my endpoint I want to do a GET of all values which return the specific fields of my own table (Commodity) and all the fields of the referenced table (OmsCommodityMaterial).
For this purpose I created a ViewModel (also because else I get a circular reference as I found out in this post: ERR_CONNECTION_RESET returning Async including object child collections) which looks as follow:
public class CommodityViewModel
{
public long CommodityID { get; set; }
public long CommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
public OmsCommodityMaterial OmsCommodityMaterial { get; set; }
}
For the ViewModels I am using AutoMapper, but I actually have no clue how I can map / return the list of the above ViewModel.
UPDATE
I ended up eliminating the Circular reference error by adding the [JsonIgnore] attribute to the public virtual Commodity Commodity { get; set; } property in the OmsCommodityMaterial POCO class. Now I can get all the needed column values:
return await this.Context.Commodity
.Include(i => i.OmsCommodityMaterial)
.ToListAsync();
Though, I suppose this is not the way to go. There should be a better solution for this by creating a ViewModel that retrieves the Commodity columns and (some) of the referenced OmsCommodityMaterial columns without falling in the Circular Reference error, but how (using AutoMapper)?

How do I force generated many-to-many Relation tables to the correct schema?

I have tables parkpay.User and parkpay.Role. EF Code First automatically generates a third table linking the two for a many-to-many relationship, but it generates dbo.UserRole. How do I get it to make that table `parkpay.UserRole'?
Use EntityTypeConfiguration<> config many-to-many mappings.
public class User
{
public long Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public class Role
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class UserMapping : EntityTypeConfiguration<User>
{
public UserMapping()
{
ToTable("User", "parkpay");
HasKey(e => e.Id).Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
HasMany(e => e.Roles).WithMany(e => e.Users).Map(m => m.ToTable("UserRole", "parkpay").MapLeftKey("RoleId").MapRightKey("UserId"));
}
}
public class RoleMapping : EntityTypeConfiguration<Role>
{
public RoleMapping()
{
ToTable("Role", "parkpay");
HasKey(e => e.Id).Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
public class DatabaseContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UserMapping());
modelBuilder.Configurations.Add(new RoleMapping());
base.OnModelCreating(modelBuilder);
}
}

literal or constant as part of composite key in EF code first

I am relatively new to the Code First approach to Entity Framework. I have used the Database First approach for a while now, but the Code First seems to be a better fit for the application I am currently developing. I am working with an existing MS SQL database, and I am not allowed to make any changes whatsoever to the database. The reason why I am using Code First is because the Fluent API allows me to dynamically assign a table name to a class.
That said, I have a predicament where I need to assign a relationship between 2 tables. One table, ArCodes, has a composite key made up of the CodeType and the Code (both are strings). The CodeType column determins the type of code and the Code column is the identifier unique to the code type.
public class ArCode {
[Column("cod_typ", Order = 0), Key]
public string CodeType { get; set; }
[Column("ar_cod", Order = 1), Key]
public string Code { get; set; }
[Column("desc")]
public string Description { get; set; }
}
The other table, Invoices, needs to have a relationship to the ArCodes table for both a "ship via" code and a "terms" code.
public class Invoice {
[Column("pi_hist_hdr_invc_no"), Key]
public int InvoiceNumber { get; set; }
[Column("shp_via_cod")]
public string ShipViaCode { get; set; }
public ArCode ShipVia { get; set; }
[Column("terms_cod")]
public string TermsCode { get; set; }
public ArCode Terms { get; set; }
}
I would like to setup the relationship for both the "ShipVia" property and the "Terms" property. However, I am not sure how to do so in regards to the CodeType portion of the composite key. For "ship via" codes the Code Type should be "S", and code "terms" codes, the code type should be "T".
I have tried the following in by DB Context, but it did not work:
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
// setup the table names
modelBuilder.Entity<ArCode>().ToTable("ARCODS" + CompanyCode);
modelBuilder.Entity<Invoice>().ToTable("IHSHDR" + CompanyCode);
//
// setup the relationships
//
// 1 Invoice <--> 0-1 Ship Via AR Codes
modelBuilder.Entity<Invoice>()
.HasOptional(invoice => invoice.ShipVia)
.WithMany()
.HasForeignKey(invoice => new { TheType = "S", invoice.ShipViaCode })
;
base.OnModelCreating(modelBuilder);
}
Any help would be appreciated.
Update #1
Ok, I reduced my code to its simplest form, and I followed the solution as provided by #GertArnold.
public abstract class ArCode {
[Column("cod_typ")]
public string CodeType { get; set; }
[Column("ar_cod")]
public string Code { get; set; }
[Column("terms_desc")]
public string TermsDescription { get; set; }
[Column("terms_typ")]
public string TermsType { get; set; }
[Column("shp_via_desc")]
public string ShipViaDescription { get; set; }
[Column("tax_desc")]
public string TaxDescription { get; set; }
}
public class TermsCode : ArCode { }
public class ShipViaCode : ArCode { }
public class Invoice {
[Column("pi_hist_hdr_invc_no"), Key]
public int InvoiceNumber { get; set; }
[Column("hdr_invc_dat")]
public DateTime InvoiceDate { get; set; }
[Column("shp_via_cod")]
public string ShipViaCode { get; set; }
public ShipViaCode ShipVia { get; set; }
[Column("terms_cod")]
public string TermsCode { get; set; }
public TermsCode Terms { get; set; }
public Invoice() {
}
}
public class PbsContext : DbContext {
public DbSet<Invoice> Invoices { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Invoice>().ToTable("IHSHDR");
modelBuilder.Entity<ArCode>().HasKey(r => r.Code).ToTable("ARCODS");
modelBuilder.Entity<TermsCode>().Map(m => m.Requires("CodeType")
.HasValue("T").HasColumnType("varchar").HasMaxLength(1).IsRequired())
.ToTable("ARCODS");
modelBuilder.Entity<ShipViaCode>().Map(m => m.Requires("CodeType")
.HasValue("S").HasColumnType("varchar").HasMaxLength(1).IsRequired())
.ToTable("ARCODS");
base.OnModelCreating(modelBuilder);
}
public PbsContext()
: base("name=PbsDatabase") {
}
}
However, the following code returns an error:
PbsContext context = new PbsContext();
var invoice = context.Invoices.OrderByDescending(r => r.InvoiceDate).FirstOrDefault();
error 3032: Problem in mapping fragments starting at line 28:Condition member 'ArCode.cod_typ' with a condition other than 'IsNull=False' is mapped. Either remove the condition on ArCode.cod_typ or remove it from the mapping.
If I remove the "CodeType" column from the ArCode class and change all "CodeType" references to the database column name of "cod_typ" within the OnModelCreating event, then the statement above executes without error. However, invoice.ShipVia and invoice.Terms will both be null event though there is a matching record in the database.
Update #2
public abstract class ArCode {
[Column("ar_cod")]
public string Code { get; set; }
[Column("terms_desc")]
public string TermsDescription { get; set; }
[Column("terms_typ")]
public string TermsType { get; set; }
[Column("shp_via_desc")]
public string ShipViaDescription { get; set; }
[Column("tax_desc")]
public string TaxDescription { get; set; }
}
public class TermsCode : ArCode { }
public class ShipViaCode : ArCode { }
public class Invoice {
[Column("pi_hist_hdr_invc_no"), Key]
public int InvoiceNumber { get; set; }
[Column("hdr_invc_dat")]
public DateTime InvoiceDate { get; set; }
[Column("shp_via_cod")]
public ShipViaCode ShipVia { get; set; }
[Column("terms_cod")]
public TermsCode Terms { get; set; }
public Invoice() {
}
}
public class PbsContext : DbContext {
public DbSet<Invoice> Invoices { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) {
modelBuilder.Entity<Invoice>().ToTable("IHSHDR");
modelBuilder.Entity<ArCode>().HasKey(r => r.Code).ToTable("ARCODS");
modelBuilder.Entity<TermsCode>().Map(m => m.Requires("CodeType")
.HasValue("T").HasColumnType("varchar").HasMaxLength(1).IsRequired())
.ToTable("ARCODS");
modelBuilder.Entity<ShipViaCode>().Map(m => m.Requires("CodeType")
.HasValue("S").HasColumnType("varchar").HasMaxLength(1).IsRequired())
.ToTable("ARCODS");
base.OnModelCreating(modelBuilder);
}
public PbsContext()
: base("name=PbsDatabase") {
}
}
Now, the following code returns an error:
PbsContext context = new PbsContext();
var invoice = context.Invoices.OrderByDescending(r => r.InvoiceDate).FirstOrDefault();
EntityCommandExecutionException - Invalid column name 'ShipVia_Code'. Invalid column name 'Terms_Code'.
What you want is impossible for EF. ArCode has a composite key, so any association to it will have to use two Properties. That means that in Invoice you'd need four properties (two pairs) to refer to the two ArCode objects. But two of these properties (those for CodeType) are not backed up by columns in the database, so EF can not map them.
But... there is a way that may help you out. You could create two derived classes from ArCode and let Invoice refer to those by single-property associations. But then you have to divert from the model as such and fool EF a bit by defining a single key:
public abstract class ArCode { ... } // abstract!
public class TermsCode : ArCode { }
public class ShipViaCode : ArCode { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Invoice>().ToTable("IHSHDR");
modelBuilder.Entity<Invoice>().HasOptional(i => i.Terms).WithOptionalDependent().Map(m => m.MapKey("terms_cod"));
modelBuilder.Entity<Invoice>().HasOptional(i => i.ShipVia).WithOptionalDependent().Map(m => m.MapKey("shp_via_cod"));
modelBuilder.Entity<ArCode>().HasKey(a => a.Code).ToTable("ARCODS");
modelBuilder.Entity<TermsCode>().Map(m => m.Requires("CodeType")
.HasValue("T").HasColumnType("varchar").HasMaxLength(1).IsRequired())
.ToTable("ARCODS");
modelBuilder.Entity<ShipViaCode>().Map(m => m.Requires("CodeType")
.HasValue("S").HasColumnType("varchar").HasMaxLength(1).IsRequired())
.ToTable("ARCODS");
base.OnModelCreating(modelBuilder);
}
public class Invoice
{
[Column("pi_hist_hdr_invc_no"), Key]
public int InvoiceNumber { get; set; }
public ShipViaCode ShipVia { get; set; }
public TermsCode Terms { get; set; }
}
This may work for you if you don't have to insert ARCODS records through EF. It won't allow you to insert records with identical Codes, although the database would allow it. But I expect the content of ARCODS to be pretty stable and maybe it is enough to fill it with a script.