using EntityFrameworkCore.Data; using EntityFrameworkCore.Domain; using Microsoft.Data.Sqlite; using Microsoft.EntityFrameworkCore; var folder = Environment.SpecialFolder.LocalApplicationData; var path = Environment.GetFolderPath(folder); var dbPath = Path.Combine(path, "DeadBallZoneLeague_EFCore.db"); var optionsBuilder = new DbContextOptionsBuilder(); optionsBuilder.UseSqlite($"Data Source={dbPath}"); using var context = new DeadBallZoneLeagueDbContext(optionsBuilder.Options); // context.Database.Migrate(); // Can be used to automatically migrate on run. context.Database.EnsureCreated(); // var teamOne = await context.Teams.FirstAsync(t => t.Id == 1); // var teamTwo = await context.Teams.FirstAsync(t => t.Id == 2); // var teamThree = await context.Teams.SingleAsync(t => t.Id == 3); // var teamFour = await context.Teams.SingleOrDefaultAsync(t => t.Id == 3); // There is no entry for coaches yet, this function will provide a null to coach. // Rather than raise: "System.InvalidOperationException: Sequence contains no elements." // var firstCoach = await context.Coaches.FirstOrDefaultAsync(); async Task UserDefinedQuery() { var earliestMatch = context.GetEarliestTeamMatch(1); // This is done, if you have a user-defined function in the database. // for example: // CREATE FUNCTION [dbo].[GetEarliestMatch] (@teamId int) // RETURNS datetime // BEGIN // DECLARE @result datetime // SELECT TOP 1 @result = date // FROM MATCHES [dbo].[Matches] // ORDER BY Date // return @result // END } async Task QueryScalarType() { // Query a scalar or non-entity type. // Scalar subqueries or scalar queries are queries that return exactly one column and one or zero records. // Source: https://stackoverflow.com/questions/20424966/what-is-a-scalar-query var leagueIds = context.Database.SqlQuery( $"SELECT Id FROM Leagues" ).ToList(); } async Task RawSQLStatement() { // Beware that SQL Injection can be used on input here. Console.WriteLine("Enter Team name: "); var teamName = Console.ReadLine(); // If you omit this param creation, SQL injection is possible. var teamNameParam = new SqliteParameter("teamName", teamName); var teams = context.Teams.FromSqlRaw( $"SELECT * FROM Teams WHERE name = @teamName", teamNameParam ); foreach (var t in teams) { Console.WriteLine($"SQL Raw Team: {t.Name}"); } // Parameterization happens automatically for these two: teams = context.Teams.FromSql($"SELECT * FROM Teams WHERE name = {teamName}"); foreach (var t in teams) { Console.WriteLine($"SQL Raw Team: {t.Name}"); } // Note, that you cannot use JOINs in these SQL queries. // Instead, the Include() function is used to add related data. // FromSqlInterpolated is the revised form of FromSql, and a better choice. teams = context.Teams.FromSqlInterpolated($"SELECT * FROM Teams WHERE name = {teamName}"); foreach (var t in teams) { Console.WriteLine($"SQL Raw Team: {t.Name}"); } // You can also mix with LINQ. var teamsList = context.Teams.FromSql($"SELECT * FROM Teams") .Where(t => t.Id == 1) .OrderBy(t => t.Id) .Include("League") .ToList(); foreach (var t in teamsList) { Console.WriteLine($"SQL Raw Team: {t.Name}"); } // Stored procedures example. Note that SQLite does not support stored proceduces. var leagueId = 1; var league = context.Leagues.FromSqlInterpolated( $"EXEC dbo.StoredProcedureToGetLeagueName {leagueId}" ); // Non-querying statement examples. var newTeamName = "Neo Oslo"; var success = context.Database.ExecuteSqlInterpolated( $"UPDATE Teams SET Name = {newTeamName}" ); var teamToDelete = 1; var teamDeletedSuccess = context.Database.ExecuteSqlInterpolated( $"EXEC dbo.DeleteTeam {teamToDelete}" ); } async Task ProjectionAndAnonymousDataTypes() { var teams = await context.Teams .Select(t => new TeamDetailsDTO { TeamId = t.Id, TeamName = t.Name, CoachName = t.Coach.Name, TotalHomeGoals = t.HomeMatches.Sum(m => m.HomeTeamScore), TotalAwayGoals = t.AwayMatches.Sum(m => m.AwayTeamScore) }) .ToListAsync(); foreach (var team in teams) { Console.WriteLine($"{team.TeamName} - {team.CoachName} | H: {team.TotalHomeGoals} | A: {team.TotalAwayGoals}"); } } async Task FilterScoredHomeMatches() { // Get teams where they scored on home matches. var teams = await context.Teams .Include(t => t.Coach) .Include(t => t.HomeMatches.Where(m => m.HomeTeamScore > 0)) .ToListAsync(); foreach (var team in teams) { Console.WriteLine($"{team.Name} - {team.Coach.Name}"); foreach (var match in team.HomeMatches) { Console.WriteLine($"Score: {match.HomeTeamScore}"); } } } async Task InsertMatches() { var match1 = new Match { AwayTeamId = 10, HomeTeamId = 12, HomeTeamScore = 1, AwayTeamScore = 0, Date = new DateTime(2025, 6, 10), TicketPrice = 20 }; var match2 = new Match { AwayTeamId = 6, HomeTeamId = 9, HomeTeamScore = 1, AwayTeamScore = 0, Date = new DateTime(2025, 6, 11), TicketPrice = 20 }; var match3 = new Match { AwayTeamId = 13, HomeTeamId = 15, HomeTeamScore = 1, AwayTeamScore = 0, Date = new DateTime(2025, 6, 12), TicketPrice = 20 }; var match4 = new Match { AwayTeamId = 4, HomeTeamId = 11, HomeTeamScore = 0, AwayTeamScore = 1, Date = new DateTime(2025, 6, 12), TicketPrice = 20 }; await context.AddRangeAsync(match1, match2, match3, match4); await context.SaveChangesAsync(); } async Task ExplicitLoadingExample() { var league = await context.FindAsync(1); if (!league.Teams.Any()) { Console.WriteLine("Teams have not been loaded."); } await context.Entry(league) .Collection(l => l.Teams) .LoadAsync(); if (league.Teams.Any()) { foreach (var team in league.Teams) { Console.WriteLine($"{team.Name}"); } } } async Task EagerLoadingExample() { var leagues = await context.Leagues .Include(l => l.Teams) .ThenInclude(t => t.Coach) .ToListAsync(); foreach (var league in leagues) { Console.WriteLine($"League - {league.Name}"); foreach (var team in league.Teams) { Console.WriteLine($"* {team.Name} - {team.Coach.Name}"); } } } async Task InsertRelatedData() { // Insert record with a Foreign Key. var match = new Match { AwayTeamId = 1, HomeTeamId = 2, AwayTeamScore = 0, HomeTeamScore = 0, Date = new DateTime(2025, 6, 1), TicketPrice = 20 }; await context.AddAsync(match); await context.SaveChangesAsync(); // Insert Parent/Child var team = new Team { Name = "Neo Istanbul", Coach = new Coach { Name = "Gimbal Wizbouski" } }; await context.AddAsync(team); await context.SaveChangesAsync(); // Insert Parent with Children. var league = new League { Name = "Star League", Teams = new List { new Team { Name = "Neo Cairo", Coach = new Coach { Name = "Damon Pulsar" } }, new Team { Name = "Cyborgs", Coach = new Coach { Name = "C-1" } } } }; await context.AddAsync(league); await context.SaveChangesAsync(); } async Task ExecuteDelete(string name) { // var coaches = await context.Coaches.Where(c => c.Name == name).ToListAsync(); // context.RemoveRange(coaches); // await context.SaveChangesAsync(); // Simplified: await context.Coaches.Where(c => c.Name == name).ExecuteDeleteAsync(); } async Task ExecuteUpdate(int id) { await context.Coaches .Where(c => c.Id == id) .ExecuteUpdateAsync(set => set.SetProperty(c => c.CreatedDate, DateTime.UtcNow)); } async Task DeleteCoach(int id) { var coach = await context.Coaches.FindAsync(id); if (coach != null) { context.Remove(coach); // context.Entry(coach).State = EntityState.Deleted; await context.SaveChangesAsync(); } } async Task UpdateCoach() { var coach = await context.Coaches.FindAsync(1); coach.CreatedDate = DateTime.UtcNow; await context.SaveChangesAsync(); } async Task UpdateCoachNoTracking() { var coach = await context.Coaches .AsNoTracking() .FirstOrDefaultAsync(c => c.Id == 1); // .FindAsync(1); // cannot be used when using no-tracking. coach.CreatedDate = DateTime.UtcNow; Console.WriteLine(context.ChangeTracker.DebugView.LongView); context.Update(coach); // Set Entity state to be updated. // Note that also other fields are considered "updated/modified", such as the CreatedDate. // You can also use: // context.Entry(coach).State = EntityState.Modified; Console.WriteLine(context.ChangeTracker.DebugView.LongView); await context.SaveChangesAsync(); } async Task InsertCoach(string name) { var coach = new Coach { Name = name, CreatedDate = DateTime.UtcNow }; await context.Coaches.AddAsync(coach); Console.WriteLine(context.ChangeTracker.DebugView.LongView); await context.SaveChangesAsync(); // If you have a list, you can use a batch insert, instead of a loop: // await context.Coaches.AddRangeAsync(coaches); } async Task ListVSIQueryableCall() { Console.WriteLine("Enter '1' for team with Id 1 or enter '2' for teams that contain 'Neo'"); var option = Convert.ToInt32(Console.ReadLine()); List teamsAsList = new List(); // After executing ToList, the records are stored in memory. // Operations after that, are also done in memory. teamsAsList = await context.Teams.ToListAsync(); if (option == 1) { teamsAsList = teamsAsList.Where(t => t.Id == 1).ToList(); } else if (option == 2) { teamsAsList = teamsAsList.Where(t => t.Name.Contains("Neo")).ToList(); } foreach (var team in teamsAsList) { Console.WriteLine(team.Name); } // Records stay as IQueryable until the ToList function is called, then the final query is performed. var teamsAsQueryable = context.Teams.AsQueryable(); if (option == 1) { teamsAsQueryable = teamsAsQueryable.Where(t => t.Id == 1); } else if (option == 2) { teamsAsQueryable = teamsAsQueryable.Where(t => t.Name.Contains("Neo")); } // the actual query: teamsAsList = await teamsAsQueryable.ToListAsync(); foreach (var team in teamsAsList) { Console.WriteLine(team.Name); } } async Task TrackedQuery() { // EF Core tracks objects that are returned by queries. // This is less useful in disconnected applictations like APIs and Web applications. var teams = await context.Teams .AsNoTracking() .ToListAsync(); foreach (var team in teams) { Console.WriteLine(team.Name); } } async Task SkipAndTakeTeams() { var recordCount = 3; var page = 0; var next = true; while (next) { var teams = await context.Teams.Skip(page * recordCount).Take(recordCount).ToListAsync(); foreach (var team in teams) { Console.WriteLine(team.Name); } Console.WriteLine("Enter 'true' for next set of records, or 'false to quit.'"); next = Convert.ToBoolean(Console.ReadLine()); if (!next) break; page += 1; } } async Task OrderedTeams() { var orderedTeam = await context.Teams .OrderByDescending(t => t.Name) .ToListAsync(); foreach (var team in orderedTeam) { Console.WriteLine(team.Name); } } async Task GroupedTeams() { var groupedTeams = context.Teams .GroupBy(t => t.CreatedDate.Date); foreach (var groupedTeam in groupedTeams) { Console.WriteLine(groupedTeam.Key); Console.WriteLine(groupedTeam.Sum(t => t.Id)); foreach (var team in groupedTeam) { Console.WriteLine(team.Name); } } } async Task CountFunctionsTeams(int id) { var numberOfTeams = await context.Teams.CountAsync(t => t.Id > id); Console.WriteLine($"Number of teams with ID > {id}: {numberOfTeams}"); var maxTeams = await context.Teams.MaxAsync(t => t.Id); Console.WriteLine($"Max teams: {maxTeams}"); var minTeams = await context.Teams.MinAsync(t => t.Id); Console.WriteLine($"Min teams: {minTeams}"); var averageTeams = await context.Teams.AverageAsync(t => t.Id); Console.WriteLine($"Average teams: {averageTeams}"); var sumTeams = await context.Teams.SumAsync(t => t.Id); Console.WriteLine($"Sum team IDs: {sumTeams}"); } async Task GetAllTeamsQuerySyntax(string searchTerm) { var teams = await ( from team in context.Teams where EF.Functions.Like(team.Name, $"%{searchTerm}%") select team) .ToListAsync(); foreach (var team in teams) { Console.WriteLine(team.Name); } } async Task PrintTeams() { var teams = await context.Teams.ToListAsync(); foreach (var team in teams) { Console.WriteLine(team.Name); } } async Task PrintFilteredTeams() { Console.WriteLine("Enter search term: "); var searchTerm = Console.ReadLine(); // var filteredTeams = await context.Teams.Where(t => t.Name.Contains(searchTerm)).ToListAsync(); var filteredTeams = await context.Teams.Where(t => EF.Functions.Like(t.Name, $"%{searchTerm}%")).ToListAsync(); foreach (var team in filteredTeams) { Console.WriteLine(team.Name); } } async Task PrintTeamById(int id) { // Selection based on finding the Primary Key. var teamBasedOnId = await context.Teams.FindAsync(id); if (teamBasedOnId != null) { Console.WriteLine(teamBasedOnId.Name); } } async Task SelectQuery() { var items = await context.Teams .Select(t => new { t.Name, t.CreatedDate }) .ToListAsync(); foreach (var item in items) { Console.WriteLine($"{item.Name} - {item.CreatedDate}"); } } async Task SelectQueryDTO() { var items = await context.Teams .Select(t => new TeamInfoDTO { Name = t.Name, Created = t.CreatedDate }) .ToListAsync(); foreach (var item in items) { Console.WriteLine($"{item.Name} - {item.Created}"); } } class TeamInfoDTO { public DateTime Created { get; set; } public string Name { get; set; } } class TeamDetailsDTO { public int TeamId { get; set; } public string TeamName { get; set; } public string CoachName { get; set; } public int TotalHomeGoals { get; set; } public int TotalAwayGoals { get; set; } }