Don't use underscores in identifiers. If that's how the database has them, use a scratch that, you're going code-first. This:ColumnAttribute
to specify the column name
public int Tournament_Id { get; set; }
public int Match_Id { get; set; }
public int Team_Id { get; set; }
public int PlayerAccount_Id { get; set; }
Is much more seesharpesque like this:
public int TournamentId { get; set; }
public int MatchId { get; set; }
public int TeamId { get; set; }
public int PlayerAccountId { get; set; }
I like that most of these foreign key properties follow a very predictable naming pattern [ForeignEntityTypeName]Id
- IIRC that's all EF needs to be able to infer the foreign keys.
...With the exception of TournamentMatchResult
, which breaks the pattern with its MatchId
(/Match_Id
) property:
public int Match_Id { get; set; }
public virtual TournamentMatch TournamentMatch { get; set; }
Would be more consistent like this:
public int TournamentMatchId { get; set; }
public virtual TournamentMatch TournamentMatch { get; set; }
You're using System.ComponentModel.DataAnnotations;
; it works, but I find all these attributes pollute the POCO:
[Key] public int Id { get; set; } [Required] public int Match_Id { get; set; } [Required] public int Team_Id { get; set; } [Required] public double Score { get; set; } [ForeignKey("Match_Id")] public virtual TournamentMatch TournamentMatch { get; set; } [ForeignKey("Team_Id")] public virtual Team Team { get; set; }
EF is smart enough to know Id
is your [Key]
(by naming convention), and smart enough to know Team
is a FK that uses TeamId
(by naming convention) - I'm not sure whether Team_Id
breaks it or not, but relying on EF's established conventions makes your POCO code less polluted:
public int Id { get; set; }
[Required]
public int TournamentMatchId { get; set; }
[Required]
public int TeamId { get; set; }
[Required]
public double Score { get; set; }
public virtual TournamentMatch TournamentMatch { get; set; }
public virtual Team Team { get; set; }
Now all that's left is [Required]
attributes. You could remove them too, if you moved POCO configurations/mappings elsewhere.
One way of doing that would be in the OnModelCreating
override of your DbContext
:
modelBuilder.Entity<TournamentMatchResult>()
.Property(t => t.TournamentMatchId).IsRequired();
modelBuilder.Entity<TournamentMatchResult>()
.Property(t => t.TeamId).IsRequired();
modelBuilder.Entity<TournamentMatchResult>()
.Property(t => t.Score).IsRequired();
Obviously this gets very verbose, very fast. An alternative is to use EntityMappingConfiguration<TEntityType>
classes. For each entity type, you create a mapping class:
public class TournamentMatchResultMap : EntityMappingConfiguration<TournamentMatchResult>
{
public TournamentMatchResultMap()
{
Property(t => t.TournamentMatchId).IsRequired();
Property(t => t.TeamId).IsRequired();
Property(t => t.Score).IsRequired();
}
}
You can also use these mapping classes to configure mapped column names and table, and relationships with other entities. See EntityMappingConfiguration<TEntityType>
Then the OnModelCreating
override simply loads all the configurations:
modelBuilder.Configurations.Add(new TournamentMatchResultMap());
I find that's the cleanest approach; it lets your POCO classes be POCO's, it leaves the OnModelCreating
method free of verbose fluent API mappings, and it puts each entity's configurations into its own class.
Let's look at the relationships. If I didn't make mistakes, this is what your code can be modeled as:
As @Jeroen explains, EF doesn't need junction tables to model a many-to-many relationship; PlayerTeamKey
and TeamTournamentKey
entities can be removed.
I would believe this simplified model could very well suit your needs:
The idea is to use the navigation properties to navigate between entities - from a business standpoint it makes sense for tournament matches to know what teams are involved, but from a data standpoint, it doesn't: when your application creates a new tournament, it also creates a TournamentMatchResult
for all teams involved, with each team's score at 0 - and that's enough for a TournamentMatch
to know what teams are playing.
As the game evolves, you can then update the TournamentMatchResult
records, and if a TournamentMatch
needs to know what teams are involved, it can easily find out by selecting its .TournamentMatchResults.Select(result => result.Team)
.
Notice this results in simple, easy-to-follow, one-to-many relationships everywhere.