Menu Close

CRUD operation using the repository pattern with .Net 8, Ef-Core, and MySQL

In this article, we will learn about CRUD operation using the repository pattern with .Net 8, Ef-Core, and MySQL. We explored the Repository pattern using .NET Core and SQL Server in a previous post (How to Implement Repository Pattern in ASP.Net Core Web API); in this piece, we will connect to the MySQL database to familiarize ourselves with it. Please read my previous article on How to use Response Compression in .NET Core.

What is a Repository Pattern?

The repository pattern is a class in your Infrastructure layer that lets you implement your data’s durability and has several advantages in your code structure. Repositories are classes that mask the logic used to store and retrieve data. As a result, our application will not care which ORM we choose because everything relating to the ORM is handled within a repository layer.

Repository Pattern

Creating an ASP.Net Core Web API Project

Here I’m creating an API of Music List so I can further use it in any of my applications, here we implement the repository pattern. The project uses .NET SDK 8

Let’s add two more.NET Core Class Library Projects to the solution. We’ll call them DataAccessEFCore and Domain. Here are the features and objectives of each project.

  • Domain – Contains the Entities and Interfaces. It is not dependent on any other projects in the solution.
  • DataAccessEFCore – Because we will be interacting with our database using Entity Framework Core – Code First, let us create a project that only contains EFCore-related files. The goal is to make it easy to construct another Data Access layer, such as DataAccess, in the future Dapper or so. And our app would still support it. Here’s where Dependency Inversion comes into play.
  • RepositoryPattern.API – This functions as the overall solution’s presentation layer. It depends on both projects.

Setting up the Entities and EFCore

Step: 1

Now, let’s add the Required Entities to the Domain Project. Create a new Folder in the Domain Project named Entities.

  • Here we have created the entities named  Movie.cs that hold the movie information along with the image property.
public class Movie
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string MovieType { get; set; }
    public string Duration { get; set; }
    public string Language { get; set; }
    public string Description { get; set; }

    [DataType(DataType.Date)]
    public DateTime? ReleaseDate { get; set; }

    [NotMapped]
    public IFormFile? Images { get; set; }

    [JsonIgnore]
    public string? ImagePath { get; set; }
    public DateTime? CreatedDate { get; set; }

    public int? CreatedById { get; set; }
    public DateTime? UpdatedDate { get; set; }

    public int? UpdatedById { get; set; }
}

 Install Required Packages

Since the Entity Framework Migration operation we want on the DataAccessEFCore Library project, so open your project in Visual Studio -> Tools -> NuGet Package Manager-> Manage NuGet Packages for Solution…

  • Microsoft.EntityFrameworkCore (In this project we are using version 8.0.7)
  • Microsoft.EntityFrameworkCore.Design (In this project we are using version 8.0.7)
  • Microsoft.EntityFrameworkCore.Relational (In this project we are using version 8.0.7)
  • Microsoft.EntityFrameworkCore.Tools (In this project we are using version 8.0.7)
  • Pomelo.EntityFrameworkCore.MySql (In this project we are using version 8.0.7)

Step-2

  • Add a reference to the Domain Project (where we have defined our entities) create a new Class in the DataAccessEFCore Project and Name it ApplicationContext.cs.
    1. Here we have added the DbContext with DbSet as Movies.
    2. By default, we set the created date and updated the date as the current date time.
public class ApplicationContext : DbContext
{
    public ApplicationContext(DbContextOptions<ApplicationContext> options) : base(options)
    {
    }
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Movie>()
                    .Property(b => b.CreatedDate)
                    .HasDefaultValue(DateTime.Now);

        modelBuilder.Entity<Movie>()
                    .Property(b => b.UpdatedDate)
                    .HasDefaultValue(DateTime.Now);
    }
    public DbSet<Movie> Movies { get; set; }
}

Once our Data Access Layer is done, let’s move to the RepositoryPattern Project to register EFCore within the ASP.NET Core Application. Firstly, Install this package on the RepositoryPattern Project. This allows you to run EF Core commands on the CLI.

Install-Package Microsoft.EntityFrameworkCore.Tools

Open up the appsettings.json file in the RepositoryPattern Project and add the connection string like below, for your case you can add the MySQL connection accordingly.

"ConnectionStrings": {
  "DefaultConnection": "server=localhost;port=3306;database=MoviesDB;user=root;password=Password!1"
}

Navigate to Program.cs and add this line to Register the ApplicationContext class that we created. Note that you will have to add a reference to the DataAccessEFCore Project to the RepositoryPattern Project.

// Register ApplicationContext with the dependency injection container
builder.Services.AddDbContext<ApplicationContext>(options =>
    options.UseMySql(builder.Configuration.GetConnectionString("DefaultConnection"), new MySqlServerVersion(new Version(8, 0, 23))));
  • Finally, Let’s update the database. Open your Package Manager Console on Visual Studio and run the following commands.
  • Make sure that you have set your Startup Project  RepositoryPattern and the Default Project as DataAccessEFCore. Here is a screenshot below, you can see after the command ran Migrations is completed the database is created and we can find the table name as Movies.
ef-migration

Building a Generic Repository in Repository Pattern

Under the Domain project, we have created a folder named as Interfaces and created the interface as IRepositoryBase.

Why do we create an Interface under Domain Project?

We will be inverting the dependencies, so that, you can define the interface in the Domain Project, but the implementation can be outside the Domain Project. In this case, the implementations will go to the DataAccess.EFCore Project. Thus, your domain layer will not depend on anything, rather, the other layers tend to depend on the Domain Layer’s interface. This is a simple explanation of the Dependency Inversion Principle

public interface IRepositoryBase<T> where T : class
    {
        Task<IEnumerable<T>> GetAll();
        Task<T> GetById(int id);
        void Create(T entity);
        void Update(T entity);
        void Delete(T entity);
    }

This is the Generic Interface, that exposes the functions.

  • Task<IEnumerable<T>> GetAll() – Get all the lists.
  • Task<T> GetById(int id) – Get the list by Id
  • void Create(T entity) – Adds new record into context
  • void Update(T entity) – Update the record into context
  • void Delete(T entity) – Removes the record from the context

Now, Let’s implement this Interfaces. Create a new class in the DataAccessEFCore Project and name it Repositories/GenericRepository

public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
    {
        protected readonly ApplicationContext _context;
        public RepositoryBase(ApplicationContext context)
        {
            _context = context;
        }
        public async Task<IEnumerable<T>> GetAll() => _context.Set<T>().AsNoTracking();

        public async Task<T> GetById(int id) => await _context.Set<T>().FindAsync(id);
        public void Create(T entity) => _context.Set<T>().Add(entity);

        public void Update(T entity) => _context.Set<T>().Update(entity);

        public void Delete(T entity) => _context.Set<T>().Remove(entity);
 
    }

This class will implement the IGenericRepository Interface. We will also inject the ApplicationContext here. This way we are hiding all the actions related to the dbContext object within Repository Classes. Also note that for the ADD and Remove Functions, we just do the operation on the dbContext object. However we are not yet committing/updating/saving the changes to the database.

Similarly, let’s create an interface and implementation for MovieRepository.

public interface IMoviesRepository : IRepositoryBase<Movie>
    {
    }
public class MoviesRepository : RepositoryBase<Movie>, IMoviesRepository
    {
        public MoviesRepository(ApplicationContext applicationContext)
            : base(applicationContext)
        {
        }
    }

Create the Wrapper class to implement the Repository

We have created the interface under the Domain Project

public interface IRepositoryWrapper
    {
        IMoviesRepository Movie { get; }
        void Save();
    }
  • Here we are injecting a private AppplicationContext.
public class RepositoryWrapper: IRepositoryWrapper
    {
        private ApplicationContext _appContext;
        private IMoviesRepository _movie;
        public IMoviesRepository Movie
        {
            get
            {
                if (_movie == null)
                {
                    _movie = new MoviesRepository(_appContext);
                }
                return _movie;
            }
        }
        public RepositoryWrapper(ApplicationContext applicationContext)
        {
            _appContext = applicationContext;
        }

        public void Save()
        {
            _appContext.SaveChanges();
        }
    }

Finally, let’s register these services, To register the service we have created the class under RepositoryPattern.API as ServiceExtensions API.

Here registered the IRepositoryWrapper and also used IPostService that is used for saving the image file that we will discuss below.

public static class ServiceExtensions
    {
        public static void ConfigureCors(this IServiceCollection services)
        {
            services.AddCors(options =>
            {
                options.AddPolicy("CorsPolicy",
                    builder => builder.AllowAnyOrigin()
                    .AllowAnyMethod()
                    .AllowAnyHeader());
            });
        }

        public static void ConfigureIISIntegration(this IServiceCollection services)
        {
            services.Configure<IISOptions>(options =>
            {

            });
        }
        public static void ConfigureRepositoryWrapper(this IServiceCollection services)
        {
            services.AddScoped<IRepositoryWrapper, RepositoryWrapper>();
            services.AddTransient<IPostService, PostService>();
        }
    }

After this implement this extension class under the program file. Added the service implementation builder.Services.ConfigureRepositoryWrapper().

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.ConfigureCors();
builder.Services.ConfigureIISIntegration();
builder.Services.ConfigureRepositoryWrapper();

// Register ApplicationContext with the dependency injection container
builder.Services.AddDbContext<ApplicationContext>(options =>
    options.UseMySql(builder.Configuration.GetConnectionString("DefaultConnection"), new MySqlServerVersion(new Version(8, 0, 23))));

var app = builder.Build();

Create the File Extensions to save the Image

We have created this CRUD where we want to save the image file.

public interface IPostService
    {
        Task SavePostImageAsync(Movie _movie);
    }

The base response contains the error, success, and error code if the image post-response failed.

public class BaseResponse
    {
        [JsonIgnore(Condition = JsonIgnoreCondition.Always)]
        public bool Success { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public string ErrorCode { get; set; }
        [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
        public string Error { get; set; }
    }
public class PostResponse : BaseResponse
    {
        public Movie Post { get; set; }
    }

This FileHelper class generates the unique ID that we can use to rename the Image.

public class FileHelper
    {
        public static string GetUniqueFileName(string fileName)
        {
            fileName = Path.GetFileName(fileName);
            return string.Concat(Path.GetFileNameWithoutExtension(fileName)
                                , "_"
                                , Guid.NewGuid().ToString().AsSpan(0, 4)
                                , Path.GetExtension(fileName));
        }
    }
  • Here in the PostService, we implemented the IWebHostEnvironment that is responsible for getting the content root path where we want to save the image file.
  • We are going to save the image file under the Images folder in RepositoryPattern.API Project
public class PostService : IPostService
    {
        private readonly IWebHostEnvironment environment;
        public PostService(IWebHostEnvironment environment)
        {
            this.environment = environment;
        }
        public async Task SavePostImageAsync(Movie _movie)
        {
            var uniqueFileName = FileHelper.GetUniqueFileName(_movie.Images.FileName);

            var uploads = Path.Combine(environment.ContentRootPath, "Images");

            var filePath = Path.Combine(uploads, uniqueFileName);

            Directory.CreateDirectory(Path.GetDirectoryName(filePath));

            await _movie.Images.CopyToAsync(new FileStream(filePath, FileMode.Create));

            _movie.ImagePath = uniqueFileName;

            return;
        }
    }

Make sure to enable the below middleware

app.UseStaticFiles();

Create the Movies Controller

  • Here we have created the Controller that will implement the repository pattern and will handle the CRUD.
  • We have used dependencies of the Repository wrapper and Postservice for saving the image.
  • We have created HTTP GET, HTTP POST, HTTP PUT, and HTTP DELETE methods to handle the CRUD operation.

[Route("api/[controller]")]
    [ApiController]
    public class MoviesController : ControllerBase
    {
        private IRepositoryWrapper _repository;
        private readonly ILogger<MoviesController> logger;
        private readonly IPostService postService;
        public MoviesController(IRepositoryWrapper repository, IPostService postService, ILogger<MoviesController> logger)
        {
            _repository = repository;
            this.postService = postService;
            this.logger = logger;
        }

        [HttpGet]
        public async Task<IActionResult> GetAsync()
        {
            var movies = await _repository.Movie.GetAll();
            return Ok(movies);
        }

        [HttpGet]
        [Route("getbyId/{id}")]
        public async Task<IActionResult> GetByIdAsync(int id)
        {
            var movies = await _repository.Movie.GetById(id);
            return Ok(movies);
        }

        [HttpPost]
        [RequestSizeLimit(5 * 1024 * 1024)]
        public async Task<IActionResult> PostAsync([FromForm] Movie movieRequest)
        {
            if (movieRequest == null)
            {
                return BadRequest(new PostResponse { Success = false, ErrorCode = "S01", Error = "Invalid post request" });
            }

            if (string.IsNullOrEmpty(Request.GetMultipartBoundary()))
            {
                return BadRequest(new PostResponse { Success = false, ErrorCode = "S02", Error = "Invalid post header" });
            }

            if (movieRequest.Images != null)
            {
                await postService.SavePostImageAsync(movieRequest);
            }
            _repository.Movie.Create(movieRequest);
            _repository.Save();
            return Ok();
        }


        [HttpPut("{id:int}")]
        public async Task<IActionResult> PutAsync(int id, [FromForm] Movie movieRequest)
        {

            try
            {
                if (id != movieRequest.Id)
                    return BadRequest("ID mismatch");

                var movieToUpdate = await _repository.Movie.GetById(id);

                if (movieToUpdate == null)
                    return NotFound($"Movie with Id = {id} not found");

                movieToUpdate.Name = movieRequest.Name;
                movieToUpdate.MovieType = movieRequest.MovieType;
                movieToUpdate.Duration = movieRequest.Duration;
                movieToUpdate.Language = movieRequest.Language;
                movieToUpdate.Description = movieRequest.Description;
                movieToUpdate.ReleaseDate = movieRequest.ReleaseDate;
                if (movieRequest.ImagePath != null){
                    movieToUpdate.ImagePath = movieRequest.ImagePath;
                    await postService.SavePostImageAsync(movieRequest);
                }
                movieToUpdate.UpdatedDate = DateTime.Now;
                _repository.Movie.Update(movieToUpdate);
                _repository.Save();
                return Ok(movieToUpdate);
            }
            catch (Exception)
            {
                return StatusCode(StatusCodes.Status500InternalServerError,
                    "Error updating data");
            }
        }
    
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteAsync(int id)
        {
            var moviedetails = await _repository.Movie.GetById(id);
            if (moviedetails == null)
            {
                return NotFound();
            }
            _repository.Movie.Delete(moviedetails);
            _repository.Save();
            return Ok("Record Deleted Successfully");
        }
    }

Testing with POSTMAN

Adding the Movie Information using POST

  • Here we have called the as api/movies and used the form-data as we were posting the image into the POST
  • Make sure in the images, choose the dropdown as File instead of Text and select the files where it is located.
  • Make sure the Content-Type as multipart/form-data as we are dealing with form-data.
post-operation
[HttpPost]
[RequestSizeLimit(5 * 1024 * 1024)]
public async Task<IActionResult> PostAsync([FromForm] Movie movieRequest)
{
    if (movieRequest == null)
    {
        return BadRequest(new PostResponse { Success = false, ErrorCode = "S01", Error = "Invalid post request" });
    }

    if (string.IsNullOrEmpty(Request.GetMultipartBoundary()))
    {
        return BadRequest(new PostResponse { Success = false, ErrorCode = "S02", Error = "Invalid post header" });
    }

    if (movieRequest.Images != null)
    {
        await postService.SavePostImageAsync(movieRequest);
    }
    _repository.Movie.Create(movieRequest);
    _repository.Save();
    return Ok();
}

You can see the response status is 200 and the data are saved into the database and the images are saved into RepositoryPattern Project.

data-saved-into-db

In the same way, you can use GET, PUT & DELETE the operation.

crud-operation-postman

Benefits of Repository Pattern

  • Reduces Duplicate Queries: Consider writing lines of code just to retrieve some data from your database. What if this set of queries is going to be used repeatedly throughout the application? Isn’t it tiresome to write the same code again and over? Here is another benefit of using Repository Classes. You can construct your data access code in the repository and call it from several Controllers / Libraries.
  • Decouples the application from the data access layer: ASP.NET Core supports a wide range of ORMs. Entity Framework Core is now the most popular. However, this will change in the next years. To keep up with emerging technologies and keep our solutions current, we must create applications that can migrate to a new DataAccess Technology with minimal impact on our application’s code base.

That’s it for this article CRUD operation using the repository pattern with .Net 8.

Conclusion

In this post, we learned about CRUD operation using the repository pattern with .Net 8, Ef-Core, and MySQL. The Repository pattern increases the level of abstraction in your code. This may make the code more difficult to understand for developers who are unfamiliar with the pattern. But once you are familiar with it, it will reduce the amount of redundant code and make the logic much easier to maintain.

Leave behind your valuable queries and suggestions in the comment section below. Also, if you think this article helps you, do not forget to share this with your developer community. Happy Coding 🙂

Related Post

SUPPORT ME

Buy Me A Coffee

Leave a Reply

Your email address will not be published. Required fields are marked *