Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I define Foreign Key Optional Relationships in FluentAPI/Data Annotations with the Entity Framework?

Tags:

I have a (sample) application with the following code:

public class Posts {      [Key]     [Required]     public int ID { get; set; }      [Required]     public string TypeOfPost { get; set; }      public int PollID { get; set; }     public virtual Poll Poll { get; set; }      public int PostID { get; set; }     public virtual Post Post { get; set; }  } 

Basically, I don't know if there is a better way of doing this, but, I have a list of Posts, and, people can choose if it is a Poll or a Post, As Entity Framework doesn't work with Enums, I just store it as a string in TypeOfPost and then in the application, I programmatically query either Poll or Post based on the value of TypeOfPost.

I don't think there is anyway of setting "Only one required" or similar, so, I handle all the checking and stuff in the application. (If anyone knows a better way, please say!).

Anyway, the problem is, I can get this working fine by going in to SQL Management Studio and manually editing the schema to allow nulls - but, I just can't work out how to do this in the FluentAPI, and need some help.

I have tried both of the following:

modelBuilder.Entity<Post>()     .HasOptional(x => x.Poll).WithOptionalDependent();  modelBuilder.Entity<Post>()     .HasOptional(x => x.Poll).WithOptionalPrincipal(); 

The first one seems to create an additional column in the database that allows nulls, and the second one doesn't appear to do anything.

I believe the first one is the one I need, but, I need to use it in combination with [ForeignKey] in the Post Class. If I am correct here, Should the [ForeignKey] go on the virtual property, or the ID of the property?

In addition, what is the actual difference between WithOptionalDependent and WithOptionalPrincipal? - I have read on MSDN, but, I really do not understand the difference.

like image 311
Wil Avatar asked Nov 05 '11 11:11

Wil


People also ask

How do you set a foreign key in an entity?

The [ForeignKey(name)] attribute can be applied in three ways: [ForeignKey(NavigationPropertyName)] on the foreign key scalar property in the dependent entity. [ForeignKey(ForeignKeyPropertyName)] on the related reference navigation property in the dependent entity.

Does Entity Framework support foreign keys?

When you change the relationship of the objects attached to the context by using one of the methods described above, Entity Framework needs to keep foreign keys, references, and collections in sync.

How do I add a foreign key in Fluent API?

You can then configure foreign key properties by using the HasForeignKey method. This method takes a lambda expression that represents the property to be used as the foreign key.


2 Answers

The reason it wasn't allowing nulls because the following:

public int PollID { get; set; } public virtual Poll Poll { get; set; }  public int PostID { get; set; } public virtual Post Post { get; set; } 

should have been

public int? PollID { get; set; } public virtual Poll Poll { get; set; }  public int? PostID { get; set; } public virtual Post Post { get; set; } 
like image 196
Wil Avatar answered Oct 20 '22 17:10

Wil


I would probably try to create the two one-to-one relationships as optional:required because a Poll must have a reference to Posts and a Post also must have a reference to Posts:

modelBuilder.Entity<Posts>()     .HasOptional(x => x.Post)     .WithRequired();  modelBuilder.Entity<Posts>()     .HasOptional(x => x.Poll)     .WithRequired(); 

This makes Posts automatically the principal in the relationship and Post or Poll the dependent. The principal has the primary key in the relationship, the dependent the foreign key which is also the primary key at the same time in Post/Poll table because it is a one-to-one relationship. Only in a one-to-many relationship you would have a separate column for the foreign key. For a one-to-one relationship you also have to remove the foreign key columns PostId and PollId because Posts refers through its primary key to the Post and Poll.

An alternative approach which seems to be appropriate in your model is inheritance mapping. Then the model would look like this:

public abstract class BasePost  // your former Posts class {     public int ID { get; set; }     public string UserName { get; set; } }  public class Post : BasePost {     public string Text { get; set; }     // other properties of the Post class }  public class Poll : BasePost {     // properties of the Poll class } 

You don't need the TypeOfPost then anymore because you can filter the two concrete types using the OfType LINQ operator, for example:

var x = context.BasePosts.OfType<Post>()     .Where(p => p.UserName == "Jim")     .ToList(); 

This would select all posts of a particular user but not the polls.

You have to decide then which kind of inheritance mapping you want to use - TPH, TPT or TPC.

Edit

To get a one-to-many relationship you can specify the following mapping in Fluent API:

modelBuilder.Entity<Posts>()     .HasOptional(x => x.Post)     .WithMany()     .HasForeignKey(x => x.PostID);  modelBuilder.Entity<Posts>()     .HasOptional(x => x.Poll)     .WithMany()     .HasForeignKey(x => x.PollID); 

The foreign key properties must be nullable (int?) for this as you already found. Because the naming of your foreign key properties follows the naming convention EF uses for mapping you can omit the Fluent mapping altogether. It would only be required if you had unconventional names (like PostFK or something). You could then also use data annotations ([ForeignKey(...)] attribute) instead of Fluent API.

like image 27
Slauma Avatar answered Oct 20 '22 17:10

Slauma