Click here to Skip to main content
15,885,537 members
Articles / Programming Languages / Visual Basic
Reference

The repository pattern (in VB.Net)

Rate me:
Please Sign up or sign in to vote.
3.83/5 (10 votes)
26 Nov 2015CPOL3 min read 55.9K   11   13
A VB.Net implementation of the repository pattern

Background

The repository pattern is a method to introduce a shearing layer between your business objects and the data access/persistence technology you are using and this especially useful in unit testing as the alternative (mocking an entire data access library) can be quite heart breaking.

There are a number of C# implementations on this site therefore this is just a VB.Net equivalent (as a reference article) for anyone wanting to achieve this pattern in VB.Net. 

Motivation

The repository pattern introduces the following advantages over the traditional three-tier architecture over an ORM:

  • The classes persisted by an ORM (Entity framework or the like) need to have a good deal of information about how they are stored. This is not ideal because when you make a change to the underlying storage you would need to change the business objects as well.
  • Not all persistence is in the form of a relational database - the repository can be backed by a blended storage made of files, database tables and NoSQL records as well.
  • Some fields exist only in order to allow navigation to a record or to identify child records - these fields should not be passed up into the business layer if they have no business meaning.

Method

The first thing we need to do in order to create a repository is to make sure our entities can be uniquely identified and that we know how so to do. For anyone from a database background this is like setting up the unique key field(s) on the database table.

I use a generic interface to do this in a highly flexible manner :-

VB.NET
''' <summary>
''' Interface defining any item we can store in a repository and can identify by 
''' an unique key
''' </summary>
''' <remarks>
''' This interface is typed so we can make type-safe code for retrieving the entity
''' (don't pass in an integer if the entity is keyed by string etc.)
''' </remarks>
Public Interface IKeyedEntity(Of TKeyType)

    ''' <summary>
    ''' Get the key to find the entity by
    ''' </summary>
    Property Key As TKeyType

End Interface

What this interface means is that for any class that implements it, an instance of that class can be meaningfully uniquely identified and the class will tell me how so to do.

For example if my Client class is uniquely identified by an integer we can declare it thus:-

VB.NET
''' <summary>
''' Record for storing a client record in the common database
''' </summary>
Public NotInheritable Class ClientRecord
    Implements IKeyedEntity(Of Integer)

    ''' <summary>
    ''' The unique number by which we know this client
    ''' </summary>
    ''' <remarks>
    ''' Every client has an unique id but this is not needed publically
    ''' </remarks>
    Public Property ClientUniqueKey As Integer Implements IKeyedEntity(Of Integer).Key

    ' Other non-key properties can go here
    ''' <summary>
    ''' The short code for the client
    ''' </summary>
    ''' <remarks>
    ''' e.g. MCL for Merrion Computing Ltd etc.
    ''' </remarks>
    Public Property Code As String
End Class

Where an entity has a key with multiple components (for example it might be a combination of two or more fields) then you can represent it with a Tuple or even better, you can create a lightweight class that only contains the key fields and use that in IKeyedEntity.

Now we'd need to define a set of standard operations to do with these objects and their backing storage. I split this into two parts - how I read from the data store and how I update the data store as this allows me to put together read-only data models quickly...this is particularily useful if you split your read and write models for example in CQRS.

VB.NET
 ''' <summary>
''' Interface to support reading entities from the backing store
''' </summary>
''' <typeparam name="TEntity">
''' The key-identified type of entity we are reading
''' </typeparam>
''' <typeparam name="TKey">
''' The type of the key
''' </typeparam>
''' <remarks>
''' In this architecture there is a seperate read and write interface but often this 
''' pattern has just the one interface for both functions
''' </remarks>
Public Interface IRepositoryRead(Of TKey, TEntity As IKeyedEntity(Of TKey))

    ''' <summary>
    ''' Does a record exist in the repository identified by this key
    ''' </summary>
    ''' <param name="key">
    ''' The unique identifier of the entity we are looking for
    ''' </param>
    Function Exists(ByVal key As TKey) As Boolean

    ''' <summary>
    ''' Get the entity uniquely identified by the given key
    ''' </summary>
    ''' <param name="key">
    ''' The unique identifier to use to get the entity
    ''' </param>
    Function GetByKey(ByVal key As TKey) As TEntity

    ''' <summary>
    ''' Get a set of entities from the repository that match the where clause
    ''' </summary>
    ''' <param name="clause">
    ''' A function to apply to filter the results from the repository
    ''' </param>
    Function GetWhere(ByVal clause As Func(Of TEntity, Boolean)) As IReadOnlyDictionary(Of Tkey, TEntity)

    ''' <summary>
    ''' Get all of this type of thing from the repository
    ''' </summary>
    ''' <remarks>
    ''' returns an IQueryable so this request can be filtered further
    ''' </remarks>
    Function GetAll() As IReadOnlyDictionary(Of Tkey, TEntity)

End Interface

..and on the write side....

VB.NET
''' <summary>
''' Interface to support writing (and deletes) to a typed repository
''' </summary>
''' <typeparam name="TEntity">
''' The type of entity in the repository
''' </typeparam>
''' <typeparam name="TKey">
''' The type of the key to uniquely identify the entity
''' </typeparam>
''' <remarks>
''' In this architecture there is a seperate read and write interface but often this 
''' pattern has just the one interface for both functions
''' </remarks>
Public Interface IRepositoryWrite(Of TKey, TEntity As IKeyedEntity(Of TKey))

    ''' <summary>
    ''' Delete the entity uniquely identified by this key
    ''' </summary>
    ''' <param name="key">
    ''' The unique identifier of the record to delete
    ''' </param>
    Sub Delete(ByVal key As TKey)

    ''' <summary>
    ''' Add or update the entity 
    ''' </summary>
    ''' <param name="entity">
    ''' The record to add or update on the repository
    ''' </param>
    ''' <param name="key" >
    ''' The key that uniquely identifies the record to add or update
    ''' </param>
    Sub AddOrUpdate(ByVal entity As TEntity, ByVal key As TKey)

    ''' <summary>
    ''' Adds an entity that we know to be new and returns its assigned key
    ''' </summary>
    ''' <param name="entity">
    ''' The entity we are adding to the repository
    ''' </param>
    ''' <returns>
    ''' The unique identifier for the entity
    ''' </returns>
    ''' <remarks>
    ''' This is useful if the unique identifier is not an intrinsic property of
    ''' the entity - for example if it is a memory address or a GUID
    ''' </remarks>
    Function AddNew(ByVal entity As TEntity) As TKey

End Interface

Of course you most often want both read and write sides in one class so I have a combining interface for that:-

VB.NET
''' <summary>
''' Read/write repository of typed entites
''' </summary>
''' <typeparam name="TKey">
''' The type by which the entity is uniquely identified
''' </typeparam>
''' <typeparam name="TEntity">
''' The type of entity in the repository
''' </typeparam>
Public Interface IRepository(Of TKey, TEntity As IKeyedEntity(Of TKey))
    Inherits IRepositoryRead(Of TKey, TEntity)
    Inherits IRepositoryWrite(Of TKey, TEntity)

End Interface

Worked example - a memory backed repository...

To show this in action a very simple memory-backed repository could look like this:-

VB.NET
Namespace MemoryBacked

    Public Class ClientRepository
        Implements IClientRepository

        ' Backing store for this data
        Private m_data As New Dictionary(Of Integer, ClientRecord)

        Public Function Exists(key As Integer) As Boolean Implements IRepositoryRead(Of Integer, ClientRecord).Exists
            Return m_data.ContainsKey(key)
        End Function

        Public Function GetAll() As IReadOnlyDictionary(Of Integer, ClientRecord) Implements IRepositoryRead(Of Integer, ClientRecord).GetAll
            Return m_data.Values.AsQueryable()
        End Function

        Public Function GetByKey(key As Integer) As ClientRecord Implements IRepositoryRead(Of Integer, ClientRecord).GetByKey
            If (m_data.ContainsKey(key)) Then
                Return m_data(key)
            Else
                Return Nothing
            End If
        End Function

        Public Function GetWhere(clause As Func(Of ClientRecord, Boolean)) As IReadOnlyDictionary(Of Integer, ClientRecord) Implements IRepositoryRead(Of Integer, ClientRecord).GetWhere
            Return m_data.Values.Where(clause)
        End Function

        Public Function AddNew(entity As ClientRecord) As Integer Implements IRepositoryWrite(Of Integer, ClientRecord).AddNew

            If (entity.ClientUniqueKey = 0) Then
                entity.ClientUniqueKey = m_data.Count
            End If

            If Not (m_data.ContainsKey(entity.ClientUniqueKey)) Then
                m_data.Add(entity.ClientUniqueKey, entity)
            End If

            Return entity.ClientUniqueKey
        End Function

        Public Sub AddOrUpdate(entity As ClientRecord, key As Integer) Implements IRepositoryWrite(Of Integer, ClientRecord).AddOrUpdate
            If Not (m_data.ContainsKey(entity.ClientUniqueKey)) Then
                m_data.Add(entity.ClientUniqueKey, entity)
            Else
                m_data(entity.ClientUniqueKey) = entity
            End If
        End Sub

        Public Sub Delete(key As Integer) Implements IRepositoryWrite(Of Integer, ClientRecord).Delete
            If (m_data.ContainsKey(key)) Then
                m_data.Remove(key)
            End If
        End Sub
    End Class

End Namespace

(It's not thread safe or overly good but sufficient for unit test purposes)

Now you write your business layer against the repository classes and leave the data access completely alone to be independently implemented.

Business-meaning interfaces

I also use what I term "business meaningful" interfaces to seperate the what of the storage operation from the how - for example in the above case I have added IClientRepository which is an additional wrapper around IRepository thus:-

VB.NET
Public Interface IClientRepository
    Inherits IRepository(Of Integer, ClientRecord)

End Interface

This is not neccessary in order to use the repository pattern but I find it makes for better unit testing and code understanding.

Exceptions

To further the shearing layer separation, I also recommend creating specific repository exceptions that your business layer can trap rather than it having to understand the underlying storage technology. These can wrap the inner exception so the developer can always get the required error information:-

VB.NET
''' <summary>
''' An exception that occured when reading from the repository backing store
''' </summary>
''' <remarks>
''' The inner exception is from whatever 
''' </remarks>
Public Class RepositoryReadException
    Inherits Exception

    ReadOnly m_fatal As Boolean

    Public ReadOnly Property Fatal As Boolean
        Get
            Return m_fatal
        End Get
    End Property

    Public Sub New(ByVal message As String, ByVal innerExcption As Exception, ByVal fatalInit As Boolean)
        MyBase.New(message, innerExcption)
        m_fatal = fatalInit
    End Sub


    Public Sub New(ByVal message As String, ByVal fatalInit As Boolean)
        MyBase.New(message)
        m_fatal = fatalInit
    End Sub
End Class

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Ireland Ireland
C# / SQL Server developer
Microsoft MVP (Azure) 2017
Microsoft MVP (Visual Basic) 2006, 2007

Comments and Discussions

 
GeneralIs there any demo project? Pin
Mr. xieguigang 谢桂纲27-Nov-15 19:05
professionalMr. xieguigang 谢桂纲27-Nov-15 19:05 
QuestionWhere/How is IClientRepository defined? Pin
assbach26-Nov-15 21:30
assbach26-Nov-15 21:30 
AnswerRe: Where/How is IClientRepository defined? Pin
Duncan Edwards Jones26-Nov-15 22:15
professionalDuncan Edwards Jones26-Nov-15 22:15 
GeneralRe: Where/How is IClientRepository defined? Pin
assbach26-Nov-15 23:47
assbach26-Nov-15 23:47 
QuestionWhy do we need an abstraction like this over an ORM? Pin
Karingatta14-Apr-14 1:15
Karingatta14-Apr-14 1:15 
AnswerRe: Why do we need an abstraction like this over an ORM? Pin
Duncan Edwards Jones14-Apr-14 1:46
professionalDuncan Edwards Jones14-Apr-14 1:46 
Questionthis is queryObject ? Pin
Member 1020865923-Feb-14 17:21
Member 1020865923-Feb-14 17:21 
AnswerRe: this is queryObject ? Pin
Duncan Edwards Jones23-Feb-14 20:42
professionalDuncan Edwards Jones23-Feb-14 20:42 
QuestionAnything exposing IQueryable is a leaky abstraction layer Pin
jgauffin21-Feb-14 22:26
jgauffin21-Feb-14 22:26 
AnswerRe: Anything exposing IQueryable is a leaky abstraction layer Pin
Duncan Edwards Jones21-Feb-14 23:14
professionalDuncan Edwards Jones21-Feb-14 23:14 
GeneralRe: Anything exposing IQueryable is a leaky abstraction layer Pin
jgauffin21-Feb-14 23:16
jgauffin21-Feb-14 23:16 
GeneralRe: Anything exposing IQueryable is a leaky abstraction layer Pin
Duncan Edwards Jones21-Feb-14 23:19
professionalDuncan Edwards Jones21-Feb-14 23:19 
GeneralRe: Anything exposing IQueryable is a leaky abstraction layer Pin
Duncan Edwards Jones24-Feb-14 9:11
professionalDuncan Edwards Jones24-Feb-14 9:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.