Entity Framework Code First Relationship - entity-framework

I am trying to figure out how to make this work in EF. I have two entities Employee and User. I need to make it so the User has an optional mapping to the Employee. If there is a mapping, then it would be 1:1.
Basically this system can be accessed by employees and also by outside vendors, but I want one table to manage logons: Users.
How do I define this realtionship in EF with fluid configurations?

You just need to set simple fluent configuration:
modelBuilder.Entity<User>()
.HasOptional(u => u.Employee)
.WithRequired(e => e.User);
or in reverse order:
modelBuilder.Entity<Employee>()
.HasRequired(e => e.User)
.WithOptional(u => u.Employee);
EF will use Employess's PK as FK to user - that is mandatory requirement for EF to correctly use one-to-one relation. In case of Data annotation it is enough to mark Employee's PK with ForeignKey attribute pairing it with User navigation property:
public class Employee {
[Key, ForeignKey("User")]
public int Id { get; set; }
public virtual User User { get; set; }
}

public class User
{
...
public virtual Employee Employee { get; set; }
}
This tutorial may help.

Related

Configuring one-to-many and one-to-one relationship in Entity Framework Core

I'm quite new to Entity Framework and am picking it up with the Core version.
I'm trying to understand how to customise model relationships.
My basic model is that I have a Company entity, and a Contact entity. A Company can have many Contacts. A company can a KeyContact, which must be one of the associated contacts, but is not required.
Thus there is a One to Many relationship, but also a One to One relationship. I've tried to implement this as below (removed most other fields for clarity);
public class Company
{
public int Id { get; set; }
public int? KeyContactId { get; set; }
public ICollection<Contact> Contacts { get; set; }
public Contact KeyContact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public int CompanyId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Company Company { get; set; }
}
It fails to add this migration with the message;
Unable to determine the relationship represented by navigation property 'Company.Contacts' of type 'ICollection'. Either manually configure the relationship, or ignore this property from the model.
I can kinda see why it's complaining about this, but I'm not sure if there's a way with the model builder I can configure this, or whether it's an invalid pattern. My model builder is currently just basic;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Company>().ToTable("Company");
modelBuilder.Entity<Contact>().ToTable("Contact");
}
I know I could just have a flag to say IsKeyContact in the contact table, but I like the idea of having the navigation property in the company entity. So I'm wondering how sugary Entity can be.
Any help much appreciated.
Thanks,
Nick
The exception is avoided by adding the following line to the OnModelCreating method:
modelBuilder.Entity<Company>().HasMany(p => p.Contacts).WithOne(d => d.Company).HasForeignKey(d => d.CompanyId);
This configures the Company.Contacts-Contact.Company relation. By default, the Company.KeyContact relation is configured as if the following line would be within the OnModelCreating method:
modelBuilder.Entity<Company>().HasOne(e => e.KeyContact).WithMany().HasForeignKey(e => e.KeyContactId);
Hence a Contact can be the KeyContact of more than one Company.
In order to ensure that a Contact can be the KeyContact of at most one Company the Company.KeyContact relation could be configured by the following line within the OnModelCreating method:
modelBuilder.Entity<Company>().HasOne(e => e.KeyContact).WithOne().HasForeignKey<Company>(e => e.KeyContactId);
But note: This will not ensure that the KeyContact is a member of the Contacts.

EF 0..1 to 0..1 relationship without shared primary key

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.

EF Code First Fluent API - Cascade Delete

I have two models:
public class User
{
.....
public virtual UserProfile UserProfile { get; set;}
}
public class UserProfile
{
.....
public virtual User User { get; set;}
}
The User is the master table and the relation is one to one. One user has only one UserProfile.
How can I define the relationship between User and UserProfile using EF CodeFirst Fluent API in such a way that when I delete one user from User table the user profile from Userprofile is also deleted?
Use WillCascadeOnDelete
modelBuilder.Entity<UserProfile>()
.HasKey(c => c.Id)
.HasRequired(c => c.User)
.WithRequiredDependent(c => c.UserProfile)
.WillCascadeOnDelete(true);

Entity Framework Code First: One-to-Many and Many-to-Many relationships to same table

I have a User model and a Event model in my project. The Event has a creator(User) and has participant(Users) so Event has a one-to-many relationship with User and also a many-to-many relationship to the same table.
I had first the one-to-many relationship like this:
Public class Event
{
...
public int CreatedById { get; set; }
public virtual User CreatedBy { get; set; }
...
}
Then when I added the many-to-many relationship the migration doesn't generate the many to many relationship:
Public class User
{
...
public virtual ICollection<Event> Events { get; set; }
...
}
Public class Event
{
...
public int CreatedById { get; set; }
public virtual User CreatedBy { get; set; }
public virtual ICollection<User> Users { get; set; }
...
}
If I remove the one-to-many relationship then the migration generates the many-to-many relationship successfully.
Is there a way to do this with only data annotations?
EF doesn't know where User.Events has to be mapped to. It could be Event.CreatedBy or it could be Event.Users. Both would result in a valid model. You must give EF a little hint what you want by applying the [InverseProperty] attribute:
public class User
{
...
[InverseProperty("Users")]
public virtual ICollection<Event> Events { get; set; }
...
}
With Code First Approach, I would always recommend to use fluent API rather than using DataAnnotations, Which uses some conversions automatically.
This way, you'll know what exact configuration you've made.
If I were you, here is what i would use :
public class EventMap : EntityTypeConfiguration<Event>
{
public EventMap()
{
this.HasRequired(m => m.CreatedBy) // envent must have a creator
.WithMany() // a user can have 0,1 or more events created by him
.HasForeignKey(m => m.CreatedById) // specify property to be used as FK
.WillCascadeOnDelete(true); // delete all events created by user if that specific user is deleted
this.HasMany(m=>m.Users) // an event can have 0,1 or more participants
.WithMany(m=>m.Events) // a user can be a participant in 0,1 or more events
.Map(m => m.MapLeftKey("EventId").MapRightKey("UserId")); // this will generate intermediate table to hold participant information - dbo.EventUser with EventId & UserId
// Cascade Delete is always true for Many to Many mapping. however, it doesn't delete entry in other table, it deletes entry in Joined Table only.
}
}

How do you ensure Cascade Delete is enabled on a table relationship in EF Code first?

I would like to enable CASCADE DELETE on a table using code-first. When the model is re-created from scratch, there is no CASCADE DELETE set even though the relationships are set-up automatically. The strange thing is that it DOES enable this for some tables with a many to many relationship though, which you would think it might have problems with.
Setup:
Table A <- Table B.
Table B's FK points to Table A's PK.
Why would this not work?
Possible reason why you don't get cascading delete is that your relationship is optional. Example:
public class Category
{
public int CategoryId { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public Category Category { get; set; }
}
In this model you would get a Product table which has a foreign key to the Category table but this key is nullable and there is no cascading delete setup in the database by default.
If you want to have the relationship required then you have two options:
Annotations:
public class Product
{
public int ProductId { get; set; }
[Required]
public Category Category { get; set; }
}
Fluent API:
modelBuilder.Entity<Product>()
.HasRequired(p => p.Category)
.WithMany();
In both cases cascading delete will be configured automatically.
If you want to have the relationship optional but WITH cascading delete you need to configure this explicitely:
modelBuilder.Entity<Product>()
.HasOptional(p => p.Category)
.WithMany()
.WillCascadeOnDelete(true);
Edit: In the last code snippet you can also simply write .WillCascadeOnDelete(). This parameterless overload defaults to true for setting up cascading delete.
See more on this in the documentation
modelBuilder
.Entity<Product>()
.HasRequired(p => p.Category)
.WithMany(x => x.Products)
.WillCascadeOnDelete(true);