0% found this document useful (0 votes)
54 views

Exploring ASP - NET Core Fundamentals - Repository Pattern and Usage in EF Core - Referbruv CodeBlog

The document discusses implementing a repository pattern with Entity Framework Core. It describes how injecting a DbContext directly into controllers leads to logic issues. The repository pattern solves this by: 1. Defining a data interface and implementation class that handles data logic separately from controllers. 2. Controllers interact only with the interface, keeping data logic decoupled. 3. This makes the code cleaner, more reusable, and maintainable even if the data implementation changes.

Uploaded by

Constantin Turta
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
54 views

Exploring ASP - NET Core Fundamentals - Repository Pattern and Usage in EF Core - Referbruv CodeBlog

The document discusses implementing a repository pattern with Entity Framework Core. It describes how injecting a DbContext directly into controllers leads to logic issues. The repository pattern solves this by: 1. Defining a data interface and implementation class that handles data logic separately from controllers. 2. Controllers interact only with the interface, keeping data logic decoupled. 3. This makes the code cleaner, more reusable, and maintainable even if the data implementation changes.

Uploaded by

Constantin Turta
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 7

Exploring ASP.

NET Core Fundamentals -


Repository Pattern and Usage in EF
Core
ASP.NET Core   •  Posted one year ago

In the  previous article  we discussed in detail about the Entity Framework Core
ORM (Object Relational Mapper) developed for .NET Core and how we can inject
the DatabaseContext or the DbContext instance extended by a custom class
which holds the bridge between the .NET Core business layer and the back-end
database by means of DbSets of entity classes which form the code-first approach
of defining and mapping a database object to a class object. We also discussed
that injecting a DbContext object directly into business logic is not a good
practice and we would require a Repository pattern to implement the same. In
this article we will look into what a Repository pattern is and how we can
implement a Repository pattern onto an Entity Framework model and what
advantages it provides over a non-pattern approach.

What is a Repository Pattern?

Let's start from the question of what really a design pattern is. A Design Pattern is
a structural solution designed for a distinct set of problems which can hinder code
structure, behaviour and finally performance. By using a particular design pattern
while programming a solution, we ensure that the code structure and hierarchy
we create is free of a particular problem that design pattern was brought in to
solve. The first step in implementing a design pattern is in identifying the flaw in
the existing code and selecting the respective pattern to implement to overcome
it. Sometimes we use design patterns subconsciously, we don't know that we're
actually implementing a pattern and yet we tend to code in that way. Having a
basic idea of design patterns basically helps code better and thereby improves
maintainability and scalability. In dotnet core, we already implement Singleton
pattern (by defining the lifetimes of services or class objects which we induct into
pipeline and later inject when necessary) and we use Factory patterns as well (for
example the IHttpClientFactory we discussed earlier for handling HttpClient
objects). Although we don't actually implement the behavior, by using the
libraries that dotnet core provides for the respective functionalities, we actually try
to mimic them in our structure. One of the 23 design patterns developed for
various programming problems, the Repository pattern we are about to
implement solves the basic problem of logical abstraction or logical partitioning.
Let's use the same example we've discussed earlier,
public MyController : Controller
{
DatabaseContext context;

public MyController(DatabaseContext ctx) {

this.context = ctx;

public void Query()


{
var records = context.Users.Where(X => x.Id == 1).FirstOrDefault();
}

public void Insert()


{
var user = new User { Name = "Alan" };

context.Users.Add(user);
context.SaveChanges();
}

public void Update()


{
var record = context.Users.FirstOrDefault(x => x.Id == 1);
record.Name = "Bibo";
context.SaveChanges();
}

public void Delete()


{
var record = context.Users.FirstOrDefault(x => x.Id == 1);
context.Users.Remove(record);
context.SaveChanges();
}
}

In the above example, we directly implement the logic of a CRUD operation


(Create, Retrieve, Update and Delete) on the database using the injected
DbContext object (context of type DatabaseContext) in the Controller class. We
need to realize a point here that, the main purpose of a Controller class is to
handle View related or View Model related logic for a given request. Which is the
concern of the class MyController. And by writing down the logic for data
handling which isn't the concern of a Controller, we defeat the purpose of a
Logical abstraction or simply called Separation of Concerns (SoC). Also, let's
assume that there's a data logic on the context entity Users which is used by three
controllers OneController, TwoController and ThreeController. If we go by the
above approach, we end up rewriting the same logic in three different places
which defeats the purpose of code reuse and maintainability. This problem will be
solved by Repository pattern which "Separates the Data Logic from the Business
Logic and lets the Business Logic utilize an object of the implementor which
implements the features stated by a Definition."

Implementing a Repository Pattern:

We start by identifying all the reusable Data Logic, which we shall define using an
interface.

public interface IDataService {


List<User> Query(string name);
int Create(string name);
void Update(string name);
void Delete(int Id);
}

The above interface defines four basic data functionalities which are needed by all
the controllers. Next, we define an implementor that holds an implementation of
the above definition.
public class DataService : IDataService {

DatabaseContext context;

public DataService(DatabaseContext context) {


this.context = context;
}

public List<User> Query(string name) {


return context.Users.Where(x => x.Name == name).ToList();
}

public int Create(string name) {


var user = new User { Name = name };
context.Users.Add(user);
context.SaveChanges();
return user;
}

public void Update(string name) {


var user = this.Query(name).FirstOrDefault();
if(user != null)
user.Name = name;
context.SaveChanges();
}

public void Delete(int Id) {


context.Users.Remove(context.Users.FirstOrDefault(x => x.Id == Id));
context.SaveChanges();
}
}

Now that we have an implementation of our DataService, we can simply use this
DataService object in our controller class for handling data logic without the need
for actually having any logic within the controller.
public class MyController : Controller {

IDataService data;

public MyController(DatabaseContext context)


{
this.data = new DataService(context);
}

public void Query(string name)


{
// returns list of matching users
var users = data.Query(name);
}

public void Insert(string name)


{
// returns the created user Id
var userId = data.Create(name);
}

public void Update(string name)


{
// updates the name of the user
data.Update(name);
}

public void Delete(int id)


{
// deletes the user bearing the id
data.Delete(id);
}
}

If we compare the controller before implementing pattern and after implementing


the pattern, we see that the code is a lot more cleaner and understandable. One
more advantage of this approach is that the consumer logic doesn't need to know
the underlying service logic, which means that later if we decide to move over to
another database logic or ORM, still the controller logic works in the same way;
we just need to change the underlying implementation. The above code can be
further more simplified by inducting DataService into pipeline as an injectable.
public class Startup {

public void ConfigureServices(IServiceCollection services) {

services.AddDbContext<DatabaseContext>(
options => {
//connection string logic
});
services.AddScoped<IDataService, DataService>();
}

...

It can be observed here that the service is defined with a Scoped lifetime, because
as we discussed earlier the DbContext service  is of SCOPED lifetime and
CANNOT be injected in a SINGLETON service.

now the controller logic can be changed as:

public class MyController : Controller {

IDataService data;

public MyController(IDataService data)


{
this.data = data;
}

...
}

This provides a decoupled code structure, since we can choose the


implementation of the IDataService at runtime and the respective implementation
will be used across the pipeline.

You might also like