C
C#6mo ago
Kindred

Entity Framework Core - how to setup this navigation correctly?

Hi all. Very new to Entity Framework Core. I have three classes right now that I want to be database tables. In my Leaderboard class, I have an ICollection called Scores which is meant to hold a list of all LeaderboardScores that link to that Leaderboard. Though, I am unsure how to get it to actually link so the LeaderboardScores that reference that Leaderboard populate that navigation list? My classes are below. I combined them into this one file for ease of sharing, they obviously aren't all in one file in Visual Studio: https://gdl.space/qoxeretoha.cs
15 Replies
Pobiega
Pobiega6mo ago
convention is for the entity PKey to be called Id in its own class so PlayerId should just be Id inside the Player class, but remain as is when referenced as a foreign key (in LeaderboardScore) other than that, this seems mostly okay. are you getting any errors when generating migrations for this?
Kindred
Kindred6mo ago
That makes sense. I'll change it to be like that
Pobiega
Pobiega6mo ago
also, personal preference, but I really dislike using attributes to configure my models I much prefer using IEntityTypeConfiguration classes 🙂
Kindred
Kindred6mo ago
I'm not getting errors, but this line doesn't seem like it is working correctly. It should be a many-to-one relationship. So a LeaderboardScore can be connected to one Leaderboard. But a Leaderboard can have many LeaderboardScores as part of it. But EFC doesn't seem to know to populate this line with LeaderboardScore's that use that Leaderboard
No description
Kindred
Kindred6mo ago
Ill look into that! Thankyou
Pobiega
Pobiega6mo ago
hm...
public Leaderboard? Leaderboard { get; set; }

public Player? Player { get; set; }
public Leaderboard? Leaderboard { get; set; }

public Player? Player { get; set; }
why are these nullable? surely a score entry cant exist without both these
Kindred
Kindred6mo ago
Yeah a score entry can't exist without them. They are only nullable because Visual Studio gives me a warning if they aren't
No description
Pobiega
Pobiega6mo ago
Ah, well instead of marking them as nullable to hide the warning, I suggest you fix your models in this case by making the fields required
public required Leaderboard Leaderboard { get; set; }

public required Player Player { get; set; }
public required Leaderboard Leaderboard { get; set; }

public required Player Player { get; set; }
the nullability modifier actually makes EF think they are nullable
namespace EFTestApp.Data.Entities;

public class Leaderboard
{
public int Id { get; set; }

public required string Name { get; set; }

public LeaderboardAccess Access { get; set; } = LeaderboardAccess.PRIVATE;

public ICollection<LeaderboardScore> Scores { get; set; } = new List<LeaderboardScore>();

public enum LeaderboardAccess
{
PRIVATE,
PUBLIC
}
}

public class Player
{
public int Id { get; set; }
public required string Name { get; set; }

public required string UniqueIdentifier { get; set; }

public Leaderboard Leaderboard { get; set; }
}

public class LeaderboardScore
{
public int LeaderboardId { get; set; }

public int PlayerId { get; set; }

public required Leaderboard Leaderboard { get; set; }

public required Player Player { get; set; }

public required int Score { get; set; }
}
namespace EFTestApp.Data.Entities;

public class Leaderboard
{
public int Id { get; set; }

public required string Name { get; set; }

public LeaderboardAccess Access { get; set; } = LeaderboardAccess.PRIVATE;

public ICollection<LeaderboardScore> Scores { get; set; } = new List<LeaderboardScore>();

public enum LeaderboardAccess
{
PRIVATE,
PUBLIC
}
}

public class Player
{
public int Id { get; set; }
public required string Name { get; set; }

public required string UniqueIdentifier { get; set; }

public Leaderboard Leaderboard { get; set; }
}

public class LeaderboardScore
{
public int LeaderboardId { get; set; }

public int PlayerId { get; set; }

public required Leaderboard Leaderboard { get; set; }

public required Player Player { get; set; }

public required int Score { get; set; }
}
Kindred
Kindred6mo ago
Ahh thankyou, I didn't realize that was a keyword
Pobiega
Pobiega6mo ago
using EFTestApp.Data.Entities;

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace EFTestApp.Data.Configurations;

public sealed class LeaderboardConfiguration :
IEntityTypeConfiguration<Player>,
IEntityTypeConfiguration<LeaderboardScore>,
IEntityTypeConfiguration<Leaderboard>
{
public void Configure(EntityTypeBuilder<Player> builder)
{
builder
.Property(x => x.Name)
.HasMaxLength(100)
.IsRequired();
}

public void Configure(EntityTypeBuilder<LeaderboardScore> builder)
{
builder
.HasKey(x => new { x.PlayerId, x.LeaderboardId });
}

public void Configure(EntityTypeBuilder<Leaderboard> builder)
{
builder
.Property(x => x.Name)
.HasMaxLength(100)
.IsRequired();
}
}
using EFTestApp.Data.Entities;

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace EFTestApp.Data.Configurations;

public sealed class LeaderboardConfiguration :
IEntityTypeConfiguration<Player>,
IEntityTypeConfiguration<LeaderboardScore>,
IEntityTypeConfiguration<Leaderboard>
{
public void Configure(EntityTypeBuilder<Player> builder)
{
builder
.Property(x => x.Name)
.HasMaxLength(100)
.IsRequired();
}

public void Configure(EntityTypeBuilder<LeaderboardScore> builder)
{
builder
.HasKey(x => new { x.PlayerId, x.LeaderboardId });
}

public void Configure(EntityTypeBuilder<Leaderboard> builder)
{
builder
.Property(x => x.Name)
.HasMaxLength(100)
.IsRequired();
}
}
with these models and these configurations, it seems to work fine the migration generates as expected and looks fine
Kindred
Kindred6mo ago
Thankyou! I'll try this out
Pobiega
Pobiega6mo ago
hm I found an inconsistancy thou I'd expect a Player to have a Scores property of some sort they are currently linked to a single leaderboard can a player not be part of several leaderboards?
Kindred
Kindred6mo ago
Yeah, so right now a player is unique to a leaderboard. That is purposeful right now
Pobiega
Pobiega6mo ago
No description
Pobiega
Pobiega6mo ago
var leaderboard = new Leaderboard() { Name = "Testboard 1", };

var p1 = new Player()
{
Name = "Steve", UniqueIdentifier = Guid.NewGuid().ToString(), Leaderboard = leaderboard
};
p1.Scores.Add(new LeaderboardScore(leaderboard, p1, 20));

var p2 = new Player()
{
Name = "John", UniqueIdentifier = Guid.NewGuid().ToString(), Leaderboard = leaderboard
};
p2.Scores.Add(new LeaderboardScore(leaderboard, p2, 50));

_db.Add(leaderboard);
_db.Add(p1);
_db.Add(p2);
await _db.SaveChangesAsync();
var leaderboard = new Leaderboard() { Name = "Testboard 1", };

var p1 = new Player()
{
Name = "Steve", UniqueIdentifier = Guid.NewGuid().ToString(), Leaderboard = leaderboard
};
p1.Scores.Add(new LeaderboardScore(leaderboard, p1, 20));

var p2 = new Player()
{
Name = "John", UniqueIdentifier = Guid.NewGuid().ToString(), Leaderboard = leaderboard
};
p2.Scores.Add(new LeaderboardScore(leaderboard, p2, 50));

_db.Add(leaderboard);
_db.Add(p1);
_db.Add(p2);
await _db.SaveChangesAsync();
looks good to me when