Entity Framework multi level inheritance using fluent - entity-framework

I get an error when I want to create a multi-level table with fluent api for migration on Entity framework.
but I got fail:
SQLite Error 19: 'FOREIGN KEY constraint failed'.
The table structures are as follows:
Base Entity just for Id
public class MainArt : BaseEntity
{
public Paper Paper { get; set; }
public int PaperId { get; set; }
}
public class Paper : BaseEntity
{
public string Name { get; set; }
public string PaperPrice { get; set; }
public ColorType ColorType { get; set; }
public int ColorTypeId { get; set; }
}
public class ColorType : BaseEntity
{
public string Name { get; set; }
}
MainArt --> Paper --> ColorType
EF Fluent as below:
MainArt:
public void Configure(EntityTypeBuilder<MainArt> builder)
{
builder.Property(p => p.Id).IsRequired();
builder.HasOne(p => p.Paper).WithMany().HasForeignKey(p => p.PaperId);
}
Paper:
public void Configure(EntityTypeBuilder<Paper> builder)
{
builder.Property(p => p.Id).IsRequired();
builder.Property(p => p.Name).IsRequired().HasMaxLength(100);
builder.Property(p => p.PaperPrice).IsRequired();
builder.HasOne(p => p.ColorType).WithMany().HasForeignKey(p => p.ColorTypeId);
}
}
ColorType and Paper created with migration but MainArt failed.
I searched the web and i think iam missing something. ToTable or one-to-many i don't know.
Thanks.

I found out why it is giving exception. The code is not wrong.
Failure to create one of the child tables causing the error.
Migration and seeding worked correctly when I created the child table correctly.

Related

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)?

Including derived child properties in Entity Framework Core 2.0

Using Entity Framework Core 2.0, I am trying to construct a query to include related data for a polymorphic child entity.
For example, given the following types:
public class ParentEntity
{
public int Id { get; set; }
public IList<ChildEntityBase> Children { get; set; }
}
public abstract class ChildEntityBase
{
public int Id { get; set; }
}
public class ChildEntityA : ChildEntityBase
{
}
public class ChildEntityB : ChildEntityBase
{
public IList<GrandchildEntity> Children { get; set; }
}
public class GrandchildEntity
{
public int Id { get; set; }
}
and the following configuration:
public DbSet<ParentEntity> ParentEntities { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<ParentEntity>().HasKey(p => p.Id);
builder.Entity<ParentEntity>().HasMany(p => p.Children).WithOne();
builder.Entity<ChildEntityBase>().HasKey(c => c.Id);
builder.Entity<ChildEntityBase>()
.HasDiscriminator<string>("ChildEntityType")
.HasValue<ChildEntityA>("a")
.HasValue<ChildEntityB>("b");
builder.Entity<ChildEntityA>()
.HasBaseType<ChildEntityBase>();
builder.Entity<ChildEntityB>()
.HasBaseType<ChildEntityBase>()
.HasMany(u => u.Children).WithOne();
builder.Entity<GrandchildEntity>()
.HasBaseType<ChildEntityBase>();
base.OnModelCreating(builder);
}
I am trying to write the following query:
var result = this.serviceDbContext.ParentEntities
.Include(p => p.Children)
.ThenInclude((ChildEntityB b) => b.Children);
Unfortunately, this is resulting in a syntax error.
However, I believe I am following the syntax as specified in https://github.com/aspnet/EntityFrameworkCore/commit/07afd7aa330da5b6d90d518da7375d8bbf676dfd
Can anyone suggest what I'm doing wrong?
Thanks
This functionality is not available in EFC 2.0.
It's been tracked as #3910 Query: Support Include/ThenInclude for navigation on derived type and according to the current EFC Roadmap, it's scheduled for EFC 2.1 release (Include for derived types item under
Features we have committed to complete).

How to have an entity inherit from another base entity and map to db using TPC with EF 4.2?

Say I have an entity model aggregate for Activity, like so:
public class Activity : Entity
{
public int PersonId { get; set; }
public virtual Person Person { get; set; }
public int Number { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public virtual ICollection<ActivityTag> Tags { get; set; }
}
public class ActivityTag : Entity
{
public int ActivityPersonId { get; set; }
public int ActivityNumber { get; set; }
public virtual Activity Activity { get; set; }
public int Number { get; set; }
public string Text { get; set; }
}
Forget about the relation between Activity and Person, but note the 1..* relation between Activity and ActivityTag. The fluent mapping looks more or less like this:
public class ActivityOrm : EntityTypeConfiguration<Activity>
{
public ActivityOrm()
{
ToTable("Activity", "Activities");
HasKey(p => new { p.PersonId, p.Number });
HasRequired(d => d.Person)
.WithMany()
.HasForeignKey(d => d.PersonId)
.WillCascadeOnDelete(true);
HasMany(p => p.Tags)
.WithRequired(d => d.Activity)
.HasForeignKey(d => new { d.ActivityPersonId, d.ActivityNumber })
.WillCascadeOnDelete(true);
Property(p => p.Content).HasColumnType("ntext");
}
}
public class ActivityTagOrm : EntityTypeConfiguration<ActivityTag>
{
public ActivityTagOrm()
{
ToTable("ActivityTag", "Activities");
HasKey(p => new { p.ActivityPersonId, p.ActivityNumber, p.Number });
Property(p => p.Text).IsRequired().HasMaxLength(500);
}
}
Given this, I want to introduce a new collection property to the Activity entity:
public ICollection<DraftedTag> DraftedTags { get; set; }
The DraftedTag entity should have the same exact properties and primary key as ActivityTag. The only thing that should be different is the table it is mapped to. I tried creating a class that derived from ActivityTag, like so:
public class DraftedTag : ActivityTag
{
}
public class DraftedTagOrm : EntityTypeConfiguration<DraftedTag>
{
public DraftedTagOrm()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("DraftedTag", "Activities");
});
HasKey(p => new { p.ActivityPersonId, p.ActivityNumber, p.Number });
}
}
The DraftedTagOrm has been added to the modelBuilder.Configurations collection, but without even adding the foreign key association to Activity, I get the following exception:
The property 'ActivityPersonId' is not a declared property on type
'DraftedTag'. Verify that the property has not been explicitly
excluded from the model by using the Ignore method or
NotMappedAttribute data annotation. Make sure that it is a valid
primitive property.
When I completely duplicate the code from the ActivityTag class and the ActivityTagOrm constructor into the respective DraftTag class / configuration constructor, then it works as expected -- I get two different tables with identical schemas, but different names. However each time I want to make a change to the ActivityTag class, I must make a corresponding change in the DraftTag class.
Is it possible to make this code DRYer by having DraftTag extend ActivityTag? If so, what would the EntityTypeConfiguration look like for DraftTag?

Specifying Foreign Key Entity Framework Code First, Fluent Api

I have a question about defining Foreign Key in EF Code First Fluent API.
I have a scenario like this:
Two class Person and Car. In my scenario Car can have assign Person or not (one or zero relationship).
Code:
public class Person
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class Car
{
public int Id { get; set; }
public string Name { get; set; }
public Person Person { get; set; }
public int? PPPPP { get; set; }
}
class TestContext : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<Car> Cars { get; set; }
public TestContext(string connectionString) : base(connectionString)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
}
In my sample I want to rename foreign key PersonId to PPPPP. In my mapping I say:
modelBuilder.Entity<Car>()
.HasOptional(x => x.Person)
.WithMany()
.HasForeignKey(x => x.PPPPP)
.WillCascadeOnDelete(true);
But my relationship is one to zero and I'm afraid I do mistake using WithMany method, but EF generate database with proper mappings, and everything works well.
Please say if I'm wrong in my Fluent API code or it's good way to do like now is done.
Thanks for help.
I do not see a problem with the use of fluent API here. If you do not want the collection navigational property(ie: Cars) on the Person class you can use the argument less WithMany method.

EF4 CTP5, mapping different entities to the same (existing) table

By code-first approach (but with an existing db schema), we are trying to map 2 different entities (Customer and Resource) to the same table. Both entities has the same keys and mapping.
However, when running the app, we have a runtime error telling us that mysterious message:
System.InvalidOperationException: Type 'Resource' cannot be mapped to table 'CLIENT' since type 'Customer' also maps to the same table and their primary key names don't match. Change either of the primary key property names so that they match.
Example:
public class EntityA
{
public string ID { get; set; }
public string Discriminator { get; set; }
public string TimeStamp { get; set; }
}
public class EntityB
{
public string ID { get; set; }
public string Discriminator { get; set; }
public string CreatedBy { get; set; }
}
public class EntityAConfiguration : EntityTypeConfiguration<EntityA>
{
public EntityAConfiguration()
{
HasKey(x => new {x.ID, x.Discriminator } );
Property(x => x.ID).HasColumnName("MyTable_ID").HasDatabaseGenerationOption(DatabaseGenerationOption.None);
Property(x => x.Discriminator).HasColumnName("MyTable_Discriminator").HasDatabaseGenerationOption(DatabaseGenerationOption.None);
Property(x => x.TimeStamp).HasColumnName("MyTable_TimeStamp");
ToTable("MyTable");
}
}
public class EntityBConfiguration : EntityTypeConfiguration<EntityB>
{
public EntityBConfiguration()
{
HasKey(x => new { x.ID, x.Discriminator });
Property(x => x.ID).HasColumnName("MyTable_ID").HasDatabaseGenerationOption(DatabaseGenerationOption.None);
Property(x => x.Discriminator).HasColumnName("MyTable_Discriminator").HasDatabaseGenerationOption(DatabaseGenerationOption.None);
Property(x => x.CreatedBy).HasColumnName("MyTable_CreatedBy");
ToTable("MyTable");
}
}
The above code is similar to our Customer/Resource code (but simpler for the explanation!).
However, get get the same Error message, telling us that EntityA and EntityB cannot be mapped to the same table because their primary key names don't match.
Any idea of what is wrong with our mapping?
Any idea how we could different entities to the same table?
Thanks for your help
Mapping 2 entity to one table requires that you create a Complex Type or Table Per Hierarchy (TPH). You can't just map 2 entities to one table like this. Let me know which one is better describe your domain model and I will provide you with the required object model/fluent API code.
Update: TPH Mapping:
public abstract class EntityBase
{
[Column(Name = "MyTable_ID")]
public string ID { get; set; }
[Column(Name = "MyTable_Discriminator")]
public string Discriminator { get; set; }
}
public class EntityA : EntityBase
{
[Column(Name = "MyTable_TimeStamp")]
public string TimeStamp { get; set; }
}
public class EntityB : EntityBase
{
[Column(Name = "MyTable_CreatedBy")]
public string CreatedBy { get; set; }
}
public class StackoverflowTestContext : DbContext
{
public DbSet<EntityBase> Entities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EntityBase>()
.HasKey(x => new { x.ID, x.Discriminator });
modelBuilder.Entity<EntityBase>()
.Map<EntityA>(m => m.Requires("TPHDiscriminator")
.HasValue("yourDesiredValueForA"))
.Map<EntityB>(m => m.Requires("TPHDiscriminator")
.HasValue("yourDesiredValueForB"))
.ToTable("MyTable");
}
}