Lazy Loading In ASP.NET Core 7 Web API

ASP.NET Core 7 Web API. Lazy loading is a technique where related data is only loaded from the database when it is explicitly requested. In Entity Framework Core, lazy loading can be achieved by marking navigation properties as virtual.

Create a new ASP.NET Core Web API Project

dotnet new webapi -n LazyLoadingInAspNetCoreWebApI
cd LazyLoadingInAspNetCoreWebApI

Install SQLite NuGet Package

Install the SQLite NuGet package to enable SQLite support in your project.

dotnet add package Microsoft.EntityFrameworkCore.Sqlite

Let's assume you have two entities, Author and Book, where an author can have multiple books. Here's a simple example:

Create your model classes

In the Author and Book classes, the navigation property Books is marked as virtual. This is a key step in enabling lazy loading for these navigation properties.

namespace LazyLoadingInAspNetCoreWebApI.Models
{
    public class Author
    {
        public int AuthorId { get; set; }
        public string Name { get; set; }

        public virtual ICollection<Book> Books { get; set; }
    }
}

The use of the virtual keyword allows Entity Framework Core to generate proxies for these classes, which can then intercept calls to navigation properties and load the related data on demand.

Create Books Model

namespace LazyLoadingInAspNetCoreWebApI.Models
{
    public class Book
    {
        public int BookId { get; set; }
        public string Title { get; set; }
        public int AuthorId { get; set; }

        public virtual Author Author { get; set; }
    }
}

Create the Application Database Context

using LazyLoadingInAspNetCoreWebApI.Models;
using Microsoft.EntityFrameworkCore;

namespace LazyLoadingInAspNetCoreWebApI.AppDbContext
{
    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }

        public DbSet<Author> Authors { get; set; }
        public DbSet<Book> Books { get; set; }
    }
}

Add the Dependencies in the Program.cs Class

Make sure you have the necessary NuGet packages installed, including Microsoft.EntityFrameworkCore.Sqlite and Microsoft.EntityFrameworkCore.Proxies. Also, ensure that your DefaultConnection in the configuration is properly set up with the correct SQLite connection details.

using LazyLoadingInAspNetCoreWebApI.AppDbContext;
using Microsoft.EntityFrameworkCore;
using Microsoft.OpenApi.Models;
using System;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
var configuration = builder.Configuration;

// Add services to the container.
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseLazyLoadingProxies().UseSqlite(configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "Lazy Loading In Asp.net Core Web API", Version = "v1" });

});
var app = builder.Build();

// Configure the HTTP request pipeline.
if(app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Add the Database Connection

// appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Data Source=LazyLoading.db"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

Run the Migration

dotnet ef migrations add InitialCreate
dotnet ef database update

Check the SQL Lite Database For Tables

SQL Lite Database table

Create a Controller For Handling HTTP Requests

using LazyLoadingInAspNetCoreWebApI.AppDbContext;
using LazyLoadingInAspNetCoreWebApI.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

namespace LazyLoadingInAspNetCoreWebApI.Controllers
{
    [Route("api/[controller]/[Action]")]
    [ApiController]
    public class AuthorsController : ControllerBase
    {
        private readonly ApplicationDbContext _context;

        public AuthorsController(ApplicationDbContext context)
        {
            _context = context;
        }

        // GET: api/authors
        [HttpGet("GetAllAuthor")]
        public async Task<ActionResult<IEnumerable<Author>>> GetAuthors()
        {
            // Eager loading using Include method
            var authors = await _context.Authors.Include(a => a.Books).ToListAsync();

            return authors;
        }

        // GET: api/authors/5
        [HttpGet("{GetAuthorById}")]
        public async Task<ActionResult<Author>> GetAuthor(int id)
        {
            // Eager loading for a specific author by ID
            var author = await _context.Authors.Include(a => a.Books).FirstOrDefaultAsync(a => a.AuthorId == id);

            if(author == null)
            {
                return NotFound();
            }

            return author;
        }

        // POST: api/authors
        [HttpPost("CreateAuthor")]
        public async Task<ActionResult<Author>> CreateAuthor(Author author)
        {
            _context.Authors.Add(author);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetAuthor", new { id = author.AuthorId }, author);
        }

        // PUT: api/authors/5
        [HttpPut("UpdateAuthorById")]
        public async Task<IActionResult> UpdateAuthor(int id, Author author)
        {
            if(id != author.AuthorId)
            {
                return BadRequest();
            }

            _context.Entry(author).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch(DbUpdateConcurrencyException)
            {
                if(!_context.Authors.Any(a => a.AuthorId == id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        // DELETE: api/authors/5
        [HttpDelete("DeleteAuthorById")]
        public async Task<IActionResult> DeleteAuthor(int id)
        {
            var author = await _context.Authors.FindAsync(id);
            if(author == null)
            {
                return NotFound();
            }

            _context.Authors.Remove(author);
            await _context.SaveChangesAsync();

            return NoContent();
        }
    }
}

Output

Lazy Loading

GitHub Project URLhttps://github.com/SardarMudassarAliKhan/LazyLoadingInAspNetCoreWebApI

Conclusion

In conclusion, the provided code configures and registers an Entity Framework Core DbContext for an ASP.NET Core application. It uses SQLite as the database provider and enables lazy loading through the Entity Framework Core Proxies library.

Here's a summary of the key points.

  1. The AddDbContext<ApplicationDbContext> the method registers the application DbContext (assumed to be ApplicationDbContext) with the dependency injection container.

  2. The UseLazyLoadingProxies() method enables proxy-based lazy loading, allowing related data to be loaded from the database only when accessed.

  3. UseSqlite(configuration.GetConnectionString("DefaultConnection")) configures the DbContext to use SQLite as the database provider and retrieves the connection string from the application's configuration.

Ensure that you have the necessary NuGet packages installed, including Microsoft.EntityFrameworkCore.Sqlite and Microsoft.EntityFrameworkCore.Proxies. Additionally, make sure your SQLite connection string in the configuration is correctly set up with the appropriate details for your SQLite database.

If you want to study how to work with SQLite Database then read my complete article : https://www.c-sharpcorner.com/article/working-with-sql-lite-database-in-asp-net-core-web-api/