DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

How does AI transform chaos engineering from an experiment into a critical capability? Learn how to effectively operationalize the chaos.

Data quality isn't just a technical issue: It impacts an organization's compliance, operational efficiency, and customer satisfaction.

Are you a front-end or full-stack developer frustrated by front-end distractions? Learn to move forward with tooling and clear boundaries.

Developer Experience: Demand to support engineering teams has risen, and there is a shift from traditional DevOps to workflow improvements.

Related

  • Projections/DTOs in Spring Data R2DBC
  • TDD Typescript NestJS API Layers with Jest Part 3: Repository Unit Test
  • Alexa Skill With Local DynamoDB
  • Why Database Migrations Take Months and How to Speed Them Up

Trending

  • How to Master a DevSecOps Pipeline that Devs and AppSec Love
  • Why Whole-Document Sentiment Analysis Fails and How Section-Level Scoring Fixes It
  • Apache Doris and DeepSeek: Redefining Intelligent Data Analytics
  • From OCR Bottlenecks to Structured Understanding
  1. DZone
  2. Data Engineering
  3. Databases
  4. Repository Pattern, Done Right

Repository Pattern, Done Right

By 
Jonas Gauffin user avatar
Jonas Gauffin
·
Feb. 04, 13 · Interview
Likes (0)
Comment
Save
Tweet
Share
12.0K Views

Join the DZone community and get the full member experience.

Join For Free
the repository pattern has been discussed a lot lately. especially about it’s usefulness since the introduction of or/m libraries. this post (which is the third in a series about the data layer) aims to explain why it’s still a great choice.

let’s start with the definition :

a repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. client objects construct query specifications declaratively and submit them to repository for satisfaction. objects can be added to and removed from the repository, as they can from a simple collection of objects, and the mapping code encapsulated by the repository will carry out the appropriate operations behind the scenes


the repository pattern is used to create an abstraction between your domain and data layer. that is, when you use the repository you should not have to have any knowledge about the underlying data source or the data layer (i.e. entity framework, nhibernate or similar).

why do we need it?

read the abstractions part of my data layer article. it explains the basics to why we should use repositories or similar abstractions.

but let’s also examine some simple business logic:

var brokentrucks = _session.query<truck>().where(x => x.state == 1);
foreach (var truck in brokentrucks)
{
   if (truck.calculatereponsetime().totaldays > 30)
       sendemailtomanager(truck);
}

what does that give us? broken trucks?

well. no. the statement was copied from another place in the code and the developer had forgot to update the query. any unit tests would likely just check that some trucks are returned and that they are emailed to the manager.

so we basically have two problems here:

a) most developers will likely just check the name of the variable and not on the query.
b) any unit tests are against the business logic and not the query.

both those problems would have been fixed with repositories. since if we create repositories we also have unit tests which targets the data layer only.

implementations

here are some different implementations with descriptions.

base classes

these classes can be reused for all different implementations.

unitofwork

the unit of work represents a transaction when used in data layers. typically the unit of work will roll back the transaction if savechanges() has not been invoked before being disposed.

public interface iunitofwork : idisposable
{
    void savechanges();
}

paging

we also need to have page results.

public class pagedresult<tentity>
{
    ienumerable<tentity> _items;
    int _totalcount;
     
    public pagedresult(ienumerable<tentity> items, int totalcount)
    {
        _items = items;
        _totalcount = totalcount;
    }
     
    public ienumerable<tentity> items { get { return _items; } }
    public int totalcount { get { return _totalcount; } }
}

we can with the help of that create methods like:
public class userrepository
{
    public pagedresult<user> find(int pagenumber, int pagesize)
    {
    }
}

sorting

finally we prefer to do sorting and page items, right?

var constraints = new queryconstraints<user>()
    .sortby("firstname")
    .page(1, 20);
     
var page = repository.find("jon", constraints);

do note that i used the property name, but i could also have written constraints.sortby(x => x.firstname) . however, that is a bit hard to write in web applications where we get the sort property as a string.

the class is a bit big, but you can find it at github .

in our repository we can apply the constraints as (if it supports linq):

public class userrepository
{
    public pagedresult<user> find(string text, queryconstraints<user> constraints)
    {
        var query = _dbcontext.users.where(x => x.firstname.startswith(text) || x.lastname.startswith(text));
        var count = query.count();
         
        //easy
        var items = constraints.applyto(query).tolist();
         
        return new pagedresult(items, count);
    }
}

the extension methods are also available at github .

basic contract

i usually start use a small definition for the repository, since it makes my other contracts less verbose. do note that some of my repository contracts do not implement this interface (for instance if any of the methods do not apply).

public interface irepository<tentity, in tkey> where tentity : class
{
    tentity getbyid(tkey id);
    void create(tentity entity);
    void update(tentity entity);
    void delete(tentity entity);
}

i then specialize it per domain model:

public interface itruckrepository : irepository<truck, string>
{
    ienumerable<truck> findbrokentrucks();
    ienumerable<truck> find(string text);
}

that specialization is important. it keeps the contract simple. only create methods that you know that you need.

entity framework

do note that the repository pattern is only useful if you have pocos which are mapped using code first. otherwise you’ll just break the abstraction using the entities. the repository pattern isn’t very useful then.

what i mean is that if you use the model designer you’ll always get a perfect representation of the database (but as classes). the problem is that those classes might not be a perfect representation of your domain model. hence you got to cut corners in the domain model to be able to use your generated db classes.

if you on the other hand uses code first you can modify the models to be a perfect representation of your domain model (if the db is reasonable similar to it). you don’t have to worry about your changes being overwritten as they would have been by the model designer.

you can follow this article if you want to get a foundation generated for you.

base class

public class entityframeworkrepository<tentity, tkey> where tentity : class
{
    private readonly dbcontext _dbcontext;
 
    public entityframeworkrepository(dbcontext dbcontext)
    {
        if (dbcontext == null) throw new argumentnullexception("dbcontext");
        _dbcontext = dbcontext;
    }
 
    protected dbcontext dbcontext
    {
        get { return _dbcontext; }
    }
 
    public void create(tentity entity)
    {
        if (entity == null) throw new argumentnullexception("entity");
        dbcontext.set<tentity>().add(entity);
    }
 
    public tentity getbyid(tkey id)
    {
        return _dbcontext.set<tentity>().find(id);
    }
 
    public void delete(tentity entity)
    {
        if (entity == null) throw new argumentnullexception("entity");
        dbcontext.set<tentity>().attach(entity);
        dbcontext.set<tentity>().remove(entity);
    }
 
    public void update(tentity entity)
    {
        if (entity == null) throw new argumentnullexception("entity");
        dbcontext.set<tentity>().attach(entity);
        dbcontext.entry(entity).state = entitystate.modified;
    }
}

then i go about and do the implementation:

public class truckrepository : entityframeworkrepository<truck, string>, itruckrepository
{
    private readonly truckerdbcontext _dbcontext;
 
    public truckrepository(truckerdbcontext dbcontext)
    {
        _dbcontext = dbcontext;
    }
 
    public ienumerable<truck> findbrokentrucks()
    {
        //compare having this statement in a business class compared
        //to invoking the repository methods. which says more?
        return _dbcontext.trucks.where(x => x.state == 3).tolist();
    }
 
    public ienumerable<truck> find(string text)
    {
        return _dbcontext.trucks.where(x => x.modelname.startswith(text)).tolist();
    }
}

unit of work

the unit of work implementation is simple for entity framework:

public class entityframeworkunitofwork : iunitofwork
{
    private readonly dbcontext _context;
 
    public entityframeworkunitofwork(dbcontext context)
    {
        _context = context;
    }
 
    public void dispose()
    {
         
    }
 
    public void savechanges()
    {
        _context.savechanges();
    }
}

nhibernate

i usually use fluent nhibernate to map my entities. imho it got a much nicer syntax than the built in code mappings. you can use nhibernate mapping generator to get a foundation created for you. but you do most often have to clean up the generated files a bit.

base class

public class nhibernaterepository<tentity, in tkey> where tentity : class
{
    isession _session;
     
    public nhibernaterepository(isession session)
    {
        _session = session;
    }
     
    protected isession session { get { return _session; } }
     
    public tentity getbyid(string id)
    {
        return _session.get<tentity>(id);
    }
 
    public void create(tentity entity)
    {
        _session.saveorupdate(entity);
    }
 
    public void update(tentity entity)
    {
        _session.saveorupdate(entity);
    }
 
    public void delete(tentity entity)
    {
        _session.delete(entity);
    }
}

implementation

public class truckrepository : nhibernaterepository<truck, string>, itruckrepository
{
    public truckrepository(isession session)
        : base(session)
    {
    }
 
    public ienumerable<truck> findbrokentrucks()
    {
        return _session.query<truck>().where(x => x.state == 3).tolist();
    }
 
    public ienumerable<truck> find(string text)
    {
        return _session.query<truck>().where(x => x.modelname.startswith(text)).tolist();
    }
}

unit of work

public class nhibernateunitofwork : iunitofwork
{
    private readonly isession _session;
    private itransaction _transaction;
 
    public nhibernateunitofwork(isession session)
    {
        _session = session;
        _transaction = _session.begintransaction();
    }
 
    public void dispose()
    {
        if (_transaction != null)
            _transaction.rollback();
    }
 
    public void savechanges()
    {
        if (_transaction == null)
            throw new invalidoperationexception("unitofwork have already been saved.");
 
        _transaction.commit();
        _transaction = null;
    }
}

typical mistakes

here are some mistakes which can be stumbled upon when using or/ms.

do not expose linq methods

let’s get it straight. there are no complete linq to sql implementations. they all are either missing features or implement things like eager/lazy loading in their own way. that means that they all are leaky abstractions. so if you expose linq outside your repository you get a leaky abstraction. you could really stop using the repository pattern then and use the or/m directly.

public interface irepository<tentity>
{
    iqueryable<tentity> query();
     
    // [...]
}

those repositories really do not serve any purpose. they are just lipstick on a pig (yay, my favorite)

the pig is back!

those who use them probably don’t want to face the truth:

bush ftw

or are just not reading very good:

book is upside down..

learn about lazy loading

lazy loading can be great. but it’s a curse for all which are not aware of it. if you don’t know what it is, google .

if you are not careful you could get 101 executed queries instead of 1 if you traverse a list of 100 items.

invoke tolist() before returning

the query is not executed in the database until you invoke tolist() , firstordefault() etc. so if you want to be able to keep all data related exceptions in the repositories you have to invoke those methods.

get is not the same as search

there are to types of reads which are made in the database.

the first one is to search after items. i.e. the user want to identify the items that he/she like to work with.

the second one is when the user has identified the item and want to work with it.

those queries are different. in the first one, the user only want’s to get the most relevant information. in the second one, the user likely want’s to get all information. hence in the former one you should probably return userlistitem or similar while the other case returns user . that also helps you to avoid the lazy loading problems.

i usually let search methods start with findxxxx() while those getting the entire item starts with getxxxx() . also don’t be afraid of creating specialized pocos for the searches. two searches doesn’t necessarily have to return the same kind of entity information.

summary

don’t be lazy and try to make too generic repositories. it gives you no upsides compared to using the or/m directly. if you want to use the repository pattern, make sure that you do it properly.




Repository (version control) Database unit test Data (computing)

Published at DZone with permission of Jonas Gauffin, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Projections/DTOs in Spring Data R2DBC
  • TDD Typescript NestJS API Layers with Jest Part 3: Repository Unit Test
  • Alexa Skill With Local DynamoDB
  • Why Database Migrations Take Months and How to Speed Them Up

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • [email protected]

Let's be friends: