Entity Framework TPH Inheritance Data Modeling Issues - entity-framework

I'm new to Entity Framework and C#/.Net and trying to create a TPH inheritance model, I'm not sure if I should be or not, so if not, please advise,
Here's the model:
public abstract class Vote
{
public int VoteID { get; set; }
public int UserID { get; set; }
public bool Value { get; set; }
public DateTime DateCreated { get; set; }
public User User { get; set; }
}
public class ProjectVote_ : Vote
{
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
public class CommentVote_ : Vote //There are three more like this, votes for different hings
{
public int CommentID { get; set; }
public virtual Comment Comment { get; set; }
}
Now the Project model (comment and model is similar)
public class Project
{
public int ProjectID { get; set; }
public string Title { get; set; }
public virtual ICollection<Vote> Vote { get; set; }
}
What happens is that ICollection creates a database column Project_ProjectID as the foreign key in the Vote table (I think) instead of using the ProjectID I defined. How do I fix it or should I model it differently. If the fluent API is the way to fix it, I don't know how to do that.
In the end I want to be able to use one table to store 5 different types of votes.

When you have related entities you don't need to have a property to store the FK in your model. Entity framework knows that it needs to make a FK to the Project table in ProjectVote when it detects Project in your ProjectVote_ model. Same thing with User and UserId and Comment and CommentId. You don't need to have a property that stores the FK in your model.
You are getting the FK column with the name you don't like "Project_ProjectID" because Entity framework is detecting that it needs to create a FK for your navigation property "Project". It's using it's own naming convention to create the column hence "Project_ProjectID".
If you want to provide your own name for the column override OnModelCreating in your DBContext class and add this fluent mapping.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasMany(p => p.Vote)
.HasRequired(v => v.Project) //or .WithOptional(v => v.Project)
.Map(m => m.MapKey("ProjectId")); //or any other name you want.
}
And for the future this is a helpful reference for how to use the Fluent API. For example here is some documentation on how to custimize TPH with fluent.
Hope that helps!

Related

.NET Core Entity Framework linking subtable to property

This is an existing .NET Core 3.1 project I inherited.
I have a class referring to a database table
public class SupportContract
{
public int Id { get; set; }
public DateTime StartDate { get; set; }
public int SupportContractStatusId { get; set; }
public virtual SupportContractStatus SupportContractStatus { get; set; }
}
and a sub table with a foreign key
public class SupportContractStatus
{
public int Id { get; set; }
public string SupportContractStatusName { get; set; }
}
This works fine I can get
supportContract.SupportContractStatus.SupportContractStatusName
But if I rename SupportContractStatusId to ContractStatusId in C# and the database, I get an error "SupportContractStatusId missing".
I cannot find any link between the column SupportContractStatusId and table SupportContractStatus anywhere in code nor is there any mention of the foreign key.
There is no link in the DbContext either.
Is this naming convention assumed by Entity Framework? How does the framework know of the foreign key?
Yes, the naming convention that EF expects by default is based on the class name, not the property name. It will look for ClassNameId or ClassName_Id. You can link the FK either through annotation or configuration.
I.e.
public int ContractStatusId { get; set; }
[ForeignKey("ContractStatusId")]
public virtual SupportContractStatus ContractStatus { get; set; }
Configuration is done through IEntityTypeConfiguration implementations or by implementing the OnModelCreating method in the DbContext and configuring the relationship within the modelBuilder. For occasional deviations from convention, the attribute approach can generally cover everything.

Defining a "one-to-one-to-one" relationship on Firebird 2.5 EF Core

I'm working with EF Core and a third-party Firebird 2.5 database and, for some reason, they decided to, rather than do a simple one-to-one relationship, create a single table with two columns that do that relationship itself, i.e.
STOCK
========
ID_STOCK(int)
more columns (and their datatypes)
STOCK_IDENTIFIER
========
ID_STOCK (int)
ID_IDENTIFIER (int)
STOCK_PRODUCT
========
ID_IDENTIFIER (int)
more columns (and their datatypes)
So, every STOCK has one STOCK_IDENTIFIER, which, in turn, has one STOCK_PRODUCT. Usually, when I'm creating my own DB in MySQL, I just set foreign keys with data annotations (I'm not fluent in Fluent API, pun intended) and let the migration do its job. However, in this case I can't change the DB's schema (on the account of it being third-party), so I need to use the existing structure.
Right now I have the following:
public class STOCK
{
[Key]
public int ID_STOCK { get; set; }
[MaxLength(50)]
public string DESCRIPTION { get; set; }
[Column(TypeName = "decimal(18, 4)")]
public decimal SELL_PRICE { get; set; }
public STOCK_IDENTIFIER STOCK_IDENTIFIER{ get; set; }
}
public class STOCK_IDENTIFIER
{
[ForeignKey("ID_STOCK")]
public STOCK ID_STOCK { get; set; }
public STOCK_PRODUCT ID_PRODUCT { get; set; }
}
public class STOCK_PRODUCT
{
[ForeignKey("ID_PRODUCT")]
public STOCK_IDENTIFIER ID_IDENTIFIER{ get; set; }
[MaxLength(18)]
public string GTIN{ get; set; }
[MaxLength(18)]
public string SKU{ get; set; }
[Column(TypeName = "decimal(18, 4)")]
public decimal INSTOCK_AMNT { get; set; }
}
I read at The property X is of type Y which is not supported by current database provider that Fluent API could fix that, however, that article works flawlessly for one-to-one. As soon as I try to implement Fluent on a cascading relationship like this one, I get
modelBuilder.Entity<STOCK_IDENTIFIER>()
.HasOne<STOCK_PRODUCT>(p => p.ID_IDENTIFIER)
.WithOne();
modelBuilder.Entity<STOCK>()
.HasOne<STOCK_IDENTIFIER>(p => p.IDENTIFICADOR)
.WithOne();
The property or navigation 'ID_IDENTIFIER' cannot be added to the entity
type 'STOCK_PRODUCT' because a property or navigation with the
same name already exists on entity type 'STOCK_PRODUCT'.
Any hints on what I've been doing wrong?

EF Core 3.1.7 Data annotations for multiple 1:1 relationships in table

I am having problems figuring out the data annotations to map more than one 1:1 relationships so that EF Core 3.11.7 understands it and can build a migration.
I have a Person table and a Notes table.
There is a 0:M Notes relationship in Person. A person record can have 0 or more notes.
In the notes table is a CreatedBy field which is a Person. It also has a LastEditedBy field which is also a person. EF keeps bombing on how to construct the relationship for Note.CreatedBy. If this were non EF, both fields would be ints with the PersonID of the proper person record. How do it, preferabbly with Data Annotations, explain this to EF Core?
When I try to create a migration it fails and says:
System.InvalidOperationException: Unable to determine the relationship represented by navigation property 'Note.CreatedBy' of type 'Person'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace VetReg.Domain.Model
{
public class Family
{
public int FamilyID { get; set; } = -1;
public string FamilyName { get; set; }
public List<Pet> Pets { get; set; } = new List<Pet>();
public List<PersonFamily> People { get; set; }
public int AddressID { get; set; } = -1;
public List<Note> Notes { get; set; }
}
public class Person
{
public int PersonID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime? Birthdate { get; set; }
public string Password { get; set; }
public List<PersonFamily> Families { get; set; }
public List<Note> Notes { get; set; }
} // class People
public class Note
{
public int NoteID { get; set; }
public int CreatedByID { get; set; }
[ForeignKey("CreatedByID")]
public Person CreatedBy { get; set; }
public DateTime DateCreated { get; set; }
public int LastEditByID { get; set; }
[ForeignKey("LastEditByID")]
public Person LastEditBy { get; set; }
public DateTime? LastEditDate { get; set; }
public string NoteText { get; set; }
}
public class PersonFamily
{
public int PersonID { get; set; }
public int FamilyID { get; set; }
public Person Person { get; set; }
public Family Family { get; set; }
}
}
The question is (and this is what makes impossible to EF to automatically determine the relationships) what is the relation between Person.Notes and Note.CreatedBy / Note.LastEditBy - none probably? You've said there is 0:M relationship between Person and Note, but note that there are potentially 3 one-to-many relationships there - notes associated with person, notes created by person and notes edited by person, which potentially leads to 3 FKs to Person in Note.
Also note that none of the navigation properties is required, but when present they must be paired.
Assuming you want 3 relationships, i.e. there is no relation between Note.CreatedBy / Note.LastEditBy and Person.Notes, you need to tell EF that Note.CreatedBy and Note.LastEditBy do not have corresponding (a.k.a. inverse) navigation property in Person. This is not possible with data annotations. The only available data annotation for that purpose [InverseProperty(...)] does not accept empty/null string name, hence cannot be used for what is needed here.
Also there is another problem here which you will encounter after resolving the current, which also cannot be resolved with data annotations. Since you have multiple required (thus cascade delete by default) relationships from Person to Note, it creates the famous "cycles or multiple cascade paths" problem with SqlServer, and requires turning off at least one of the cascade delete.
With that being said, the model in question needs the following minimal fluent configuration:
modelBuilder.Entity<Note>()
.HasOne(e => e.CreatedBy)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Note>()
.HasOne(e => e.LastEditBy)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
The essential for the original issue are the HasOne / WithMany pairs. Once you do that, EF Core will automatically map the unmapped Person.Notes collection to a third optional relationship with no inverse navigation property and shadow FP property (and column) called "PersonId", i.e. the equivalent of
modelBuilder.Entity<Person>()
.HasMany(e => e.Notes)
.WithOne()
.HasForeignKey("PersonId");
Regarding the second issue with multiple cascade paths, instead of Restrict you can use any non cascading option or the newer ClientCascade. And it could be for just one of the relationships, as soon as it breaks the "cascade path" (apparently you can't break the cycle because it is demanded by the model).

EF 5 code first optional one to one mapping accessing foreign key fields on the model

This question is basically a repeat of this question regarding EF4 CTP but specific to EF 5.
I have a POCOs set up such that
public class ClassPrinciple
{
public int ClassPrincipleID { get; set; }
public virtual ClassDependent ClassDependent{ get; set; }
}
and
public class ClassDependent
{
public int ClassDependentID { get; set; }
public virtual ClassPrinciple ClassPrinciple{ get; set; }
}
in my model builder I create the optional one to one mapping like this
modelBuilder.Entity<ClassPrinciple>().HasOptional(p => p.ClassDependent)
.WithOptionalDependent(s => s.ClassPrinciple);
this creates, on the ClassPrinciples table a column called ClassDependent_ClassDependentID . I would like to be able to reference the data in this column through a property on the ClassPrinciple model but I seem unable to do so. The web page I linked to at the top of this question states:
EF in general only supports exposing FK properties on your entities in
one:many relationships (unless the FK is also the PK). This is
somewhat artificial but a side effect of EF not supporting non-PK
unique constraints. We are working on support for unique constraints
for EF at the moment but it won't be there in our first RTM of Code
First.
Sorry not to have a better answer as there really isn't a workaround
at this stage.
Is this still the case or is there a way to resolve this. I have tried fluent api map to column and data annotations in all sorts of combinations without success.
use this code :
public class ClassPrinciple
{
public int ClassPrincipleID { get; set; }
public int ClassDependentId { get; set; }
[ForeignKey("ClassDependentId")]
[InverseProperty("ClassPrinciple")]
public virtual ClassDependent ClassDependent{ get; set; }
}
public class ClassDependent
{
public int ClassDependentID { get; set; }
[InverseProperty("ClassDependent")]
public virtual ClassPrinciple ClassPrinciple{ get; set; }
}

Entity Framework code first FK field

I have two classes:
public class Fighter
{
public int FighterID { get; set; }
public int DivsionID { get; set; }
public string Name { get; set; }
//...
public virtual Division Division { get; set; }
}
public class Division
{
public int DivisionID { get; set; }
public string Name { get; set; }
public int? FromWeight { get; set; }
public int? ToWeight { get; set; }
public ICollection<Fighter> Fighters { get; set; }
}
Why do I have Division_DivisionID on my Fighters table ? I thought the DevisionID should be the FK.
I wrote an article on how this works, take a look at http://blog.staticvoid.co.nz/2012/07/entity-framework-navigation-property.html - See How does Entity Framework detect Navigation Properties
In short this is due to a convention which says FKs are named
<LocalPropertyName>_<ForeignIdPropertyName>
Also see Entity Framework Navigation Property generation rules
to make EF name the FK DivisionID, add the following to your modelbuilder
modelBuilder.Entity<Fighter>()
.HasRequired(f => f.Division)
.WithMany(d => d.Fighters)
.HasForeignKey(f => f.DivisionID);
You are mixing the EF FK Association concept with your database FK concept, they are not the same. The FK Association concept on EF was introduced so you could do things like lookups and data binding more easily (like DropDownList data binding for example).
The ER FK concept that is created in your table is a mapping for the composition you have on the Fighter class, in this case the Division property. The naming of that table column follows EF's rules.
For more on the EF FK Association read this article.