Repository Pattern To Eager Load Entities Using Include In Entity Framework Core


In this post, we will discuss how to properly handle the eager-loading problem for complex objects when using the Repository pattern. We'll explore the need to eagerly load related entities using the repository pattern in Entity Framework, will delve into the process of using the repository pattern to eagerly load entities using ThenInclude in Entity Framework. We'll explore why this approach is beneficial, understand the challenges it presents, and discuss possible solutions and best practices to efficiently implement eager loading within our projects. 
 

Working with the repository pattern to eager load entities using ThenInclude in Entity Framework, we can achieve efficient data retrieval and minimize the number of database round trips. 

We are going to implement the repository pattern, which allows us to pass our class and context into a general repository, and enables us to specify 'include' statements.

Firstly, we define our repository interface with methods for retrieving entities and eagerly loading related entities:


        public interface IRepository<T>
        {
            IQueryable<T> GetAll();
            IQueryable<T> GetAllIncluding(params Expression<Func<T, object>>[] includeProperties);
        }
    

Next, we implement the repository interface in our concrete repository class:


        public class Repository<T> : IRepository<T> where T : class
        {
            private readonly AppDbContext _context;
            private readonly DbSet<T> _dbSet;

            public Repository(AppDbContext context)
            {
                _context = context;
                _dbSet = context.Set<T>();
            }

            public IQueryable<T> GetAll()
            {
                return _dbSet;
            }

            public IQueryable<T> GetAllIncluding(params Expression<Func<T, object>>[] includeProperties)
            {
                IQueryable<T> query = _dbSet;
                return includeProperties.Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
            }
        }
    

Now, we can utilize the repository to eager load related entities using ThenInclude in our service layer:


        public class ProductService
        {
            private readonly IRepository<Product> _productRepository;

            public ProductService(IRepository<Product> productRepository)
            {
                _productRepository = productRepository;
            }

            public List<Product> GetProductsWithCategories()
            {
                return _productRepository.GetAllIncluding(p => p.Category).ToList();
            }
        }
    

In this example, we're eager loading the Category entity related to each Product entity using ThenInclude. This allows us to retrieve all products along with their associated categories in a single database query, optimizing performance and reducing overhead.

Below are the model classes for Product and Category:


        public class Product
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public decimal Price { get; set; }                         
            public Category Category { get; set; }
        }

        public class Category
        {
            public int Id { get; set; }
            public string Name { get; set; }
            public ICollection<Product> Products { get; set; }
        }
    

In the Product class, we define properties Id, Name, Price, and a navigation property for the related Category entity. Similarly, in the Category class, we define properties like Id, Name, and a navigation property for the collection of related Product entities.


Below is the DbContext class:


        public class AppDbContext : DbContext
        {
            public DbSet<Product> Products { get; set; }
            public DbSet<Category> Categories { get; set; }

            public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
            {
            }

            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                
            }
        }
    

In this DbContext class, we define DbSets for the Product and Category entities, allowing us to query and manipulate data in these tables, the constructor initializes the DbContext with the provided options, and the OnModelCreating method can be used to configure relationships, constraints, and other aspects of the database schema.