4

I'm in a situation where one table has two One-None/One Relationships. How do I implement this using Entity Framework Code-First?

I've seen the following links

Where essentially it's said that the dependent end needs to have a primary key that is the same as that of the principal end. But I'm weary of implementing this with more than one One-None/One Relationship without confirmation and proper knowledge of what's going on. Furthermore I am not sure how to construct statements as it does not have a conventional Foreign Key.

I've also seen Configuring multiple 1 to 0..1 relationships between tables entity framework which confused me beyond recognition.

See below for the relevant part of my DB Diagram: So Essentially, a Player shouldn't be saved without a DKImage, similarly a Product shouldn't be saved without a DKImage.

Below is the code for Models: Players, Products, DKImages (I know it's not correct, I only implemented it this way so I can generate the database and show the diagram)

Player

public enum Positions { PG, SG, SF, PF, C }

public class Player
{
    [Key]
    [ForeignKey("Images")]
    public int PlayerID { get; set; }

    [Required]
    public string PlayerName { get; set; }

    [Required]
    public string PlayerLastName { get; set; }

    [Required]
    public int PlayerAge { get; set; }

    [Required]
    public Positions Position { get; set; }

    [Required]
    public bool Starter { get; set; }

    [Required]
    [Display(Name = "Active / Not Active")]
    public bool Status { get; set; }

    //Foreign Keys
    public int PlayerStatsID { get; set; }

    //Navigation Properties
    [ForeignKey("PlayerStatsID")]
    public virtual IQueryable<PlayerStats> PlayerStats { get; set; }
    public virtual DKImages Images { get; set; }
}

DKImages

public class DKImages
{
    [Key]
    public int ImageID { get; set; }
    [Required]
    public string ImageURL { get; set; }
    [Required]
    public DateTime DateUploaded { get; set; }

    //Foreign Keys
    [Required]
    public int CategoryID { get; set; }

    //Navigation Properties
    public virtual Products Products { get; set; }
    public virtual Category Category { get; set; }
    public virtual Player Player { get; set; }
}

Products

public class Products
{
    [ForeignKey("Images")]
    [Key]
    public int ProductID { get; set; }
    [Required]
    public string ProductName { get; set; }
    [Required]
    public DateTime DateAdded { get; set; }

    //Foreign Keys
    [Required]
    public int ProductTypeID { get; set; }

    //Navigation Properties
    [ForeignKey("ProductTypeID")]
    public virtual ProductType ProductType { get; set; }
    public virtual DKImages Images { get; set; }
}

Edit

I have been told that the code above is correct. If so then how do I create CRUD LINQ Statements (Or any method of constructing CRUD statements for that matter) with the above code.

James9oo0
  • 159
  • 12
  • What is concerning you? Players and Products are separate tables. Just they won't have their own (identity) PK, but will use subset of the DKImages PK. – Ivan Stoev May 29 '18 at 12:38
  • What is the problem ? Are you just asking if what you did is good at this point ? Well... Indeed... It is the right way. If you have a problem with this code then state it in your question. if there is no problem, delete your question and keep up the good work. – Antoine Pelletier May 29 '18 at 14:24
  • @AntoinePelletier This is correct?? How do I associate an Image with a Player/Product to add to the database? I assumed this was incorrect because there is no foreign key with which I can associate an Image with a Player/Product. – James9oo0 May 29 '18 at 14:34
  • @IvanStoev Could you elaborate on your answer using some kind of metaphor? I didn't really understand that. – James9oo0 May 29 '18 at 14:35
  • Take a look at [Associations in EF Code First: Part 3 – Shared Primary Key Associations](https://weblogs.asp.net/manavi/associations-in-ef-4-1-code-first-part-3-shared-primary-key-associations). Hopefully the author explains it much better than me :) My point was that I see no problem with PK of a table being at the same time a FK to another table. – Ivan Stoev May 29 '18 at 14:55
  • You have already used the `ForeignKey` key word in your model, this is how you define a foreign key. Now how do you want it ? A categories can have one or zero DKImage, and a DKImage can have one or zero player ? Or is it the other way around ? This should also be part of your question – Antoine Pelletier May 29 '18 at 15:16
  • @AntoinePelletier "So Essentially, a Player shouldn't be saved without a DKImage, similarly a Product shouldn't be saved without a DKImage." I've written this in the question – James9oo0 May 29 '18 at 15:32

2 Answers2

5

What you want here is referred to as polymorphic associations: several entities having child entities of one type. They're typically used for comments, remarks, files etc. and usually applied to 1:n associations. In your case there are polymorphic 1:1 associations. Basically these associations look like this (using a bit more generic names):

How to implement them?

Entity Framework 6

In EF6 that's problem. EF6 implements 1:1 associations as shared primary keys: the child's primary key is also a foreign key to its parent's primary key. That would mean that there should be two FKs on Image.ID , one pointing to Person.ID and another one pointing to Product.ID. Technically that's not a problem, semantically it is. Two parent entities now own the same image or, stated differently, an image should always belong to two different parents. In real life, that's nonsense.

The solution could be to reverse the references:

enter image description here

But now there's another problem. The entity that's referred to is named the principal, the other entity is dependent. In the second diagram, Image is the principal, so in order to create a Person, its image must be inserted first and then the person copies its primary key. That's counter-intuitive and most likely also impractical. It's impossible if images are optional.

Nevertheless, since in your case you want images to be required let me show how this association is mapped in EF6.

Let's take this simple model:

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual Image Image { get; set; }
}

public class Product
{
    public int ID { get; set; }
    public string Name { get; set; }
    public virtual Image Image { get; set; }
}

public class Image
{
    public int ImgID { get; set; } // Named for distinction
    public string Url { get; set; }
}

The required mapping is:

modelBuilder.Entity<Image>().HasKey(pd => pd.ImgID);
modelBuilder.Entity<Person>().HasRequired(p => p.Image).WithRequiredDependent();
modelBuilder.Entity<Product>().HasRequired(p => p.Image).WithRequiredDependent();

As you see, Image has two required dependents. Perhaps that's better than two required parents, but it's still weird. Fortunately, in reality it's not a problem, because EF doesn't validate these associations. You can even insert an image without a "required" dependent. I don't know why EF doesn't validate this, but here it comes in handy. The part WithRequiredDependent might as well have been WithOptional, it doesn't make a difference for the generated data model, but at least this mapping conveys your intentions.

An alternative approach could be inheritance. If Person and Product inherit from one base class this base class could be the principal in a 1:1 association with Image. However, I think this is abusing a design pattern. People and products have nothing in common. From a design perspective there's no reason for them to be part of one inheritance tree.

Therefore, in EF6 I think the most feasible solution is to use the third alternative: separate image tables per entity.

Entity Framework Core

In EF-core 1:1 associations can be implemented the EF6 way, but it's also possible to use a separate foreign key field in the dependent entity. Doing so, the polymorphic case looks like this:

The Image class is different:

public class Image
{
    public Image()
    { }
    public int ImgID { get; set; }
    public int? PersonID { get; set; }
    public int? ProductID { get; set; }
    public string Url { get; set; }
}

And the mapping:

modelBuilder.Entity<Person>().Property(p => p.ID).UseSqlServerIdentityColumn();
modelBuilder.Entity<Person>()
    .HasOne(p => p.Image)
    .WithOne()
    .HasForeignKey<Image>(p => p.PersonID);
modelBuilder.Entity<Product>().Property(p => p.ID).UseSqlServerIdentityColumn();
modelBuilder.Entity<Product>()
    .HasOne(p => p.Image)
    .WithOne()
    .HasForeignKey<Image>(p => p.ProductID);
modelBuilder.Entity<Image>().HasKey(p => p.ImgID);

Watch the nullable foreign keys. They're necessary because an image belongs to either a Person or a Product. That's one drawback of this design. Another is that you need a new foreign key field for each new entity you want to own images. Normally you want to avoid such sparse columns. There's also an advantage as compared to the EF6 implementation: this model allows bidirectional navigation. Image may be extended with Person and Product navigation properties.

EF does a pretty good job translating this into a database design. Each foreign key has a filtered unique index, for example for Person:

CREATE UNIQUE NONCLUSTERED INDEX [IX_Image_PersonID] ON [dbo].[Image]
(
    [PersonID] ASC
)
WHERE ([PersonID] IS NOT NULL)

This turns the association into a genuine 1:1 association on the database side. Without the unique index it would be a 1:n association from the database's perspective.

Community
  • 1
  • 1
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • Now I want to use EF Core, the solution that it offers seems to be more robust. The solution you said would be most feasible: "separate image tables per entity." Does that mean creating a new Image table for each entity? – James9oo0 Jun 15 '18 at 06:43
  • Yes, it does. I've found it to be the most practical solution for polymorphic associations using EF6. It allows bidirectional navigation and hard foreign keys (I didn't even mention an alternative with "soft" foreign keys). If ever you need to query all images, make a view with `UNION`. – Gert Arnold Jun 15 '18 at 07:25
  • Since the Image table isn't awfully complex I think the most practical solution would be to simply add a PlayerImageURL column and ProductImageURL column to the appropriate tables. My solution was the first method mentioned in your answer. So it would have caused two parents to have the same image. – James9oo0 Jun 15 '18 at 10:08
1

An exemple in your Player table would be this :

public class Player
{

    // All the rest you already coded

    [Required]
    public int ImageID

    [ForeignKey("ImageID")]
    public virtual DKImage DKImage {get;set;}
}

This would force a player to have a DKImage, but as said in the comments, this create a one to many relationship.

Another way out would be to put all Player fields into the DKImage table, those fields would be null if there is no player associated to this DKImage.

Edit for 1 to 1..0

Ivan Stoev's link got some pretty interesting insight on how to accomplish this :

https://weblogs.asp.net/manavi/associations-in-ef-4-1-code-first-part-3-shared-primary-key-associations

It seems like you will have to put a bit more code in your class :

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<DKImage>().HasOptional(t => t.Player).WithRequired();

}

If the tutorial is correct, this would read as :

"DKImage entity has an optional association with one Player object but this association is required for Player entity".

I have not tested it yet.

Antoine Pelletier
  • 3,164
  • 3
  • 40
  • 62
  • Note that this creates one-to-many relationship (`DKImage` 1 <-> 0..N `Player`) while the requirement was 1 <-> 0..1 – Ivan Stoev May 31 '18 at 06:47
  • @IvanStoev Yeah I just tested the code now, I've unmarked as answer as a result – James9oo0 May 31 '18 at 06:55
  • If you want a DKImage to have no more than one player... i would just put the field of Player table into DKImage table and they would be null if there is no player associated. But your question remains very interesting, is it even possible to make a 1 to 1..0 relationship in entity framework ? I'm beginning to doubt. – Antoine Pelletier May 31 '18 at 13:26
  • @James9oo0 I have updated my answer, if this is not the solution, tell me, and I hope you won't mind if one day I put a bounty on your question ? – Antoine Pelletier May 31 '18 at 14:00
  • @AntoinePelletier I don't know what the bounty is but go for it! I haven't had the time to test the code but I'll let you know as soon as I am able. – James9oo0 Jun 01 '18 at 06:32
  • @James9oo0 Have you tried the code yet ? If this is not the solution, i'll be glad to put a bounty on this question. A bounty on your question is going to increase visibility and bring more EF experts that are going to look at your question, this way you are sure to get a true answer – Antoine Pelletier Jun 05 '18 at 14:39
  • @AntoinePelletier Apologies I haven't had the time, I've been working on another more prioritized project. I'll let you know. – James9oo0 Jun 06 '18 at 10:36
  • @James9oo0 Ok I was just wondering, take all the time you need ;) – Antoine Pelletier Jun 06 '18 at 13:41
  • @James9oo0 Bounty activated. Your question is going to draw a lot of attention now. – Antoine Pelletier Jun 12 '18 at 17:15
  • @AntoinePelletier With all my respect, I don't find any interesting in this question. Technically 1 to 1 relationship cannot be enforced with relational database constrains, so 1 to 0..1 is the standard relationship. And is standardly and best supported by EF6 vis shared PK association. If you take the OP code and remove the irrelevant properties, you'll end up with the desired relationships w/o any fluent configuration, thanks to the `ForeignKey` attribute put on PKs of the 0..1 ends (`Player` and `Products`). The OP code and the shown diagram are indeed correct, there is nothing to add. – Ivan Stoev Jun 12 '18 at 19:34
  • @AntoinePelletier I've run into the same problem as before. How am I to associate a picture with a Player? There is no FK for me to indicate the relationship. – James9oo0 Jun 13 '18 at 07:10
  • @IvanStoev I take full offence :P – James9oo0 Jun 13 '18 at 07:17
  • @AntoinePelletier Okay I finally figured out how to do it I'm going to post my answer. If you could remove the bounty :) – James9oo0 Jun 13 '18 at 08:23
  • @James9oo0 Lol well... there is no turning back, YOU are going to be awarded the bounty, so +100 rep for you once you answer ;) my pleasure, just upvote my answer IF it has been useful to you somehow – Antoine Pelletier Jun 13 '18 at 15:02
  • @IvanStoev I don't mind spending rep, I know you have a lot of experience with EF. I always think my answers aren't the bests so I was expecting someone like you to make it better. Since James will answer himself, save your time. I wish i could give YOU that rep but hey, you already have plenty ;) – Antoine Pelletier Jun 13 '18 at 15:08
  • @AntoinePelletier Lol, I didn't mind applying for a bounty :) If I wanted to answer, be sure I would have, with or without the bounty. But as you see I'm just commenting :) Cheers. – Ivan Stoev Jun 13 '18 at 15:46
  • @AntoinePelletier Lol I feel like I'm cheating though. I want to get reputation from actually doing useful things. I suppose we'll wait for someone to give use a detailed answer on why and how something works :P. – James9oo0 Jun 14 '18 at 06:49
  • @James9oo0 If you don't mind, i'd like to see your answer, it can be edited by another experienced user later. The thing is, if no one answer within the time limit, my offered rep will just vanish, I cannot take it back... I would not like to see 100 rep points disappear. There is no cheating here, personally I think you asked a great question. Take it this way : you were good AND lucky ;) – Antoine Pelletier Jun 14 '18 at 14:57