Implementing Generic Data Access Object (DAO) in a .NET Core App with Entity Framework
Often when writing a new software application, developers need to write Data Access Object (DAO) classes with CRUD operations for each entity class. This can get frustrating if you have a lot of tables/entities to work with.
Wouldn't it be nice to write a single DAO class with CRUD operations once and reuse it for all entity classes regardless of their type?
One way would be to work with the object
type. But this would require explicitly casting the object to the appropriate entity type whenever it is accessed or it
may require type checking during runtime depending on the use case.
Luckily, there is a better way to achieve this using Generics. Generics allow class definitions, methods and interfaces to work with objects of different types while ensuring type safety.
Benefits of using Generics for DAO with CRUD operations
Type Safety:
- Generics provide compile-time type checking.
- When you use a generic class, the compiler ensures that you’re working with the correct data types.
- This reduces runtime errors related to type mismatches.
Code Reusability:
- With generics, you can write a single implementation that can work with different types of data.
- Using a generic DAO class helps avoid duplicating similar code for each entity, thereby promoting code reusability.
Less Boilerplate Code:
- A generic DAO class abstracts common CRUD operations (such as fetching records, saving, updating, and deleting).
- This allows you to focus on the essential business logic specific to each entity.
Flexibility:
- You can extend the generic DAO class:
- To override existing methods.
- Add custom methods or behaviors specific to a particular entity.
- For example, you might need additional filtering or sorting options for certain entities.
Implementation
We will implement the generic DAO class in C# using Entity Framework Core. The concept remains the same for other programming languages.
Let’s begin by declaring an abstract BaseEntity class, this class will have only one field/column named Id
to map to the primary key in our database tables.
Step 1: Declare a BaseEntity class that will be inheritted by our Entity classes.
public abstract class BaseEntity
{
/**
* This field will map to the primary key in the database.
*/
public int Id { get; set; }
}
Step 2: Declare Entity classes that inherit from BaseEntity.
Now let’s declare a few more classes that inherit from BaseEntity class. For simplicity, let’s use User, Role and Permission classes which map to the same tables in the database.
These classes have the navigation properties defined. For instance, the User class has the Role navigation property which links a user to the Role in the database. For it to work, you need to implement the relationship in the OnModelCreating method of your DbContext class.
public class User : BaseEntity
{
public string Username { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public bool IsActive { get; set; }
public int RoleId { get; set; }
//Role navigation property
public Role Role { get; set; }
}
public class Role : BaseEntity
{
public string Name { get; set; }
public ICollection<User> Users { get; set; } = new List<User>();
public ICollection<RolePermission> RolePermissions
{ get; set; } = new List<RolePermission>();
}
public class Permission : BaseEntity
{
public string Name { get; set; }
public ICollection<RolePermission> RolePermissions
{ get; set; } = new List<RolePermission>();
}
Step 3: Declare the Generic DAO class
Our generic DAO will provide an abstraction layer for interacting with our database. Let’s appropriately call it BaseDao
. This class will accept a generic type TEntity
which has a constraint that it should inherit from or be of type BaseEntity
.
We will also declare a protected readonly field of type DbSet
that holds a reference to the DbSet of the TEntity
generic type which is intialised by the context.Set<TEntity>()
method which in itself is a generic method.
public abstract class BaseDao<TEntity>(AppDbContext context)
where TEntity : BaseEntity
{
protected readonly DbSet<TEntity> DbSet = context.Set<TEntity>();
/**
* Checks if an entity of type TEntity exists in the database with the
* specified id.
* Returns a Task<bool> indicating whether the entity exists.
*/
public Task<bool> ExistsAsync(int id)
{
var exists = DbSet.AnyAsync(e => e.Id == id);
return exists;
}
/**
* An overrideable method that retrieves all entities of type TEntity from
* the database.
* Returns a Task<TEntity[]>.
*/
public virtual Task<TEntity[]> GetAllAsync()
{
return DbSet.ToArrayAsync();
}
/**
* An overrideable method that retrieves an entity of type TEntity from the
* database with the specified id.
* Returns a Task<TEntity?>.
*/
public virtual async Task<TEntity?> GetAsync(int id)
{
return await DbSet.FindAsync(id);
}
/**
* Adds a new record of type TEntity to the database.
* Returns the added entity.
*/
public async Task<TEntity> AddAsync(TEntity entity)
{
DbSet.Add(entity);
await context.SaveChangesAsync();
return entity;
}
/**
* Saves changes made to the database.
* Returns the number of affected rows
*/
public async Task<int> SaveChangesAsync()
{
var affectedRows = await context.SaveChangesAsync();
return affectedRows;
}
/**
* Deletes an entity of type TEntity from the database with the specified id.
* If the entity exists, it removes it and returns the number of affected
* rows.
* Otherwise, it returns 0.
*/
public async Task<int> DeleteAsync(int id)
{
var entityInSystem = await GetAsync(id);
if (entityInSystem != null)
{
DbSet.Remove(entityInSystem);
int affectedRows = await context.SaveChangesAsync();
return affectedRows;
}
return 0;
}
}
Step 4: Inherit BaseDao class
Now, let’s create individual Dao classes for Permission, Role and User entities which will inherit from BaseDao and automatically have access to the methods in the BaseDao class.
/**
* This PermissionDao inherits all the CRUD methods related to the
* Permission table through the BaseDao.
*/
public class PermissionDao(AppDbContext context) : BaseDao<Permission>(context);
/**
* This RoleDao inherits all the CRUD methods related to the Role table
* through the BaseDao.
*/
public class RoleDao(AppDbContext context) : BaseDao<Role>(context);
Let’s say for the UserDao, we want the GetAsync
method to also include the Role relationship object using eager loading.
We can simply override
this method in the UserDao class.
We can also add a new method called GetActiveUsersAsync
in the UserDao class that only retrieves users who have an active status.
/**
* This UserDao inherits all the CRUD methods related to the User table
* through the BaseDao.
* Plus it has additional methods for added functionality.
*/
public class UserDao(AppDbContext context) : BaseDao<User>(context)
{
/**
* Overridden method to also include the Role information of the user
* with the specified user id
*/
public override Task<User?> GetAsync(int id)
{
return DbSet.Include(user => user.Role)
.Where(user => user.Id == id)
.FirstOrDefaultAsync();
}
/**
* New method to only select active users
*/
public Task<User[]> GetActiveUsersAsync()
{
return DbSet.Where(user => user.IsActive).ToArrayAsync();
}
}
Step 5: Use the DAO classes to interact with the database.
We will use repository classes to interact with each entity type. Let’s create a UserRepository class that allows us to deactivate a user with a given id and then also print all active users.
We will inject the UserDao object to the constructor of this UserRepository class which will allow us to interact with the user table.
public class UserRepository(UserDao userDao)
{
/**
* De-activate a user with a given id.
*/
public async Task<User?> DeactivateUser(int id)
{
//Use the DAO object to read the user from the database.
var userInSystem = await userDao.GetAsync(id);
if (userInSystem != null)
{
userInSystem.IsActive = false;
//Use the Dao object to save changes to the database
await userDao.SaveChangesAsync();
}
return userInSystem;
}
/**
* Print all active users in the database
*/
public async Task PrintAllActiveUsersAsync()
{
//Use the DAO to read all active users
var allActiveUsers = await userDao.GetActiveUsersAsync();
Console.WriteLine("Active Users");
foreach (var activeUser in allActiveUsers)
{
Console.WriteLine(activeUser.Name);
}
}
}
Similarly we can declare a RoleRepository class and inject the RoleDao in its constructor and read all the roles from the database.
public class RoleRepository(RoleDao roleDao)
{
/**
* Print all roles in the database
*/
public async Task PrintAllRoles()
{
//Use the DAO to get all roles from the database
var allRoles = await roleDao.GetAllAsync();
Console.WriteLine("All Roles");
foreach (var role in allRoles)
{
Console.WriteLine(role.Name);
}
}
}
The roleDao.GetAllAsync()
actually calls the method in the BaseDao
class, allowing reusability.
Similarly, any entity class, as long as it inherits from the BaseEntity
class can use the BaseDao
generic class and reuse all the common CRUD methods in it and that’s all.
Happy coding!