EF Code First Optional Navigation - entity-framework

I have the following POCO objects:
public class Chat
{
public int ChatID { get; set; }
public virtual Audio ChatAudio { get; set; }
public virtual Video ChatVideo { get; set; }
}
public class Audio
{
public int AudioID { get; set; }
public int ChatID { get; set; }
public Chat Chat { get; set; }
public DateTime Recorded { get; set; }
}
public class Video
{
public int VideoID { get; set; }
public int ChatID { get; set; }
public Chat Chat { get; set; }
public DateTime Recorded { get; set; }
}
DBA has the following tables setup
-Chat-
ChatID
-Audio-
AudioID
ChatID <-- FK, Unique Index
Recorded
-Video-
VideoID
ChatID <-- FK, Unique Index
Recorded
So this is a 1-to-optional relationship. He doesn't want to create a nullable column for Audio or Video on the Chat table. How can I force this to work in EF Code First? The only way I've gotten this to work is to make those navigational properties collections and defined HasMany(). I do not want that. I want to be able to do:
if(Chat.Audio == null || Chat.Video==null)
{
// Do stuff
}
I appreciate any suggestions you may have.

No, you cannot map this model to your database schema with Entity Framework (no matter which version up to current version 4.2).
The main reason is that EF doesn't support unique key constraints, which means: mapping a reference navigation property to a column in the database which has a unique key constraint. EF will consider such a FK column in the database always as not unique and doesn't see the unique key constraint. So, the ends of the two associations in Chat must be collections. Or you could not expose them in your model, but internally EF will consider them always as "many"-ends.
The only true one-to-one relationship which EF supports are shared primary key relationships. It would mean that you don't have a ChatId column in the Audio and Video table but instead the primary key columns AudioId and VideoId are foreign key columns to the Chat table at the same time. Obviously the primary key columns cannot be all autogenerated identities in the database to make this kind of mapping possible.
A great description for both one-to-one mapping strategies and a comparison of their benefits and limitations can be found here:
Shared Primary Key Associations
One-to-One Foreign Key Associations

You could "cheat" and configure the the relationship as one to many, enforcing the one to one in the object model (also, in the datacontext initializer you can manually add the unique constraint to the database).
It would basically look something like this
public class Chat
{
public int ChatID { get; set; }
protected virtual ICollection<Audio> HiddenAudioCollection{get;set;}
public Audio ChatAudio
{
get{return HiddenAudioCollection.FirstOrDefault();}
set
{
if(HiddenAudioCollection==null)
HiddenAudioCollection=new List<Audio>();
if(HiddenAudioCollection.Any())
{
//decide if you throw an exception or delete the existing Audio
}
HiddenAudioCollection.Add(value);
}
}
//Do the same with ChatVideo
}

Another option, since the Video and Audio "belong" to the chat is to use a complex structure. Don't give the Video and Audio their own Id, they will be mapped to the chat table. I know that some DBA's hate this idea because it's optional data. But technically it's no different than storing an Address in a Customer table directly. It all depends on how normalized you want your database to be.

Let me make sure I understand this correctly:
Chat has zero or one Audio, and zero or one Video.
Audio, if it exists, belongs to exactly 1 Chat.
Video, if it exists, belongs to exactly 1 Chat.
Is this correct? If so, would your DBA mind a schema like this?
-Chat-
ChatID
-Audio-
ChatID <-- FK, Primary Key
Recorded
-Video-
ChatID <-- FK, Primary Key
Recorded
If so, I think you can do this:
public class Chat
{
public int ChatID { get; set; }
public virtual Audio ChatAudio { get; set; }
public virtual Video ChatVideo { get; set; }
}
public class Audio
{
public int ChatID { get; set; }
public virtual Chat Chat { get; set; }
public DateTime Recorded { get; set; }
}
public class Video
{
public int ChatID { get; set; }
public virtual Chat Chat { get; set; }
public DateTime Recorded { get; set; }
}
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Audio>().HasKey(a => a.ChatID);
modelBuilder.Entity<Video>().HasKey(a => a.ChatID);
var chat = modelBuilder.Entity<Chat>();
chat
.HasOptional(c => c.ChatAudio)
.WithRequired(a => a.Chat);
chat
.HasOptional(c => c.ChatVideo)
.WithRequired(a => a.Chat);
...
}

Related

EF Core 3.1 - How to use an already combined PK for another combined PK?

I´m currently learning to use EF and I have the following relationships:
An Alert has 1 to n occurences.
Each occurence can have 0 to n values (additional information).
public class Alert
{
// PK
public int AlertId { get; set; }
// Attributes
public int CurrentAlertLevel { get; set; }
public DateTime TimeRaised { get; set; }
public DateTime TimeLastRaised { get; set; }
// Some other attributes ommitted...
// Relations
public ICollection<AlertOccurrence> Occurrences { get; set; }
}
public class AlertOccurrence
{
// Relations which are part of the primary key
public int AlertId { get; set; }
// Attributes
public int Ordinal { get; set; }
// some ommited attributes
// Relations
public ICollection<AlertDetailValue> AlertDetailValues { get; set; }
}
public class AlertDetailValue
{
public int AlertDetailValueId { get; set; }
public int Order { get; set; }
public string Value { get; set; }
}
In the DB Context OnModelCreating I´m setting the combined PK for AlertOccurence:
modelBuilder.Entity<AlertOccurrence>().HasKey(ao => new {ao.AlertId, ao.Ordinal});
While it seems that this is working - what I would actually like to archive is the same relationship without the need to have the AlertDetailValueId as PK. The table that EF generates also includes AlertOccurrenceAlertId and AlertOccurrenceOrdinal which seems a waste of space to me.
So what I would like to do is:
Have a combined primary key for AlertDetailValue consisting of AlertDetailValue.Order and the (already combined) PK of AlertOccurence instead of the "artificial" AlertDetailValueId. Is that even possible ?
Part of my problem might be that the PK defined using the fluent api is not part of the data classes. So probably another question to ask would be: Is there a way to use a key defined in fluent api in a entity class ?
Or do I need to include AlertOccurrenceAlertId and AlertOccurrenceOrdinal in my entity class AlertDetailValue - but how do I link them then ?
As I said I´m still trying to get my head around EF so while there might be better ways to do this I´m interested in this special kind of relation / combined(combined) PK even if it might be academic... Any help would be highly appreciated.
Trying to explain what I try to do and what my problem is - and taking a good shower - helped me to ask different questions to google and focus more on the foreign key.
It´s not that I did not try to google it before... I just asked the wrong questions..
So I found this:
Mapping composite foreign key to composite primary key where the foreign key is also a primary key
(While trying the new approch #atiyar also hinted on the missing foreign key...)
My new solution was to change AlertDetailValue to intentionally include the parts that the Occurence PK is build of:
public class AlertDetailValue
{
// relations will be set up in fluent api in OnModelCreating of db context
public int AlertOccurenceAlertId { get; set; }
public int AlertOccurenceOrdinal { get; set; }
public int Order { get; set; }
public string Value { get; set; }
}
And then to tell EF that there is a combined PK and also a combined foreign key:
modelBuilder.Entity<AlertDetailValue>().HasKey(adv => new { adv.AlertOccurenceAlertId, adv.AlertOccurenceOrdinal, adv.Order });
modelBuilder.Entity<AlertOccurrence>().HasMany<AlertDetailValue>(adv => adv.AlertDetailValues).WithOne()
.HasForeignKey(adv => new {adv.AlertOccurenceAlertId, adv.AlertOccurenceOrdinal});

Having nullable foreign keys in one-to-one relationship with more than one entity

I wanted Movie, Actor, Director, User etc entities to have exactly one Image and an Image to belong to exactly one entity. I defined and configured them as -
Models : (simplified)
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public Image Image { get; set; }
}
public class Actor
{
public int Id { get; set; }
public string Name { get; set; }
public Image Image { get; set; }
}
// Director, User etc are defined in similar way
public class Image
{
public int Id { get; set; }
public string Base64 { get; set; }
public int? MovieId { get; set; }
public int? ActorId { get; set; }
public int? DirectorId { get; set; }
public int? UserId { get; set; }
}
Configurations : (simplified)
public class MovieConfig : IEntityTypeConfiguration<Movie>
{
public void Configure(EntityTypeBuilder<Movie> builder)
{
builder.ToTable("Movie");
builder.HasKey(p => p.Id);
builder.Property(p => p.Title).IsRequired(true).HasColumnType("nvarchar(128)");
builder.HasOne(e => e.Image)
.WithOne()
.HasForeignKey<Image>(e => e.MovieId)
.OnDelete(DeleteBehavior.Cascade);
}
}
// Actors, Director, User etc are configured in similar way
public class ImageConfig : IEntityTypeConfiguration<Image>
{
public void Configure(EntityTypeBuilder<Image> builder)
{
builder.ToTable("Image");
builder.HasKey(p => p.Id);
builder.Property(p => p.Base64).IsRequired(true).HasColumnType("nvarchar(MAX)");
}
}
This generates the schema with Image table having one-to-one relationship with each of Movie, Actor, Director, User etc table, as expected.
What's bothering me are all those nullable foreign-key fields in Image, because -
a new nullable foreign-key must be added whenever a new entity with Image is introduced
for any image entry only one of those foreign-key columns is going to have a value
What other ways I could define and configure the entities to achieve the same result while avoiding those nullable foreign-keys?
Edit :
Or is it, in general practice, considered OK to have a schema like the one I currently have (with multiple nullable foreign-keys where only one of them can have a value)?
I don't have a lot of experience in database design/schema and best/general practices. It just felt wrong to me and that's where the question came.
So, please feel free to give your opinion and suggestion?
Create relationship for MovieId, ActorId, DirectorId and UserId in Image table in sql server.
Then re-update your dbcontext and those foreign IDs will be auto saved from movie, actor, director and user when inserting.

Entity framework one foreign key toward two tables - code first

All,
Is it possible to use the same FK for two tables.
Probably it is not a good practice, but I have a two different classes that can be both booked:
public class Course {
public Course() {
BookingRefs = new HashSet<BookingRef>();
}
public long Id { get; set; }
public string Title { get; set; }
// other props ...
[InverseProperty(nameof(BookingRef.Course))]
public virtual ICollection<BookingRef> BookingRefs { get; set; }
}
public class GiftCard {
public GiftCard() {
BookingRefs = new HashSet<BookingRef>();
}
public long Id { get; set; }
public string Prop1 { get; set; }
public int Prop2 { get; set; }
// other props ...
[InverseProperty(nameof(BookingRef.Course))]
public virtual ICollection<BookingRef> BookingRefs { get; set; }
}
// this is the bookin reference for a Course or an GiftCard
public class BookingRef {
public BookingRef() {
}
public long Id { get; set; }
// other props ...
/// <summary>The item (usually the course but theoretically anything with a long id)</summary>
public long? ItemId { get; set; }
// maybe a generic Object?
[ForeignKey(nameof(ItemId))]
public Object GiftCard { get; set; }
// maybe 2 items possibly null?
[ForeignKey(nameof(ItemId))]
public Course Course { get; set; }
// maybe 2 items possibly null?
[ForeignKey(nameof(ItemId))]
public GiftCard GiftCard { get; set; }
}
Is it possible to use the same FK for two tables
No. The relational model doesn't allow that. You can introduce a superclass of all your bookable things and have a FK to that, but you shouldn't do that just get a single collection rather than multiple.
Think of it from the relational data perspective. How would the database know what table an "Item ID" pointed at? How would it index it?
This would be a case for using a null-able FK to each related table on the booking. These FKs do not need to reside in the entity, just the navigation properties. You can leverage .Map(x => x.MapKey) in EF6 or .HasForeignKey("") in EF Core to leverage a shadow property.
This does not enforce if you want a booking to only be associated to a course or a gift card but not both. That would need to be catered for at the application level, and I would recommend using a scheduled maintenance task to evaluate the data for violations to that rule. (Look for bookings holding both a course ID and a gift card ID for example)
You can alternatively keep the joins "loose" and evaluated by the application based on a discriminator similar to an inheritance model. (ItemId + ItemType) However you have to resolve the relationship load separately in your application based on the ItemType and lose out on any FK, indexing, and data integrity checks in the database. This could be a significant performance & maintenance cost to save adding a couple FKs.

One-to-one in EF only producing one Foreign Key

I'm attempting to build a 1-1 relationship - a Tenant has a Url, and vice versa:
Models
public class Tenant {
[Key]
[Required]
public int TenantId { get; set; }
public string Name { get; set; }
public virtual Url Url { get; set; }
public int UrlId { get; set; }
}
public class Url {
[Key]
[Required]
public int UrlId { get; set; }
public string Name { get; set; }
public virtual Tenant Tenant { get; set; }
public int TenantId { get; set; }
}
Configs
public class UrlConfiguration : EntityTypeConfiguration<Url> {
public UrlConfiguration() {
HasKey(s => s.UrlId);
}
}
public class TenantConfiguration : EntityTypeConfiguration<Tenant>
{
public TenantConfiguration() {
HasRequired(s => s.Url).WithRequiredPrincipal(s => s.Tenant);
}
}
Result:
I'd expect there to be a foreign key on both models... why is this not the case?
A one-to-one relationship with both ends having required foreign keys cannot exist in a relational database. If saving a new Tenant record requires a URL record, but in order to create that URL record, that URL requires a Tenant record, where will you begin?
Even though on a database level it can't practically exist, this model will still work. From my experience, Entity Framework will enforce the dependency on application level, and will throw an EntityException when it detects that one of the entities you're trying to save has no relationship to one of the other.
It creates this database model so that it can still save your entities, and enforce relationships on an application level.
No, this isn't nice on a database level as the one-to-one constraint won't be enforced there. If you need the database constraints as well, consider merging the tables or redesigning your data structures so that a one-to-one relationship isn't necessary.

Entity Framework TPH Inheritance Data Modeling Issues

I'm new to Entity Framework and C#/.Net and trying to create a TPH inheritance model, I'm not sure if I should be or not, so if not, please advise,
Here's the model:
public abstract class Vote
{
public int VoteID { get; set; }
public int UserID { get; set; }
public bool Value { get; set; }
public DateTime DateCreated { get; set; }
public User User { get; set; }
}
public class ProjectVote_ : Vote
{
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
public class CommentVote_ : Vote //There are three more like this, votes for different hings
{
public int CommentID { get; set; }
public virtual Comment Comment { get; set; }
}
Now the Project model (comment and model is similar)
public class Project
{
public int ProjectID { get; set; }
public string Title { get; set; }
public virtual ICollection<Vote> Vote { get; set; }
}
What happens is that ICollection creates a database column Project_ProjectID as the foreign key in the Vote table (I think) instead of using the ProjectID I defined. How do I fix it or should I model it differently. If the fluent API is the way to fix it, I don't know how to do that.
In the end I want to be able to use one table to store 5 different types of votes.
When you have related entities you don't need to have a property to store the FK in your model. Entity framework knows that it needs to make a FK to the Project table in ProjectVote when it detects Project in your ProjectVote_ model. Same thing with User and UserId and Comment and CommentId. You don't need to have a property that stores the FK in your model.
You are getting the FK column with the name you don't like "Project_ProjectID" because Entity framework is detecting that it needs to create a FK for your navigation property "Project". It's using it's own naming convention to create the column hence "Project_ProjectID".
If you want to provide your own name for the column override OnModelCreating in your DBContext class and add this fluent mapping.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasMany(p => p.Vote)
.HasRequired(v => v.Project) //or .WithOptional(v => v.Project)
.Map(m => m.MapKey("ProjectId")); //or any other name you want.
}
And for the future this is a helpful reference for how to use the Fluent API. For example here is some documentation on how to custimize TPH with fluent.
Hope that helps!