Entity Framework HasForeignKey error with 1:1 Relationship - entity-framework

When setting up a one-to-many relationship I can do this and it works:
EntityTypeBuilder<BrandOwner> brandOwner = X
brandOwner.HasMany(a => a.Brands).WithOne(b => b.Owner).HasForeignKey("OwnerId");
In a 1:1 relationship I can use this syntax and it works (observe generic HasForeignKey)
person.HasOne(a => a.SinCard).WithOne(b => b.Owner).HasForeignKey<SocialInsuranceCard>(c => c.OwnerId);
But this gives an error:
person.HasOne(a => a.SinCard).WithOne(b => b.Owner).HasForeignKey("OwnerId");
The error is:
InvalidOperationException: You are configuring a relationship between 'Person' and 'SocialInsuranceCard' but have specified a foreign key on 'OwnerId'. The foreign key must be defined on a type that is part of the relationship.
Sample classes:
public class BrandOwner
{
public int Id { get; set; }
public virtual List<Brand> Brands { get; set; }
}
public class Brand
{
public int Id { get; set; }
public int OwnerId { get; set; } // I'm aware of the "BrandOwnerId" convention
public virtual BrandOwner Owner { get; set; }
}
pubic class Person
{
public int Id { get; set; }
public virtual SocialInsuranceCard SinCard { get; set; }
}
pubic class SocialInsuranceCard
{
public int Id { get; set; }
public int OwnerId; // I know I could get auto mapping by using "PersonId"
public virtual Person Owner { get; set; }
}
Why in the 1:1 relationship does using the non-generic HasForeignKey give an error?

The structure you have is not 1-to-1, it is 1-to-many. A 1-to-1 is generally set up to share the same ID:
public class Car
{
public int CarId { get; set; }
public virtual Seat seat { get; set; }
}
public class Seat
{
public int CarId { get; set; } // PK and FK to Car
public virtual Car car { get; set; }
}
This assumes 1 car has 1 seat. If you want to identify something like a Primary Seat within a car that can have multiple seats, you might want something like:
public class Car
{
public int CarId { get; set; }
public virtual ICollection<Seat> Seats { get; set; }
public virtual Seat PrimarySeat { get; set; }
}
public class Seat
{
public int SeatId { get; set; } // PK
public int CarId { get; set; } // FK to Car 1-to-Many
public virtual Car car { get; set; }
}
This relationship would need a Seat ID on Car:
public class Car
{
// ...
public int PrimarySeatId { get; set; } // FK for Primary Seat
}
Setting this up may be possible, but would require explicit mapping for all car and seat relationships rather than leaving it to convention. Car will explicitly need to know how to resolve its Seats collection (which use the CarId on Seat) and to resolve the PrimarySeat instance. (many-to-1) It's not a 1-to-1 specifically because there is nothing stopping multiple cars from specifying the same SeatID as the primary. Additionally, there is nothing enforcing that the PrimarySeat even belongs to the Car's Seats collection either. Any such restrictions and rules would need to be enforced at the application level, possibly supported by a health check query run against the database daily to report any mismatches. (Cars with primary seats who's carId does not match)

Related

because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them

I have implemented following generic model as base model and two models derived from it and i need one to many relationship. I have used fluent api to create the relationships but i get the error mentioned in the title.
Would you please see if you find any thing wrong in my code.
public abstract class ModelBase<T> : BaseEntity, IModelBase<T>
{
public virtual T Id { get; set; }
}
[Table("VehicleModel", Schema = "Tracker")]
public class VehicleModelModel : ModelBase<int>
{
public string Company { get; set; }
public string ModelName { get; set; }
public byte LiPerKM { get; set; }
public virtual ICollection<VehicleModel> Vehicles { get; set; }
[Table("Vehicle", Schema = "Tracker")]
public class VehicleModel : ModelBase<Guid>
{
public string VehicleName { get; set; }
public string LicensePlate { get; set; }
public int VehicleModelId { get; set; }
public virtual VehicleModelModel Model { get; set; }
public virtual TrackerModel Tracker { get; set; }
[Required]
public virtual DriverModel Driver { get; set; }
modelBuilder.Entity<VehicleModel>()
.HasRequired<VehicleModelModel>(s => s.Model)
.WithMany(s => s.Vehicles)
.HasForeignKey(s => s.VehicleModelId);
Here I get this error:
entity types 'VehicleModelModel' and 'VehicleModel' cannot share table 'VehicleModels' because they are not in the same type hierarchy or do not have a valid one to one foreign key relationship with matching primary keys between them.

Entity Framework one-to-one relationship - Unable to determine the principal

I have 2 models:
public class TransactionHistory : IDbEntity {
[Key]
public int ID { get; set; }
public ItemHistory ItemHistory { get; set; }
}
public class ItemHistory : IDbEntity {
[Key]
public int ID { get; set; }
public int TransactionHistoryID { get; set; }
public TransactionHistory TransactionHistory { get; set; }
}
There's a one to one relationship between TransactionHistory and ItemHistory, ItemHistory MUST have a TransactionHistory but TransactionHistory may or may not have an ItemHistory.
I want to be able to do this in code:
var test = db.ItemHistory.Include(x => x.TransactionHistory).ToList();
As well as:
var test2 = db.TransactionHistory.Include(x => x.ItemHistory).ToList();
But I only want a single FK on the ItemHistory table.
With the code I've listed I get this error:
Unable to determine the principal end of an association between the types 'InventoryLibrary.DomainModels.TransactionHistory' and 'InventoryLibrary.DomainModels.ItemHistory'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
How is this achieved in Entity Framework code first data annotations?
Firstly, you have to mark foreign keys by virtual keyword to enable overrides.
public class TransactionHistory : IDbEntity
{
[Key]
public int ID { get; set; }
public virtual ItemHistory ItemHistory { get; set; }
}
public class ItemHistory : IDbEntity
{
[Key]
public int ID { get; set; }
public int TransactionHistoryID { get; set; }
[Required]
public virtual TransactionHistory TransactionHistory { get; set; }
}
If HistoryItem must have Transaction History, add DataAnnotation [Required], which makes it non-nullable.
Finally, wonder, if you want to have one-to-one relationship. I imagine you'd like to have many transaction history entries. Am I right? If not - let me know.
To create one-to-many relationship, use IEnumerable<> type.

EF 4.0 - CodeFirst One To Many - Fluent API

I have the following two classes:
public class Person
{
public int Id { get; set; }
public string FullName { get; set; }
}
public class Trip
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IEnumerable<Person> Persons { get; set; }
}
As you can see, a Trip can have 1 or more Persons...
I tried to use the EntityConfiguration to build the database properly but I cannot manage to make it work... I am quite confused on its usage:
public class TripConfiguration : EntityTypeConfiguration<Trip>
{
internal TripConfiguration()
{
// ???
}
}
What do I need to write to have the application to behave properly:
I need at least one person.
I might have more that one person
A person cannot be in the SAME trip twice
A person can be in more than one trip
Try this:
this.HasRequired(x => x.Person)
.WithMany(x => x.Trips)
.HasForeignKey(x => x.PersonId);
Your classes:
public class Person
{
public int Id { get; set; }
public string FullName { get; set; }
public virtual ICollection<Trip> Trips { get; set;}
}
public class Trip
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
}
And as far that I know, EF doesn't support unique FK (or correct me if I'm wrong..). So you have to check it yourself.
This is not a One-To-Many relationship, this is a Many-To-Many relationship, you need to have collections on both sides of the relationship. EF will create the joiner table on your behalf. Since today you cannot configure a person being in a trip only once you will need to create a unique constraint in your joiner table once is created to assure this happens since EF does not yet support Unique Key constraints through configuration.
public class Person
{
public int Id { get; set; }
public string FullName { get; set; }
public virtual ICollection<Trip> Trips { get; set; }
}
public class Trip
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Person> Persons { get; set; }
}
then
class PersonConfiguration : EntityTypeConfiguration<Person>
{
public PersonConfiguration()
{
this.HasMany(t => t.Trips).WithMany(t => t.Persons);
}
}

Why am I getting an extra foreign key column with Entity Framework Code First Foreign Key Attributes?

I recently came across this strange problem with Entity Framework Code First.
My class looks like this
public class Status
{
[Key]
public int StatusID { get; set; }
public string Name { get; set; }
public int MemberID { get; set; }
[ForeignKey("MemberID")]
public virtual Member Member { get; set; }
public int PosterID { get; set; }
[ForeignKey("PosterID")]
public virtual Member Poster { get; set; }
public virtual ICollection<StatusLike> StatusLikes { get; set; }
public virtual ICollection<StatusComment> StatusComments { get; set; }
}
My Member class looks like this
public class Member
{
[Key]
public int MemberID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Bio { get; set; }
public virtual ICollection<MemberCourseTaken> MemberCourseTakens { get; set; }
public virtual ICollection<Status> Statuses { get; set; }
public virtual ICollection<Club> FoundedClubs { get; set; }
public string EmailAddress { get; set; }
public string Password { get; set; }
public string Phone { get; set; }
public int AccountSourceID { get; set; }
public AccountSource AccountSource { get; set; }
public int AddressID { get; set; }
public Address Address { get; set; }
public string ProfilePhoto { get; set; }
public int MemberRankID { get; set; }
public MemberRank MemberRank { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
}
And for whatever reason the database table that is created has the following columns
StatusID
Name
MemberID
PosterID
Member_MemberID
with MemberID, PosterID, and Member_MemberID being foreign keys.
How can I keep Member_MemberID from being generated?
Your Member_MemberID column is created because of the Member.Statuses property. I can imagine that this is not what you want. Probably members and statuses should exist independent of each other, so you need a junction table.
I don't know if you already use the OnModelCreating override of the DbContext, but that's the place to change the mapping between Member and Status:
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<Member>().HasMany(m => m.Statuses).WithMany();
}
This will create a table MemberStatuses table with the two Id columns as foreign keys. This is a way to model a many-to-many relationship without a navigation property on the "other" side of the association. (I don't think you want a Members property in Status).
I've seen this before. In my case (Using EF 6.1), it was because my Fluent API Mapping was set up like so:
// In my EntityTypeConfiguration<Status>
HasRequired(x => x.Member).WithMany().HasForeignKey(x => x.MemberID);
That code works perfectly fine, but it doesn't tell EF that my Member class's Collection Navigational Property Status ha been taken into account. So, while I explicitly handled the existence of a Member Navigational Property in my Status Class, I now left an orphaned related collection property. That orphaned property, being a collection, tells EF that my Status class needs to have a Foreign Key to it. So it creates that on the Status Class.
To fix it, I had to be 100% explicit.
HasRequired(x => x.Member).WithMany(x => x.Statuses).HasForeignKey(x => x.MemberID)
It could bee that your Statuses Collection property in Member needs an attribute telling it that it is already considered, and not to go auto-creating mappings. I don't know that attribute.

Help with EF Code First, many to many relationship

Person and EHR(electronic health record) are one to one related.
Person has EHRId nullable and EHR has PersonId not nullable.
At the same time EHR and Person must be many to many related.
Because a person can have many medics (represented by person entity) and a medic can have many EHRs.
I would like to have extra attributes on the join table.
I dont know how to define this in EF.
Please help.
Here are my classes.
public class Person
{
public int ID { get; set; }
public string firstName { get; set; }
public string lastName { get; set; }
public ICollection<UserSpecialist> patients { get; set; }
public int ehrID { get; set; }
public virtual EHR ehr { get; set; }
}
public class EHR
{
public int ID { get; set; }
public bool asthmatic{ get; set; }
public ICollection<UserSpecialist> specialists { get; set; }
public int PersonID { get; set; }
public virtual Person Person { get; set; }
}
public class UserSpecialist
{
public int ID { get; set; }
public DateTime creationDate { get; set; }
public int PersonID { get; set; }
public int EHRID { get; set; }
public virtual Person Person { get; set; }
public virtual EHR EHR { get; set; }
}
When EF tries to create the database throws this error
Unable to determine the principal end
of an association between the types
'Project.Person' and 'Project.EHR'.
The principal end of this association
must be explicitly configured using
either the relationship fluent API or
data annotations.
Please help
Person and EHR are not one-to-one related and they cannot be in EF. What you have defined is bidirectional one-to-many. You have also declared both relations as required because FK's are not nullable.
Real one-to-one can be defined in EF only if EHR's PK (Id) is also FK to Person. Once you define this the part with many-to-many becomes really strange because Person will be related with EHRs of other persons. You domain description is most probably not correct.