Create a small blog in Blazor - Part 3 - Build Entity Framework queries

Published: Monday 8 June 2020

File Downloads

We are continuing with building a small blog in Blazor. In the first edition, we went ahead and created our Blazor application in Visual Studio. Afterwards, we integrated Entity Framework into our Blazor application. Now, we are going to build up our Entity Framework queries needed for our blog.

Entity Changes

One of the tasks we want to do is to show all categories that belong to a particular post. So we need to make a change to our Post entity. We added a new property collection of PostCategories. That means that when we run our query, we can get the categories that belong to the post.

// Post.cs
using System;
using System.Collections.Generic;
 
namespace RoundTheCode.Blazor.Data.PostSection
{
	public class Post : Base
	{
		public virtual string Title { get; set; }

		public virtual string Content { get; set; }

		public virtual bool Enabled { get; set; }

		public virtual DateTimeOffset? Published { get; set; }

		// The line below has been added
		public virtual ICollection<PostCategory> PostCategories { get; set; }

	}
}

In addition, we had to make a small tweak to our OnModelCreating method in PostCategories. When forming our relationship between Post and PostCategories, we need to add a parameter to the WithMany method, referencing the PostCategories property from post.

// PostCategories.cs
public static void OnModelCreating(ModelBuilder builder)
{
	builder.Entity<PostCategory>().HasOne(postCategory => postCategory.Category)
		.WithMany()
		.HasPrincipalKey(category => category.Id)
		.HasForeignKey(postCategory => postCategory.CategoryId);

	builder.Entity<PostCategory>().HasOne(postCategory => postCategory.Post)
		.WithMany(post => post.PostCategories) // Added  post.PostCategories
		.HasPrincipalKey(post => post.Id)
		.HasForeignKey(postCategory => postCategory.PostId);
}

Services

Now that we have made that change, we can go ahead and create our services. These services will hold the queries needed for our blog. Afterwards, we will need to add the services to dependency injection so we can use them in our Blazor application.

The Category Service

The first service we create is the Category Service. This has two methods contained in it.

  • GetAsync - This has a parameter where you pass in the ID. The purpose of this method is to get a category record that will help us display the title at the top of the category page
  • GetAllAsync - This is a parameterless method that gets all the categories. The purpose of this method is to get all the categories to be displayed in the navigation menu on the left-hand side of the page.

In addition, we have created a static EnabledCategories method. With this method, it ensures that we only return categories that have been enabled and not deleted.

// ICategoryService.cs
using RoundTheCode.Blazor.Data.CategorySection;
using System.Collections.Generic;
using System.Threading.Tasks;
 
namespace RoundTheCode.Blazor.Services.CategorySection
{
	public partial interface ICategoryService
	{
		Task<Category> GetAsync(int id);

		Task<IEnumerable<Category>> GetAllAsync();
	}
}
// CategoryService.cs
using Microsoft.EntityFrameworkCore;
using RoundTheCode.Blazor.Data;
using RoundTheCode.Blazor.Data.CategorySection;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
 
namespace RoundTheCode.Blazor.Services.CategorySection
{
	public partial class CategoryService : ICategoryService
	{
		protected readonly RoundTheCodeBlazorDbContext _context;

		public CategoryService([NotNull] RoundTheCodeBlazorDbContext context)
		{
			_context = context;
		}

		public virtual async Task<Category> GetAsync(int id)
		{
			return await _context.Set<Category>().EnabledCategories().FirstOrDefaultAsync(post => post.Id == id);
		}

		public virtual async Task<IEnumerable<Category>> GetAllAsync()
		{
			return await _context.Set<Category>().EnabledCategories().ToListAsync();
		}
	}

	public static class CategoryServiceHelpers
	{
		public static IQueryable<Category> EnabledCategories(this IQueryable<Category> query)
		{
			return query.Where(category => category.Enabled && !category.Deleted.HasValue);
		}
	}
}

The Post Service

The other service we created was the post service. Once again, this has two methods contained in it:

  • GetAsync - This has a parameter where you pass in the ID. The purpose of this method is to get a post record that will help us display the title, published date and content on the post page
  • GetAllAsync - This is a method that gets all the posts. You can pass in an optional category parameter that will get all the posts linked to a particular category, and this is used on the category page. Using it as a parameterless method is designed to show all posts on the home page.

In addition, we have created a static EnabledPosts method. With this method, it ensures that we only return posts that have been enabled, published and not deleted.

// IPostService.cs
using RoundTheCode.Blazor.Data.PostSection;
using System.Collections.Generic;
using System.Threading.Tasks;
 
namespace RoundTheCode.Blazor.Services.PostSection
{
	public partial interface IPostService
	{
		Task<Post> GetAsync(int id);

		Task<IEnumerable<Post>> GetAllAsync(int? categoryId = null);
	}
}
// PostService.cs
using Microsoft.EntityFrameworkCore;
using RoundTheCode.Blazor.Data;
using RoundTheCode.Blazor.Data.PostSection;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
 
namespace RoundTheCode.Blazor.Services.PostSection
{
	public class PostService : IPostService
	{
		protected readonly RoundTheCodeBlazorDbContext _context;

		public PostService([NotNull] RoundTheCodeBlazorDbContext context)
		{
			_context = context;
		}

		public virtual async Task<Post> GetAsync(int id)
		{
			return await _context.Set<Post>().EnabledPosts().FirstOrDefaultAsync(post => post.Id == id);
		}

		public virtual async Task<IEnumerable<Post>> GetAllAsync(int? categoryId = null)
		{
			var query = _context.Set<Post>().Include(post => post.PostCategories).ThenInclude(postCategory => postCategory.Category).AsQueryable().EnabledPosts();

			if (!categoryId.HasValue)
			{
				return await query.ToListAsync();
			}

			return await query.Where(post => post.PostCategories.Any(postCategory => postCategory.CategoryId == categoryId && !postCategory.Deleted.HasValue && postCategory.Category != null && postCategory.Category.Enabled && !postCategory.Category.Deleted.HasValue)).ToListAsync();
		}
	}

	public static class PostServiceHelpers {
		public static IQueryable<Post> EnabledPosts(this IQueryable<Post> query)
		{
			return query.Where(post => post.Enabled && post.Published.HasValue && !post.Deleted.HasValue);
		}        
	}
}

Adding Services to Dependency Injection

Now it's a case of adding these services to Dependency Injection so we can inject them into our Razor components. We open up the Startup class and add the following lines to our ConfigureServices method:

// Startup.cs
services.AddTransient<ICategoryService, CategoryService>();
services.AddTransient<IPostService, PostService>();

That's it. We can now inject these services into our Blazor application.

See the Services Run In Blazor

You can see a demonstration of injecting the services that we have created in this article. The services will be injected into a Razor component, and a method within a service will be called to return data from the database to our application, via Entity Framework.

Next Steps

Now that we've built up our queries for Entity Framework, we can go ahead and build up our blog. In the next addition, we create a Category and Post Razor component that will be used in our Blazor application.