diff --git a/EntityFrameworkCore.Console/Program.cs b/EntityFrameworkCore.Console/Program.cs index 7120de4..b4fec72 100644 --- a/EntityFrameworkCore.Console/Program.cs +++ b/EntityFrameworkCore.Console/Program.cs @@ -15,6 +15,8 @@ context.Database.EnsureCreated(); // Rather than raise: "System.InvalidOperationException: Sequence contains no elements." // var firstCoach = await context.Coaches.FirstOrDefaultAsync(); +var details = await context.TeamsAndLeaguesView.ToListAsync(); + async Task ProjectionAndAnonymousDataTypes() { var teams = await context.Teams diff --git a/EntityFrameworkCore.Data/DeadBallZoneLeagueDbContext.cs b/EntityFrameworkCore.Data/DeadBallZoneLeagueDbContext.cs index d30cfc1..d539fa9 100644 --- a/EntityFrameworkCore.Data/DeadBallZoneLeagueDbContext.cs +++ b/EntityFrameworkCore.Data/DeadBallZoneLeagueDbContext.cs @@ -20,6 +20,7 @@ public class DeadBallZoneLeagueDbContext : DbContext public DbSet Coaches { get; set; } public DbSet Leagues { get; set; } public DbSet Matches { get; set; } + public DbSet TeamsAndLeaguesView { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { @@ -37,5 +38,8 @@ public class DeadBallZoneLeagueDbContext : DbContext // This line can be used, then individual configurations do not need to be loaded. modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly()); + + // This database object does not have a Primary Key, so we state that here, otherwise an exception is thrown. + modelBuilder.Entity().HasNoKey().ToView("vw_TeamsAndLeagues"); } } diff --git a/EntityFrameworkCore.Data/Migrations/20250407174459_AddTeamLeaguesAndCoachesView.Designer.cs b/EntityFrameworkCore.Data/Migrations/20250407174459_AddTeamLeaguesAndCoachesView.Designer.cs new file mode 100644 index 0000000..d04970d --- /dev/null +++ b/EntityFrameworkCore.Data/Migrations/20250407174459_AddTeamLeaguesAndCoachesView.Designer.cs @@ -0,0 +1,490 @@ +// +using System; +using EntityFrameworkCore.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace EntityFrameworkCore.Data.Migrations +{ + [DbContext(typeof(DeadBallZoneLeagueDbContext))] + [Migration("20250407174459_AddTeamLeaguesAndCoachesView")] + partial class AddTeamLeaguesAndCoachesView + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.3"); + + modelBuilder.Entity("EntityFrameworkCore.Domain.Coach", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("ModifiedBy") + .HasColumnType("TEXT"); + + b.Property("ModifiedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Coaches"); + + b.HasData( + new + { + Id = 1, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Christian Southgate" + }, + new + { + Id = 2, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Rob Mann" + }, + new + { + Id = 3, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Jon Curtis" + }, + new + { + Id = 4, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Andy Taylor" + }, + new + { + Id = 5, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Steve Johnson" + }, + new + { + Id = 6, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Dan Cook" + }, + new + { + Id = 7, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Ken Jarvis" + }, + new + { + Id = 8, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Kenny Suzuki" + }, + new + { + Id = 9, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Gordon Hall" + }, + new + { + Id = 10, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "John O Dowd" + }, + new + { + Id = 11, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Julian Widdows" + }, + new + { + Id = 12, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Andy Williams" + }, + new + { + Id = 13, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Trevor Williams" + }, + new + { + Id = 14, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Blake Deathray" + }, + new + { + Id = 15, + CreatedDate = new DateTime(2025, 4, 7, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Rock Housebrick" + }); + }); + + modelBuilder.Entity("EntityFrameworkCore.Domain.League", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("ModifiedBy") + .HasColumnType("TEXT"); + + b.Property("ModifiedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.ToTable("Leagues"); + + b.HasData( + new + { + Id = 1, + CreatedDate = new DateTime(2025, 4, 6, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Local League" + }, + new + { + Id = 2, + CreatedDate = new DateTime(2025, 4, 6, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "National League" + }, + new + { + Id = 3, + CreatedDate = new DateTime(2025, 4, 6, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Geosphere" + }, + new + { + Id = 4, + CreatedDate = new DateTime(2025, 4, 6, 17, 7, 27, 33, DateTimeKind.Unspecified), + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Cyber war" + }); + }); + + modelBuilder.Entity("EntityFrameworkCore.Domain.Match", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AwayTeamId") + .HasColumnType("INTEGER"); + + b.Property("AwayTeamScore") + .HasColumnType("INTEGER"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("Date") + .HasColumnType("TEXT"); + + b.Property("HomeTeamId") + .HasColumnType("INTEGER"); + + b.Property("HomeTeamScore") + .HasColumnType("INTEGER"); + + b.Property("ModifiedBy") + .HasColumnType("TEXT"); + + b.Property("ModifiedDate") + .HasColumnType("TEXT"); + + b.Property("TicketPrice") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("AwayTeamId"); + + b.HasIndex("HomeTeamId"); + + b.ToTable("Matches"); + }); + + modelBuilder.Entity("EntityFrameworkCore.Domain.Team", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CoachId") + .HasColumnType("INTEGER"); + + b.Property("CreatedBy") + .HasColumnType("TEXT"); + + b.Property("CreatedDate") + .HasColumnType("TEXT"); + + b.Property("LeagueId") + .HasColumnType("INTEGER"); + + b.Property("ModifiedBy") + .HasColumnType("TEXT"); + + b.Property("ModifiedDate") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CoachId") + .IsUnique(); + + b.HasIndex("LeagueId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Teams"); + + b.HasData( + new + { + Id = 1, + CoachId = 1, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 2, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Neo Delhi" + }, + new + { + Id = 2, + CoachId = 2, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 1, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Voodoo" + }, + new + { + Id = 3, + CoachId = 3, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 1, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Penal X" + }, + new + { + Id = 4, + CoachId = 4, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 3, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Neo Tokyo" + }, + new + { + Id = 5, + CoachId = 5, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 2, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Neo Barcelona" + }, + new + { + Id = 6, + CoachId = 6, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 2, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Neo Manchester" + }, + new + { + Id = 7, + CoachId = 7, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 3, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Neo Bangkok" + }, + new + { + Id = 8, + CoachId = 8, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 3, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Neo Amsterdam" + }, + new + { + Id = 9, + CoachId = 9, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 1, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Killaklowns" + }, + new + { + Id = 10, + CoachId = 10, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 1, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Sol" + }, + new + { + Id = 11, + CoachId = 11, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 4, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "DEC" + }, + new + { + Id = 12, + CoachId = 12, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 1, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Leopards" + }, + new + { + Id = 13, + CoachId = 13, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 1, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Harlequins" + }, + new + { + Id = 14, + CoachId = 14, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 4, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Gladiators" + }, + new + { + Id = 15, + CoachId = 15, + CreatedDate = new DateTime(2025, 4, 4, 17, 7, 27, 33, DateTimeKind.Unspecified), + LeagueId = 1, + ModifiedDate = new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), + Name = "Fiz-O" + }); + }); + + modelBuilder.Entity("EntityFrameworkCore.Domain.Match", b => + { + b.HasOne("EntityFrameworkCore.Domain.Team", "AwayTeam") + .WithMany("AwayMatches") + .HasForeignKey("AwayTeamId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("EntityFrameworkCore.Domain.Team", "HomeTeam") + .WithMany("HomeMatches") + .HasForeignKey("HomeTeamId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AwayTeam"); + + b.Navigation("HomeTeam"); + }); + + modelBuilder.Entity("EntityFrameworkCore.Domain.Team", b => + { + b.HasOne("EntityFrameworkCore.Domain.Coach", "Coach") + .WithOne("Team") + .HasForeignKey("EntityFrameworkCore.Domain.Team", "CoachId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("EntityFrameworkCore.Domain.League", "League") + .WithMany("Teams") + .HasForeignKey("LeagueId"); + + b.Navigation("Coach"); + + b.Navigation("League"); + }); + + modelBuilder.Entity("EntityFrameworkCore.Domain.Coach", b => + { + b.Navigation("Team"); + }); + + modelBuilder.Entity("EntityFrameworkCore.Domain.League", b => + { + b.Navigation("Teams"); + }); + + modelBuilder.Entity("EntityFrameworkCore.Domain.Team", b => + { + b.Navigation("AwayMatches"); + + b.Navigation("HomeMatches"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/EntityFrameworkCore.Data/Migrations/20250407174459_AddTeamLeaguesAndCoachesView.cs b/EntityFrameworkCore.Data/Migrations/20250407174459_AddTeamLeaguesAndCoachesView.cs new file mode 100644 index 0000000..03dddf3 --- /dev/null +++ b/EntityFrameworkCore.Data/Migrations/20250407174459_AddTeamLeaguesAndCoachesView.cs @@ -0,0 +1,32 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace EntityFrameworkCore.Data.Migrations +{ + /// + public partial class AddTeamLeaguesAndCoachesView : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // I added this SQL manually, by first creating an empty migration. + // A view is a read-only representation of a data set. + migrationBuilder.Sql(@" + CREATE VIEW vw_TeamsAndLeagues + AS + SELECT t.name, l.Name AS LeagueName + FROM Teams AS t + LEFT JOIN Leagues AS l ON t.LeagueId = l.Id + "); + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + // Don't forget this part as well, for each manually added SQL statement. + migrationBuilder.Sql(@"DROP VIEW vw_TeamsAndLeagues"); + } + } +} diff --git a/EntityFrameworkCore.Domain/TeamsAndLeaguesView.cs b/EntityFrameworkCore.Domain/TeamsAndLeaguesView.cs new file mode 100644 index 0000000..4d4dc8f --- /dev/null +++ b/EntityFrameworkCore.Domain/TeamsAndLeaguesView.cs @@ -0,0 +1,7 @@ +namespace EntityFrameworkCore.Domain; + +public class TeamsAndLeaguesView +{ + public string? Name { get; set; } + public string? LeagueName { get; set; } +}