Entity Framework is an object-relational mapper (ORM) tool. With time saving auto-generated code, support of LINQ and ease in unit testing makes it natural choice of .NET developers to work with databases. Entity Framework Core or EF Core is its newest version, “lightweight, extensible, open source and cross-platform”.
In this post, I’ll share my experience with *ebook is huge. To explain it better, I trimmed it down for a smaller application but kept the concepts intact.
I used this architecture in many projects. As application grows bigger and more developers join the team, then one really starts appreciating it’s separation into different projects, rules and patterns. This architecture implements *Repository *and *Service *pattern, uses *Dependency Injection and fulfils SOLID principles.
In this article we’ll use EF Core to
We’ll have two more projects (Class Library) in our application dealing with database using EF Core.
This project will hold the business logic, comprising
This is data layer. Everything to connect with database (DbContext) to repositories will be dealt here. It’ll have
Add new Class Library project with name Library.Core
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Library.Core.SharedKernal
{
public abstract class BaseEntity
{
[Key, Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime CreatedDate { get; set; } = DateTime.UtcNow;
public DateTime? ModifiedDate { get; set; } = null;
public string CreatedBy { get; set; }
public string ModifiedBy { get; set; } = null;
public bool IsDeleted { get; set; } = false;
}
}
All entities will inherit this base entity class
using Library.Core.Entities;
using Microsoft.EntityFrameworkCore;
namespace Library.Infrastructure.Data
{
public class LibraryDbContext : DbContext
{
public DbSet<Author> Authors { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<Category> Categories { get; set; }
public LibraryDbContext() { }
public LibraryDbContext(DbContextOptions<LibraryDbContext> options) : base(options) { }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (optionsBuilder.IsConfigured == false)
{
var connectionString = "Server=(localdb)\\MSSQLLocalDB; Database=LibraryDb; Integrated Security=True;";
optionsBuilder.UseSqlServer(connectionString);
}
}
}
}
To make changes to our database schema whenever there’s change in our model (entities), we use Entity Framework Migrations feature.
In *Library.WebApi *project, add reference of *Library.Infrastructure *project
Open *Package Manager Console, *select *Library.Infrastructure *as *Default Project and *
PM> Add-Migration initial
PM> Update-Database
As database is created, we got all the tables we were seeking to have in it. Let’s seed database with some demo data.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Author>().HasData(
new Author
{
Id = 1,
FirstName = "Elizabeth",
LastName = "Gilbert"
},
new Author
{
Id = 2,
FirstName = "Alex",
LastName = "Michaelides"
},
new Author
{
Id = 3,
FirstName = "Jayson",
LastName = "Greene"
},
new Author
{
Id = 4,
FirstName = "Jennifer",
LastName = "Weiner"
},
new Author
{
Id = 5,
FirstName = "Yangsze",
LastName = "Choo"
},
new Author
{
Id = 6,
FirstName = "Taylor",
LastName = "Jenkins Reid"
});
modelBuilder.Entity<Category>().HasData(
new Category { Id = 1, CategoryName = "Novel" },
new Category { Id = 2, CategoryName = "Thriller" },
new Category { Id = 3, CategoryName = "Memoir" }
);
modelBuilder.Entity<Book>().HasData(
new Book { Id = 1, BookName = "City of Girls", AuthorId = 1, CategoryId = 1 },
new Book { Id = 2, BookName = "The Silent Patient", AuthorId = 2, CategoryId = 2 },
new Book { Id = 3, BookName = "Once More We Saw Stars", AuthorId = 3, CategoryId = 3 },
new Book { Id = 4, BookName = "Mrs. Everything", AuthorId = 4, CategoryId = 1 },
new Book { Id = 5, BookName = "The Night Tiger", AuthorId = 5, CategoryId = 1 },
new Book { Id = 6, BookName = "Daisy Jones & The Six", AuthorId = 6, CategoryId = 1 }
);
}
PM> Add-Migration SeedData
PM> Update-Database
Database has seeded, now we’ll fetch this data in our webAPI and further make it available for UI application to display it. Repositories hold data access code. In this application we’ll see the magic of generic repositories, i-e repository that’ll take entity as a parameter and perform data access operations on it.
I know there’s in favour and against discussion in developers circles about repository pattern and sepcifically do we really need it in EF Core. I must admit, I’m in favour of this pattern. And in this example we’ll experience a working example on how repository and service pattern can result in
And how we can have different repositories and services for our Read and *Write *operations to achieve maximum performance, still requiring very less code change in case of change in database technology and architecture.
Service layer consumes repository and exposes business logic.
Best architectures adhere to SOLID Principles. Last thing any architect wants is need to change in classes (specifically core classes) whenever we want to query data with different predicate. That’s where this specification part of *Core *project comes to help.
using Library.Core.Entities;
namespace Library.Core.Specifications.Books
{
public class BookWithAuthorAndCategorySpecification : BaseSpecification<Book>
{
public BookWithAuthorAndCategorySpecification() : base()
{
AddInclude(b => b.Author);
AddInclude(b => b.Category);
}
}
}
Now when we can have multiple *specifications *without making changes in our repository. Just against any entity we’ll have a new specification class and call repository Get method with that class in the Service.
public async Task<IEnumerable<Book>> GetAllBooksAsync()
{
var spec = new BookWithAuthorAndCategorySpecification();
return await repository.ListAsync(spec);
}
// Add DbContext
services.AddDbContext<LibraryDbContext>(cfg =>
{
cfg.UseSqlServer(
Configuration
.GetConnectionString("LibraryConnectionString"));
});
// Add Repository
services.AddScoped<IGenericReadRepository, GenericReadRepository>();
// Add Books Service
services.AddScoped<IBookService, BookService>();
This will make BookService injectable in controller
In *Library.WebApi *project add following package.
<PackageReference Include="AutoMapper" Version="9.0.0" />
Automapper is a widely used utility, which frees coders from mapping ViewModels to *Models *in controllers. This is a very useful tutorial in case you are new to that.
Automapper also requires to be added in *ConfigureServices *method
//Add AutoMapper
services.AddAutoMapper(typeof(Startup));
Now BooksController code in webAPI will look like that
using AutoMapper;
using Library.Core.Interfaces;
using Library.WebApi.Resources;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Library.WebApi.Controllers
{
[Authorize]
public class BooksController : BaseApiController
{
private readonly ILogger<BooksController> logger;
private readonly IMapper mapper;
private readonly IBookService service;
public BooksController(ILogger<BooksController> logger,
IMapper mapper,
IBookService service)
{
this.logger = logger;
this.mapper = mapper;
this.service = service;
}
[HttpGet]
[ProducesResponseType(typeof(BookResultResource), 200)]
public async Task<IActionResult> Get()
{
try
{
logger.LogInformation("In BooksController Get");
var bookResource = new BookResultResource()
{
UserFullName = GetUserInfo("name"),
UserName = GetUserInfo("preferred_username"),
Books =
mapper.Map<IEnumerable<BookResource>>(await service.GetAllBooksAsync())
};
return Ok(bookResource);
}
catch (Exception ex)
{
logger.LogError($"Error in BooksController: {ex.Message}");
return BadRequest($"{BadRequest().StatusCode} : {ex.Message}");
}
}
}
}
We won’t need any change in this project, just build and run as usual like previous post. And this time by clicking *“Fetch Books” *data will be served from database through Library.WebApi BooksController.
Clone following repository for code sample
Please don’t forget to add Application IDs for your UI and WebApi projects in authprovider.js and appsettings.json respectively.
https://github.com/AhsanRazaUK/webapi-ef-core
So, what we learnt in this article
And as usual, feel free to contact me if this all went well for you? Happy coding!
How to make Web API with ASP.NET Core 3.0 and MongoDB
#aspnet #asp.net #aspnetcore