Cookie Consent by Free Privacy Policy Generator 📌 Gentle introduction to Generic Repository Pattern with C#

🏠 Team IT Security News

TSecurity.de ist eine Online-Plattform, die sich auf die Bereitstellung von Informationen,alle 15 Minuten neuste Nachrichten, Bildungsressourcen und Dienstleistungen rund um das Thema IT-Sicherheit spezialisiert hat.
Ob es sich um aktuelle Nachrichten, Fachartikel, Blogbeiträge, Webinare, Tutorials, oder Tipps & Tricks handelt, TSecurity.de bietet seinen Nutzern einen umfassenden Überblick über die wichtigsten Aspekte der IT-Sicherheit in einer sich ständig verändernden digitalen Welt.

16.12.2023 - TIP: Wer den Cookie Consent Banner akzeptiert, kann z.B. von Englisch nach Deutsch übersetzen, erst Englisch auswählen dann wieder Deutsch!

Google Android Playstore Download Button für Team IT Security



📚 Gentle introduction to Generic Repository Pattern with C#


💡 Newskategorie: Programmierung
🔗 Quelle: dev.to

The Generic Repository pattern in C# is a design pattern that abstracts the application’s data layer, making it easier to manage data access logic across different data sources and projects. It aims to reduce redundancy by implementing common data operations in a single, generic repository rather than having separate repositories for each entity type. Type of project can be desktop or web. And when web, considerations on implementation using dependency injection. In the samples provided a console project is used to reach a wider audience and dependency injection is not used to keep focus on how to write a generic repository.

  • A generic repository begins with a generic interface defining common operations for instance CRUD operations. These operations are defined in a generic way, applicable to any entity type.
  • The generic interface is then implemented in a concrete class. This class handles the data source interactions, such as querying a database using an ORM like EF Core or Dapper.
  • The implementation will often utilize an Entity Framework Core DbContext or Dapper with IDbConnection to interact with a database.

Creating the generic repository

A common repository will provide CRUD functionality, select all records, a single record, insert a new record with the option to return the new primary key, update and delete single or batch set of records.

🟢 No matter if the code will be used by a single developer or a team of developers it is critical to consider what should be included and naming conventions as the idea here is consistency when the idea is to be used in more than one project.

First a base interface is defined so that if there is a need to iterate data using a primary key, Id will be used for consistency and is not part of the generic interface.

public interface IBase 
{
    public int Id { get; }
}

The generic interface. If you don't care for method names feel free to change them along with adding or removing methods.

public interface IGenericRepository<T> where T : class
{
    IEnumerable<T> GetAll();
    Task<List<T>> GetAllAsync();
    T GetById(int id);
    T GetByIdWithIncludes(int id);
    Task<T> GetByIdAsync(int id);
    Task<T> GetByIdWithIncludesAsync(int id);
    bool Remove(int id);
    void Add(in T sender);
    void Update(in T sender);
    int Save();
    Task<int> SaveAsync();
    public T Select(Expression<Func<T, bool>> predicate);
    public Task<T> SelectAsync(Expression<Func<T, bool>> predicate);
}

The following models will be used, one will be created with all methods from the interface below and two others are there for the reader to practice with.

public class Category : IBase
{
    public int Id => CategoryId;
    public int CategoryId { get; set; }
    public string? Name { get; set; }
    public virtual ICollection<Product>? Products { get; set; }
    public override string? ToString() => Name;
}

public partial class Countries : IBase
{
    public int Id => CountryId;
    [Key]
    public int CountryId { get; set; }
    public string Name { get; set; }
    public override string ToString() => Name;
}

public class Product : IBase
{
    public int Id => ProductId;
    public int ProductId { get; set; }
    public string? Name { get; set; }
    public int CategoryId { get; set; }
    public virtual Category Category { get; set; } = null!;
    public override string? ToString() => Name;
}

To implement the interface for Production operations, create a new class named ProductsRepository. Change the class signature as follows.

public class ProductsRepository : IGenericRepository<Product>

At this point Visual Studio will prompt to create missing methods, allow Visual Studio to create the methods. Each method will be stubbed out ready for your code

public IEnumerable<Countries> GetAll()
{
    throw new NotImplementedException();
}

As this example uses EF Core with a DbContext and DbSets in the same class project we can initialize the DbContext named Context here as follows so each time the repository is needed the DbContext is ready.

public class ProductsRepository : IGenericRepository<Product>
{
    private Context _context;

    public ProductsRepository(Context context)
    {
        _context = context;
    }

It is also good to clean up afterwards so we implement IDisposable.

public class ProductsRepository : IGenericRepository<Product>, 
    IDisposable
{

...

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

From here, write code for each method so that when done you end up with something like this.

public class ProductsRepository : IGenericRepository<Product>, 
    IDisposable
{
    private Context _context;

    public ProductsRepository(Context context)
    {
        _context = context;
    }

    public IEnumerable<Product> GetAll()
    {
        return _context.Products.ToList();
    }

    public Task<List<Product>> GetAllAsync()
    {
        return _context.Products.ToListAsync();
    }

    public Product GetById(int id)
    {
        return _context.Products.Find(id);
    }

    public Product GetByIdWithIncludes(int id)
    {
        return _context.Products.Include(x => x.Category)
            .FirstOrDefault(x => x.ProductId == id);
    }

    public async Task<Product> GetByIdAsync(int id)
    {
        return await _context.Products.FindAsync(id);
    }

    public async Task<Product> GetByIdWithIncludesAsync(int id)
    {
        return await _context.Products.Include(x => x.Category)
            .FirstOrDefaultAsync(x => x.ProductId == id);
    }

    public bool Remove(int id)
    {
        var product = _context.Products.Find(id);
        if (product is { })
        {
            _context.Products.Remove(product);
            return true;
        }

        return false;
    }

    public void Add(in Product sender)
    {
        _context.Add(sender).State = EntityState.Added;
    }

    public void Update(in Product sender)
    {
        _context.Entry(sender).State = EntityState.Modified;
    }

    public int Save()
    {
        return _context.SaveChanges();
    }

    public Task<int> SaveAsync()
    {
        return _context.SaveChangesAsync();
    }

    public Product Select(
        Expression<Func<Product, bool>> predicate)
    {
        return _context.Products
            .WhereNullSafe(predicate).FirstOrDefault()!;
    }

    public async Task<Product> SelectAsync(
        Expression<Func<Product, bool>> predicate)
    {
        return 
            (
                await _context.Products
                    .WhereNullSafe(predicate).FirstOrDefaultAsync())!;
    }
    private bool _disposed = false;

    protected virtual void Dispose(bool disposing)
    {
        if (!_disposed)
        {
            if (disposing)
            {
                _context.Dispose();
            }
        }
        _disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

What is missing from above? You can add more methods such as your database model included orders and order details and only wanted products for a specific order, simply add the method.

Put ten developers in a room and ask how they would use a generic repository we most likely get at least three different versions.

The following comes from a random Stackoverflow post. Note that their interface is similar to the interface shown above.

public interface IRepositoryBase<T> where T : class
{
    void Add(T objModel);
    void AddRange(IEnumerable<T> objModel);
    T? GetId(int id);
    Task<T?> GetIdAsync(int id);
    T? Get(Expression<Func<T, bool>> predicate);
    Task<T?> GetAsync(Expression<Func<T, bool>> predicate);
    IEnumerable<T> GetList(Expression<Func<T, bool>> predicate);
    Task<IEnumerable<T>> GetListAsync(Expression<Func<T, bool>> predicate);
    IEnumerable<T> GetAll();
    Task<IEnumerable<T>> GetAllAsync();
    int Count();
    Task<int> CountAsync();
    void Update(T objModel);
    void Remove(T objModel);
    void Dispose();
}

Then there is a base repository which unlike the one shown above is used for all entities in the ORM.

public class RepositoryBase<TEntity> : IRepositoryBase<TEntity> where TEntity : class
{

    protected readonly Context _context = new();

    public void Add(TEntity model)
    {
        _context.Set<TEntity>().Add(model);
        _context.SaveChanges();
    }

    public void AddRange(IEnumerable<TEntity> model)
    {
        _context.Set<TEntity>().AddRange(model);
        _context.SaveChanges();
    }

    public TEntity? GetId(int id)
    {
        return _context.Set<TEntity>().Find(id);
    }

    public async Task<TEntity?> GetIdAsync(int id)
    {
        return await _context.Set<TEntity>().FindAsync(id);
    }

    public TEntity? Get(Expression<Func<TEntity, bool>> predicate)
    {
        return _context.Set<TEntity>().FirstOrDefault(predicate);
    }

    public async Task<TEntity?> GetAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await _context.Set<TEntity>().FirstOrDefaultAsync(predicate);
    }

    public IEnumerable<TEntity> GetList(Expression<Func<TEntity, bool>> predicate)
    {
        return _context.Set<TEntity>().Where<TEntity>(predicate).ToList();
    }

    public async Task<IEnumerable<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate)
    {
        return await Task.Run(() => _context.Set<TEntity>().Where<TEntity>(predicate));
    }

    public IEnumerable<TEntity> GetAll()
    {
        return _context.Set<TEntity>().ToList();
    }

    public async Task<IEnumerable<TEntity>> GetAllAsync()
    {
        return await Task.Run(() => _context.Set<TEntity>());
    }

    public int Count()
    {
        return _context.Set<TEntity>().Count();
    }

    public async Task<int> CountAsync()
    {
        return await _context.Set<TEntity>().CountAsync();
    }

    public void Update(TEntity objModel)
    {
        _context.Entry(objModel).State = EntityState.Modified;
        _context.SaveChanges();
    }

    public void Remove(TEntity objModel)
    {
        _context.Set<TEntity>().Remove(objModel);
        _context.SaveChanges();
    }

    public void Dispose()
    {
        _context.Dispose();
    }
}

Which pattern to use? That is personal preference and experience.

Running the provided code

The project WorkingWithInterfacesApp1 defines the connection string in appsettings.json using .\SQLEXPRESS as the server. Either leave as is if this server is available or change it to an available server.

  1. Code in the DbContext will create the database and populate the tables defined in the DbContext.
  2. Using the ProductsRepository
    1. Use the GetAll method to get all products
    2. Use GetByIdWithIncludeAsync to get a product
    3. Delete the product with id of 4
    4. Add a new product with the Add method
    5. Save changes and check for 2 returned from save changes, one for a delete, one for an add.
    6. Edit a product
    7. Save changes.

results of demo code

Spreading your wings

Study the code for products than try your hand at working with one of the other models or take the two interfaces and try it in your project.

Not using EF Core

The generic repository pattern will work with Dapper for instances or a connection and command object using a DataTable also.

Source code

Clone the following GitHub repository.

...



📌 Gentle introduction to Generic Repository Pattern with C#


📈 70.83 Punkte

📌 A Gentle Introduction To Deep Learning with TensorFlow [Intermediate Level]


📈 33.14 Punkte

📌 A Gentle Introduction to Functional JavaScript


📈 33.14 Punkte

📌 Quantum Mechanics: A Gentle Introduction


📈 33.14 Punkte

📌 A gentle introduction to Linux Kernel fuzzing


📈 33.14 Punkte

📌 Zero to Nix, an unofficial, opinionated, gentle introduction to Nix


📈 33.14 Punkte

📌 Reactive Programming: a gentle introduction


📈 33.14 Punkte

📌 A Gentle Introduction to Kubernetes


📈 33.14 Punkte

📌 A Gentle Introduction to Analytical Stream Processing


📈 33.14 Punkte

📌 A Gentle Introduction to GPT Models


📈 33.14 Punkte

📌 Gentle Introduction To Typescript Compiler API


📈 33.14 Punkte

📌 A gentle introduction to Steerable Neural Networks (part 2)


📈 33.14 Punkte

📌 A gentle introduction to Steerable Neural Networks (part 1)


📈 33.14 Punkte

📌 A Gentle Introduction to Deep Reinforcement Learning in JAX


📈 33.14 Punkte

📌 A Gentle Introduction to Using R Markdown


📈 33.14 Punkte

📌 Gentle introduction for generics (C#)


📈 33.14 Punkte

📌 Gentle introduction to IDataProtector (C#)


📈 33.14 Punkte

📌 MlOps —  A gentle introduction to Mlflow Pipelines


📈 33.14 Punkte

📌 TypeScript for Beginners: A Gentle Introduction


📈 33.14 Punkte

📌 A Gentle Introduction to OpenCV: An Open Source Library for Computer Vision and Machine Learning


📈 33.14 Punkte

📌 A Gentle Introduction to Containerization and Docker


📈 33.14 Punkte

📌 [dos] Pdfium - Out-of-Bounds Read with Shading Pattern Backed by Pattern Colorspace


📈 28.1 Punkte

📌 #0daytoday #Pdfium - Out-of-Bounds Read with Shading Pattern Backed by Pattern Colorspace Exploit [#0day #Exploit]


📈 28.1 Punkte

📌 Observer Pattern: Was steckt hinter dem Observer Design Pattern?


📈 28.1 Punkte

📌 Factory Pattern: Alle Informationen zum Factory Method Pattern


📈 28.1 Punkte

📌 C# Pattern Matching Inside Out: Kompakter und prägnanter C#-Code durch Pattern Matching


📈 28.1 Punkte

📌 Neu in .NET 7 [5]: List Pattern und Slice Pattern mit C# 11


📈 28.1 Punkte

📌 A transição do Higher-Order Component pattern para o React Hooks pattern


📈 28.1 Punkte

📌 Go program pattern 01: Functional Options Pattern


📈 28.1 Punkte

📌 Patterns no Terminal: Desvende a Magia da Arte ASCII com Star Pattern, Number Pattern e Mais! Usando Dart 🔵💙


📈 28.1 Punkte

📌 Generic:ASP/Backdoor.Generic.A


📈 26.93 Punkte

📌 Generic:BIN/Trojan.Generic.A


📈 26.93 Punkte

📌 Generic:PHP/Redirect.Generic.B


📈 26.93 Punkte

📌 Generic:PHP/Upload.Generic.A


📈 26.93 Punkte

📌 Generic:PHP/Irc.Generic.A


📈 26.93 Punkte











matomo