EF 0..1 to 0..1 relationship without shared primary key - entity-framework

I have two entities which I would like to configure an optional 1-1 relationship (i.e. 0..1 to 0..1). I would like to do this using a key only on one entity, while having navigation properties in both directions.
Consider the following simplified example to illustrate:
public class Person
{
public int PersonId { get; set; }
public int? TicketId { get; set; }
public virtual Ticket Ticket { get; set; }
}
public class Ticket
{
public int TicketId { get; set; }
public virtual Person Person { get; set; }
}
There are a number of People, and a number of Tickets. A person can possess at most one ticket. There are people with no ticket, and unclaimed tickets.
I thought the following might work:
modelBuilder.Entity<Person>()
.HasOptional(p => p.Ticket)
.WithOptionalPrincipal(t => t.Person);
But this creates an additional ID on the Ticket table.
I know this is possible using two optional relationships, but this is not an ideal solution as it requires two calls to SaveChanges, and does not guarantee referential integrity (e.g. Person1 could own Ticket1, but Ticket1 could point to Person2).
Similar questions have usually focussed on a 1-0..1 relationship where the principal entity's primary key can be used on the dependent - this is not suitable either.
I am using EF6 Code-First.

Related

How to change foreign key suffix in Entity Framework Core?

In EF Core with a code-first approach, by default column referencing another entity has an Id suffix - for example PersonId.
Is it possible - and if so, how? - to change it to _id, so to person_id?
Create the foreign key explicitly under the name you want - in your case Parent_Id. Keep a navigation property and foreign key property.
public int Parent_ID { get; set; }
public virtual Parent Parent { get; set; }
Map the foreign key relations using .HasForeignKey(). Something similar as below
builder.HasOne(d => d.Prop)
.WithMany(p => p.NavigationProp)
.HasForeignKey(d => d.ForeignKeyProp)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_ConstraintName");
If you prefer data annotation, you could also use
[Column("Parent_ID")]
public int ParentID { get; set; }
To add to WisdomSeeker's answer, you can use a [ForeignKey] annotation to point at a shadow property for the FK.
Given a class like a Course with a Person reference for a Teacher:
public class Course
{
[Key]
public int Id {get; set;}
// other fields.
[ForeignKey("person_id")]
public virtual Person Teacher { get; set; }
}
Alternatives as above would be:
[ForeignKey("Teacher")]
public int person_id { get; set; } // Not recommended naming convention in code.
public virtual Person Teacher { get; set; }
or
[Column("person_id"), ForeignKey("Teacher")]
public int TeacherId { get; set; }
public virtual Person Teacher { get; set; }
I generally avoid adding FK fields into classes as this leads to two sources of truth for what Teacher is assigned to a course. You have course.TeacherId and course.Teacher.Id, which could differ on update prior and after a SaveChanges. Shadow properties help avoid confusion and keep data updates consistent.
Using [Column] is common in Db-First implementations where you want to use a C# naming convention for properties to use in-code, but abide by existing/desired DB naming conventions in the database. I don't generally recommend using DB naming conventions in C# classes.

EF: configuring many to many relationship (code first)

In my scenario I have Jobs, Companies and Departments.
Single Job may have only one Company; Company may have multiple Jobs (One-To-Many)
Single Job may have multiple Departments; Department may have multiple Jobs (Many-To-Many).
I want to set relations using Foreign Keys only. For that I have property of Foreign Key and lazy navigation property.
These are my classes:
public class JobEntity
{
[Key]
public int Id
{
get;
set;
}
public Companies CompanyId
{
get;
set;
}
//Navigation
[ForeignKey("CompanyId")]
public virtual CompanyEntity Company
{
get;
set;
}
public IList<Departments> Departments
{
get;
set;
}
//navigation
public virtual IList<DepartmentEntity> DepartmentsNavigation
{
get;
set;
}
}
public class DepartmentEntity
{
public Departments Id
{
get;
set;
}
public string Name
{
get;
set;
}
//navigation
public virtual IList<JobEntity> Jobs
{
get;
set;
}
}
public class CompanyEntity
{
public Companies Id
{
get;
set;
}
public string Name
{
get;
set;
}
//navigation
public virtual List<JobEntity> Jobs
{
get;
set;
}
}
Also, I have many-to-many mapping inside my context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<JobEntity>()
.HasMany<DepartmentEntity>(s => s.DepartmentsNavigation)
.WithMany(c => c.JobsNavigation)
.Map(cs =>
{
cs.MapLeftKey("JobId");
cs.MapRightKey("DepartmentId");
cs.ToTable("JobsDepartments");
});
}
When I set CompanyId into Job, everything work as expected: when I getting Job from DB, I have an associated Company lazy loaded.
However, when I setting into Job list of related foreign keys ('Departments') - when I loading Job from DB this list is null and departments navigation property ('DepartmentsNavigation') having count of 0 (I was expected to have collection of department ids that I set + lazy loaded collection of departments).
What I doing wrong?
For many-to-many, you can't use a foreign key association.
There's a reference to this here:
One-to-one relation in EFv4 always uses Foreign Key association and many-to-many relation always uses Independent association.
and here:
Note: In many-to-many (*:*) you cannot add foreign keys to the entities. In a *:* relationship, the association information is managed with an independent object.
and here:
However, if you have a pure many-to-many relationship that is connected by a join table that contains only foreign keys, the EF will use an independent association to manage such many-to-many relationship.
Your foreign keys are already in the JobsDepartments table, so EF won't let you add additional foreign keys for that relationship in the JobEntity and DepartmentEntity entities.

EF: Unable to determine the principal end of an association between the types

Unable to determine the principal end of an association between the types. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Models:
`
[Table("Employees")]
public class Employee : Entity
{
public string Name { get; set; }
public int? AbsenceId { get; set; }
[ForeignKey("AbsenceId")]
public virtual Absence Absence { get; set; }
}
[Table("Absences")]
public class Absence : Entity
{
public DateTime From { get; set; }
public DateTime To { get; set; }
public string Reason { get; set; }
public int? SubstituteId { get; set; }
[ForeignKey("SubstituteId")]
public virtual Employee Substitute { get; set; }
}
`
The Employee have a Absence that can have a Employee that is not same Employee that have a Absence mentioned.
Any solution for this case?
Well, first of all.. You do not need to specify ForeignKey when you are following the Entity Framework conventions. By convention, EF will reocognize the fact that your Navigation property is called Foo and your ForeignKey will be called FooId.
However, the real problem is that you are attempting to create a 1:1 association between two entities and EF does not support associations like this.
EF only supports 1:1 associations with shared primary keys, that is where both tables have the same primary key and one table's PK is a FK to the other table's PK.
If you think about this, it makes sense. There is no native 1:1 relationship in SQL that does not have a shared primary key. If you add a FK in one table to the other, it creates a 1:Many. You can simulate a 1:1 by creating a unique constraint on the FK but EF does not support constraints.
Looking at your model. Do you really want a 1:1 anyways? Can an employee really only have a single absence? Ever? Probably not. You probably want Absence to be a 1:Many. So remove AbsenceId and change Absence to:
public virtual List<Absence> Absences { get; set; }

EF Code First Table Splitting issue when used with many-to-many relationships

I am trying to use "Table Splitting" in EF Code First and it works fine when I use one of the entities relationships of type one-to-many, but as soon as I use it in a many-to-many relationship I start getting this error:
(27,6) : error 3018: Problem in mapping fragments starting at line
27:Foreign key constraint 'Itinerary_Addresses_Target' from table
ItineraryAddress (Address_Id) to table User (Id): The columns of table
ItineraryAddress are mapped to AssociationSet Itinerary_Addresses's
End Itinerary_Addresses_Target but the key columns of table User are
not mapped to the keys of the EntitySet Addresses corresponding to
this End.
Here is the code (https://github.com/jorgef/tablesplitting):
Table Splitting
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public int Id { get; set; }
public string Street { get; set; }
public User User { get; set; }
}
modelBuilder.Entity<User>().ToTable("Users");
modelBuilder.Entity<Address>().ToTable("Users");
modelBuilder.Entity<User>().HasRequired(u => u.Address).WithRequiredPrincipal(a => a.User);
One-To-Many Relationship
public class Itinerary
{
public int Id { get; set; }
public ICollection<Address> Addresses { get; set; }
}
With the previous code, everything works like a charm, the problem is when introducing a many-to-many relationship
Many-To-Many Relationship
public class Address
{
...
public ICollection<Itinerary> Itineraries { get; set; }
}
After adding that relationship, the app raises the mentioned exception on runtime. I managed to save to disk the generated edmx just in case that helps, here is the link: https://github.com/jorgef/tablesplitting/blob/master/TableSplitting/SavedModel.edmx
If somebody wants to play with the two versions of the app, the one working and the one not working, I have two different commits:
Table splitting working with one to many relationship
Table splitting not working with many to many relationship
Any ideas or thougths are appreciated.
Many thanks!
In case anyone else has encountered this issue:
This was a bug in Entity Framework. The bug has since been fixed, and upgrading to EF6 will resolve the issue. See the following discussion for details:
https://entityframework.codeplex.com/workitem/1385
A related bug with table splitting validation has also been discovered and fixed, planned to be released with EF6.1.0:
https://entityframework.codeplex.com/workitem/1611

Multiple references from one entity to another, database schema, Entity Framework

Read it several times and understood that i'm not so good in asking questions correctly. Made some corrections to the question.
I have 2 classes in my app: Contractor and ContractorGroup.
Each Contractor has a parent Contractor. Parent Contractor can see only it's dependants. And can not see dependants of it's dependants. Only one level of visibility.
Parent Contractor can group it's dependant Contractors into ContractorGroup.
So, many dependant Contractors for one ContractorGroup (many-to-one)
Contractor has a NavProperty ContractorGroup, the group to which it belongs. Each Contractor can be ONLY IN ONE ContractorGroup.
Example:
I'm the parent Contractor and have 5 dependant Contractors and i want to group first 2 of them into 1stContractorGroup and last 3 to 2ndContractorGroup.
So, options to implement:
First: I can not to include FK(VisibleToContractorId - id of my parent Contractor) in ContractorGroup which connects each of 2 groups to parent Contractor.
In this case i can do query similar to:
var ContractorGroupsToDispalayForParentContractor =
context.ContractorGroups.Where(p => p.Contractors.All(p => p.Parent == ParentContractor));
In other words: "Find all groups which consist of contractors with parent == ParentContractor"
In this option everything works fine. DbSchema is simple and clear. But i dont like the query.
Second: Or i can introduce FK(VisibleToContractorId). So one parent Contractor has many ContractorGroups which consist of dependant Contractors. Then i have a simplier and more robust query:
var ContractorGroupsToDispalayForParentContractor =
context.ContractorGroups.Where(p => p.VisibleToContractor == ParentContractor);
This query i like. But EF introduces strange DbColumn which is ALWAYS null. >:-E
Short db schema:
Table("Contractor")
ContractorId = PK
ContractorGroupId = FK
ContractorGroup_ContractorGroupId = FK <--- This One
Table("ContratorGroup")
ContractorGroupId = PK
VisibleToContractorId = FK
My domain Classes and EntityConfiguration:
public class Contractor : IObjectWithState
{
[Key]
public virtual int ContractorId { get; set; }
public virtual Contractor Parent { get; set; }
public virtual int? ParentId { get; set; }
public virtual ContractorGroup ContractorGroup { get; set; }
public virtual int? ContractorGroupId { get; set; }
public virtual ICollection<ContractorGroup> GroupsVisible { get; set; }
}
public class ContractorGroup : IObjectWithState
{
[Key]
public virtual int ContractorGroupId { get; set; }
public virtual Contractor VisibleToContractor { get; set; }
public virtual int? VisibleToContractorId { get; set; }
public virtual ICollection<Contractor> Contractors { get; set; }
}
Entity configurations (only in ContractorGroupConfiguration):
HasMany(p => p.Contractors).WithOptional(p=>p.ContractorGroup);
HasOptional(p => p.VisibleToContractor).WithMany(
p=>p.GroupsVisible).HasForeignKey(p=>p.VisibleToContractorId);
Is it a bug in EF?
What implementation First or Second of domain model would you prefer?
Thanks.
The point is that you need to tell EF that the associations ContractorGroup.VisibleToContractor and Contractor.GroupsVisible are no independent associations (with two FK fields), but are two parts of a bidirectional association. Replace the entity configurations by:
class ContractorConfiguration : EntityTypeConfiguration<Contractor>
{
public ContractorConfiguration()
{
HasMany(c => c.GroupsVisible).WithOptional(g => g.VisibleToContractor);
}
}
Without this, EF creates the field ContractorGroup_ContractorGroupId as a FK for Contractor.GroupsVisible on top of the FK VisibleToContractorId.