본문 바로가기

Backend/.NET

Relational DB - Getting Data including Data from Other Entities (Generic Repository Pattern)

반응형

The Generic repository pattern enables us to dynamically change the type so that we don't have to create duplicate methods. However, one downside is that we can use features provided by Entity Framework such as 'Include' to show the data of related entities in the response. But, as always, there is a way around this, and today we will use a pattern called 'Specification'.

List of Contents

Project Set Up

Creating Application

 

How to create .NET web-API

Setting up development tools When working with .NET, we need tools to create a web application. We can download them in the link below.다. .NET | Free. Cross-platform. Open Source. (microsoft.com) .NET | Free. Cross-platform. Open Source. .NET is a develo

jin-co.tistory.com

Adding Relationships to DB

 

Adding Relation to DB

Entity framework makes adding a relationship between tables easy. Let's see how it is done. Creating an Application First, let's create a .NET application. How to create a .NET web-API application (tistory.com) How to create .NET web-API Setting up develop

jin-co.tistory.com

For better demonstration, I have created three entities

▶ Adding Base Entity

We will create a common entity for all the entities we are going to use in this example. So that we can remove common properties from each entity. Another purpose of creating a base entity is because it makes it easier to specify a type limitation in the generic interface later.

 

Add a new class under the entity folder

Add the common properties in the base entity and remove them from each entity

Add the base entity as a parent to each entity

▶ Data Files for Testing

You can download the seed data for testing

item.json
0.00MB
itemTarget.json
0.00MB
itemType.json
0.00MB

 

Repository Pattern

 

Application Architecture - Repository with Service

An Interface is a kind of contract that specifies what the application needs. Let's see how we can use an interface with the repository pattern. Project Configurations Creating a Project with MVC Pattern Server Architecture - Distributing Projects Server S

jin-co.tistory.com

Generic Repository Pattern

 

Application Architecture - Generic Repository

The repository pattern has a static type so whenever we create an entity, we have to create a repository as well. A generic is a way to restrict to a type or to dynamically change types. Let's see how we can use a generic repository for multiple entities P

jin-co.tistory.com

▶ Updating Type Limitation in the Generic Interface

Go to the interface class under the core folder and update the type limitation to the base entity

Updating Type Limitation in the Generic Repository

Go to the repository class under the infrastructure folder and update the type limitation to the base entity

Implementation

Adding Methods in the Interface

Replace the previous methods that each gets a list of entities with one method that uses generic with dynamic type <T>

Implementing Interface

Go to the repository and implement the methods

Then update the method with the desired code. Use the code below to set a dynamic type

.Set<T>()

Adding Services

Register the service in the Program.cs file as shown below 

builder.Services.AddScoped(typeof(IGenericRepo<>), typeof(GenericRepo<>));

Updating the Controller

Add additional endpoints for added entities. To specify the path use parenthesis and put a path in it as a string

[HttpGet("targets")]

Go to the controller class and inject a generic interface repository for each entity separately to specify a type for each request and get the desired result

private readonly IGenericRepo<Item> _itemRepo;
private readonly IGenericRepo<ItemTarget> _itemTargetRepo;
private readonly IGenericRepo<ItemType> _itemTypeRepo;
public StoreController(
  IGenericRepo<Item> itemRepo, 
  IGenericRepo<ItemTarget> itemTargetRepo, 
  IGenericRepo<ItemType> itemTypeRepo)
{
  this._itemTypeRepo = itemTypeRepo;
  this._itemTargetRepo = itemTargetRepo;
  this._itemRepo = itemRepo;
}

Update the methods

Before
After

Run

Move to the API folder

cd /API

And run the app

dotnet watch

You will see added endpoints right away like below if you are using Swagger

Open each and try

Getting Referenced Data from the Primary Entity with a Specification Pattern

As you can see, referenced data in the primary entity show a 'null' value. We need an additional step to show their data. The thing is, however, with the generic pattern, we cannot use the 'Include' syntax directly from within the generic repository method.

A way to get around this issue is to use a pattern called 'specification'. Specification pattern allows us to limit the scope of the generic pattern and to add options. Let's see how it is done.

 

First, we need to change our request to an asynchronous way.

Asynchronous Request

 

 

Relational DB - Getting Data (Asynchronous Generic Repository Pattern)

Let's see how we can get data asynchronously using Entity Framework. Setting Up a Project Creating an Application How to create .NET web-API Setting up development tools When working with .NET, we need tools to create a web application. We can download the

jin-co.tistory.com

Creating a Specification

Create a folder to hold specifications classes

Add a specification interface in the folder

Add generic type to the interface

Add methods that return a type with a generic expression (here I have added one for getting an individual item and the other for getting a list of items)

Expression<Func<T, bool>> Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }

▶ Base Specification

Add a class file that we will use as the base

Implement the interface

Update the methods with {get;} at the end. For the include method, initialize it as shown below. And add two constructors with and without a parameter

new List<Expression<Func<T, object>>>();

Add another method that includes entities

protected void AddInclude(Expression<Func<T, object>> includeExpression) {
  Includes.Add(includeExpression);
}

Go to the infrastructure folder and create a class that we will use to include entities

Add a generic type and a type condition

Add the static method as shown below

public static IQueryable<T> GetQuery(IQueryable<T> input, ISpec<T> spec)
{
  var query = input;
    if (spec.Criteria != null)
  {
    query = query.Where(spec.Criteria);
  }
  query = spec.Includes.Aggregate(query, (entity, expression) => entity.Include(expression));
  return query;
}

Go to the generic interface and add the specification as a parameter to the methods 

Task<List<T>> GetListAsync(ISpec<T> spec);
Task<T> GetItem(ISpec<T> spec);

Go to the generic repository

Add another method that applies the specification

private IQueryable<T> ApplySpec(ISpec<T> spec)
{
  return SpecEvaluator<T>.GetQuery(_context.Set<T>().AsQueryable(), spec);
}

Update the existing methods with the specification parameter and use the newly created method to pass the specification

public async Task<List<T>> GetListAsync(ISpec<T> spec)
{
  return await ApplySpec(spec).ToListAsync();
}

▶ List Specification (Include)

Create an item specification class in the core folder

Inherit from the base specification

Add a constructor that includes entities

public ItemWithTargetAndTypeSpec()
{
    AddInclude(x => x.ItemTarget);
    AddInclude(x => x.ItemType);
}

Updating the Controller

Go to the controller class and update the method that gets a list of items like the one below.

[HttpGet]
public async Task<List<Item>> GetItems()
{      
  var spec = new ItemWithTargetAndTypeSpec();
  return await _itemRepo.GetListAsync(spec);
}

▶ Getting an Individual item

Let's see how we can get an item too.

 

Update the 'GetItem' method that we implemented above to use the specification method

public async Task<T> GetItem(ISpec<T> spec)
{
  return await ApplySpec(spec).FirstOrDefaultAsync();
}

Go to the individual specification class and add the constructor with the parameter we created earlier

Replace the automatically added parameter with an id of an int type. Then add code that includes referenced entities

public ItemWithTargetAndTypeSpec(int id) : base(x => x.Id == id) {
  AddInclude(x => x.ItemTarget);
  AddInclude(x => x.ItemType);
}

Go to the controller class and add an endpoint with an id parameter. Instantiate the individual specification and pass it as a parameter to the generic method that gets an item

[HttpGet("{id}")]
public async Task<ActionResult<Item>> GetItem(int id)
{
  var spec = new ItemWithTargetAndTypeSpec(id);
  return await _itemRepo.GetItem(spec);
}

We have seen how we can get data from an entity that has reference to other entities with a generic pattern.

 

728x90
반응형