C
C#5d ago
surwren

Puzzled about Nullability in EFCore

"Non-nullable property 'Owner' must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring the property as nullable." I get this warning constantly, and sometimes it causes EFcore to error out when I try to apply migrations. Say for example I have a Post entity that may or may not have an uploaded cover UpCover
public class Post : BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }

public bool SubscriberOnly { get; set; } = false;
public bool Pinned { get; set; } = false;
public string Description { get; set; } = "";
public int Likes { get; set; } = 0;

public float LocLatitude { get; set; } = 0.0f;
public float LocLongitude { get; set; } = 0.0f;

// Navigation properties
public Guid OwnerId { get; set; }
public User Owner { get; set; }

public Guid CoverId { get; set; }
public UpCover Cover { get; set; }

public ICollection<UpMedia> Media { get; set; }
public ICollection<CommentThread> CommentThreads { get; set; }
public ICollection<User> Tagged { get; set; }

public Post() : base()
{
Media = new HashSet<UpMedia>();
CommentThreads = new HashSet<CommentThread>();
Tagged = new HashSet<User>();
}

public bool HasTaggedLocation()
{
return !(LocLatitude == 0.0f && LocLongitude == 0.0f);
}
}
public class Post : BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }

public bool SubscriberOnly { get; set; } = false;
public bool Pinned { get; set; } = false;
public string Description { get; set; } = "";
public int Likes { get; set; } = 0;

public float LocLatitude { get; set; } = 0.0f;
public float LocLongitude { get; set; } = 0.0f;

// Navigation properties
public Guid OwnerId { get; set; }
public User Owner { get; set; }

public Guid CoverId { get; set; }
public UpCover Cover { get; set; }

public ICollection<UpMedia> Media { get; set; }
public ICollection<CommentThread> CommentThreads { get; set; }
public ICollection<User> Tagged { get; set; }

public Post() : base()
{
Media = new HashSet<UpMedia>();
CommentThreads = new HashSet<CommentThread>();
Tagged = new HashSet<User>();
}

public bool HasTaggedLocation()
{
return !(LocLatitude == 0.0f && LocLongitude == 0.0f);
}
}
How can I account for this on both sides of the relationship?
No description
28 Replies
surwren
surwrenOP5d ago
UpCover.cs:
public class UpCover : BaseUpload
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }

public Guid PostId { get; set; }
public Post Post { get; set; } // must always be present

public UpCover() : base()
{
}
public UpCover(Post Post, string Bucket, string Path) : base(Bucket, Path)
{
this.Post = Post;
this.PostId = Post.Id;
}

}
public class UpCover : BaseUpload
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }

public Guid PostId { get; set; }
public Post Post { get; set; } // must always be present

public UpCover() : base()
{
}
public UpCover(Post Post, string Bucket, string Path) : base(Bucket, Path)
{
this.Post = Post;
this.PostId = Post.Id;
}

}
AppDbContext
modelBuilder.Entity<Post>(entity =>
{
...
entity.HasOne(p => p.Cover)
.WithOne(c => c.Post)
.HasForeignKey<Post>(c => c.CoverId)
.OnDelete(DeleteBehavior.Restrict); // Restrict deletion of post when cover is deleted

});

modelBuilder.Entity<UpCover>(entity =>
{
entity.HasOne(c => c.Post)
.WithOne(p => p.Cover)
.HasForeignKey<UpCover>(c => c.PostId)
.OnDelete(DeleteBehavior.Cascade); // Cascade delete cover when a post is deleted
});
modelBuilder.Entity<Post>(entity =>
{
...
entity.HasOne(p => p.Cover)
.WithOne(c => c.Post)
.HasForeignKey<Post>(c => c.CoverId)
.OnDelete(DeleteBehavior.Restrict); // Restrict deletion of post when cover is deleted

});

modelBuilder.Entity<UpCover>(entity =>
{
entity.HasOne(c => c.Post)
.WithOne(p => p.Cover)
.HasForeignKey<UpCover>(c => c.PostId)
.OnDelete(DeleteBehavior.Cascade); // Cascade delete cover when a post is deleted
});
Is making the nav property nullable on one side only a correct way to resolve this?
public Guid CoverId? { get; set; }
public UpCover Cover? { get; set; }
public Guid CoverId? { get; set; }
public UpCover Cover? { get; set; }
mg
mg5d ago
yes i follow these rules * all navigation properties from a dependent to a principal are nullable (e.g. Cover is always nullable, even if the relationship is required) * all navigation properties from a principal to its dependents is a non-nullable List<T> with a default value of []
surwren
surwrenOP5d ago
all navigation properties from a principal to its dependents is a non-nullable List<T> with a default value of [] This is only for one-to-many, no? Many-to-many would be different?
surwren
surwrenOP5d ago
so a post must have an owner, so is setting required modifier here correct?
No description
mg
mg5d ago
I personally like to explicitly type my many to many relationships, so for me if A and B have a many-to-many relationship through the join table AB then I have
class A
{
public List<B> Bs { get; set; } = [];
}

class B
{
public List<A> As { get; set; } = [];
}
class A
{
public List<B> Bs { get; set; } = [];
}

class B
{
public List<A> As { get; set; } = [];
}
and then configure them with HasMany(a => a.Bs).WithMany(b => b.As).UsingEntity<AB>() using the fluent api EF doesn't care about required. If the property is a non-nullable guid, it'll be required in the database required requires you to supply a value when initializing an instance of the class
surwren
surwrenOP5d ago
I did this for a different many-to-many, is this correct?
entity.HasMany(p => p.Media)
.WithMany(m => m.Posts)
.UsingEntity(j => j.ToTable("PostMedia"));
entity.HasMany(p => p.Media)
.WithMany(m => m.Posts)
.UsingEntity(j => j.ToTable("PostMedia"));
also if I set nullable, should I set it on both the object and the Id or just the object
mg
mg5d ago
not sure, i use explicit join types object is always nullable
surwren
surwrenOP5d ago
what are explicit join types
mg
mg5d ago
having a class AB to represent the join table between A and B
surwren
surwrenOP5d ago
so just Guid needed to become Guid?
No description
mg
mg5d ago
yes
surwren
surwrenOP5d ago
you manually create the intermediary table?
mg
mg5d ago
yes
glhays
glhays5d ago
I think what @surwren has is the same just her way is telling ef that the table name is to be PostMedia. Just letting ef create the table for the relationship of many to many
mg
mg5d ago
the difference is that if you explicitly create a type and DbSet for it then you can query the join table directly and also add more columns on to it if you need to
surwren
surwrenOP5d ago
can you show an example of the proper way to do a manual table? I have something similar in UserSubscriptions but idk if it is correct:
modelBuilder.Entity<UserSubscription>(entity =>
{
entity.HasKey(us => new { us.SubscribedToId, us.SubscriberId });
entity.HasOne(us => us.SubscribedTo)
.WithMany() // No navigation property on User
.HasForeignKey(us => us.SubscribedToId)
.OnDelete(DeleteBehavior.Restrict); // Prevent cascading delete

entity.HasOne(us => us.Subscriber)
.WithMany()
.HasForeignKey(us => us.SubscriberId)
.OnDelete(DeleteBehavior.Restrict);
});

modelBuilder.Entity<User>(entity =>
{
...
entity.HasMany(u => u.Followers)
.WithMany(u => u.Following)
.UsingEntity(j => j.ToTable("UserFollows"));
});
modelBuilder.Entity<UserSubscription>(entity =>
{
entity.HasKey(us => new { us.SubscribedToId, us.SubscriberId });
entity.HasOne(us => us.SubscribedTo)
.WithMany() // No navigation property on User
.HasForeignKey(us => us.SubscribedToId)
.OnDelete(DeleteBehavior.Restrict); // Prevent cascading delete

entity.HasOne(us => us.Subscriber)
.WithMany()
.HasForeignKey(us => us.SubscriberId)
.OnDelete(DeleteBehavior.Restrict);
});

modelBuilder.Entity<User>(entity =>
{
...
entity.HasMany(u => u.Followers)
.WithMany(u => u.Following)
.UsingEntity(j => j.ToTable("UserFollows"));
});
public class UserSubscription
{
[Key]
[Column(Order = 0)]
public Guid SubscribedToId { get; set; }

[Key]
[Column(Order = 1)]
public Guid SubscriberId { get; set; }

[Required]
public int Tier { get; set; }
[Required]
public DateTimeOffset SubscriptionStart { get; set; } //support more timezones
[Required]
public DateTimeOffset SubscriptionEnd { get; set; }

public User SubscribedTo { get; set; }
public User Subscriber { get; set; }
}


public class UserSubscription
{
[Key]
[Column(Order = 0)]
public Guid SubscribedToId { get; set; }

[Key]
[Column(Order = 1)]
public Guid SubscriberId { get; set; }

[Required]
public int Tier { get; set; }
[Required]
public DateTimeOffset SubscriptionStart { get; set; } //support more timezones
[Required]
public DateTimeOffset SubscriptionEnd { get; set; }

public User SubscribedTo { get; set; }
public User Subscriber { get; set; }
}


public class User : BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
...

public Guid? DpId { get; set; }
public UpDp? Dp { get; set; }

public ICollection<Post> Posts { get; set; }

public ICollection<Comment> Comments { get; set; }

public ICollection<UpMedia> Media { get; set; }

public ICollection<Post> TaggedIn { get; set; }

public ICollection<User> Followers { get; set; }

public ICollection<User> Following { get; set; }

public User() : base()
{
Posts = new HashSet<Post>();
Comments = new HashSet<Comment>();
Media = new HashSet<UpMedia>();
TaggedIn = new HashSet<Post>();
Followers = new HashSet<User>();
Following = new HashSet<User>();

}

}
public class User : BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
...

public Guid? DpId { get; set; }
public UpDp? Dp { get; set; }

public ICollection<Post> Posts { get; set; }

public ICollection<Comment> Comments { get; set; }

public ICollection<UpMedia> Media { get; set; }

public ICollection<Post> TaggedIn { get; set; }

public ICollection<User> Followers { get; set; }

public ICollection<User> Following { get; set; }

public User() : base()
{
Posts = new HashSet<Post>();
Comments = new HashSet<Comment>();
Media = new HashSet<UpMedia>();
TaggedIn = new HashSet<Post>();
Followers = new HashSet<User>();
Following = new HashSet<User>();

}

}
idk if this is right
mg
mg5d ago
looks like it could work. does it? this is the general form of what i do
surwren
surwrenOP5d ago
It doesn't throw any errors, I haven't tried adding to it yet but you said you do manually this way which is very different from the syntax I just posted I'm confused
mg
mg5d ago
not really, in your case A and B are just the same entity
surwren
surwrenOP5d ago
are you referring to the custom relationship for Subscribers or Followers because Followers is kind of like your example whereas subscribers is what I would call a custom table (UserSubscription)
mg
mg5d ago
A and B are both User
surwren
surwrenOP5d ago
yes for Followers it uses similar syntax as you do, except without the [] creation bc it uses a hashset for UserSubscription it is an entirely custom table can you elaborate on which style you are referring to when you say custom table
mg
mg5d ago
i'm referring to what i described and posted pseudocode for it's not going to be 100% analagous to your case because your case doesn't have two different entities being joined what you posted is an example of an explicit join entity, yes
surwren
surwrenOP5d ago
can you explain how you do this not sure, i use explicit join types having a class AB to represent the join table between A and B the difference is that if you explicitly create a type and DbSet for it then you can query the join table directly and also add more columns on to it if you need to with the pseudocode you posted
mg
mg5d ago
not right now, advent of code day 17 is about to drop lol but i'll get back to you
surwren
surwrenOP5d ago
I feel really stupid about this. How did you do explicit type creation or more columns in the the pseudocode you posted here
No description
glhays
glhays5d ago
@surwren see the section Many to Many with Class for Join Entity. May answer you current question. https://learn.microsoft.com/en-us/ef/core/modeling/relationships/many-to-many?source=recommendations
Many-to-many relationships - EF Core
How to configure many-to-many relationships between entity types when using Entity Framework Core
mg
mg5d ago
what i didn't include (but probably should have, sorry) was the definition of AB
public class AB
{
public int AId { get; set; }
public int BId { get; set; }
public A? A { get; set; }
public B? B { get; set; }
}
public class AB
{
public int AId { get; set; }
public int BId { get; set; }
public A? A { get; set; }
public B? B { get; set; }
}
Want results from more Discord servers?
Add your server