Implementing a Data Access Layer (DAL) within your application can eliminate pointless hours of debugging and prevent repeated code. This layer of abstraction is one of the first software architecture practices you want to adopt in any new data reliant project. Here are 3 reasons why building your application on top of a Data Access Layer from the start will help ensure your project's success.
The last thing you need when developing a new project, is to remember the specific SQL query syntax for your database each time you need to load data. Creating an abstracted Data Access Layer with standardized CRUD operations removes the mental load of data retrieval when you want to be focusing on business logic. One style of implementing this layer is with extension methods.
using System.Threading.Tasks;
public static class EfCoreReadExtensions
{
public static async Task<TModel> ReadAsync<TModel>(this DbSet<TModel> dbSet, Expression<Func<TModel, bool>> predicate)
where TModel : class
{
// You can do a lot of other fun stuff here!
return await dbSet.FirstOrDefaultAsync(predicate);
}
}
Then you can call dbContext.Users.Read(u => u.Name == userName) in your business logic. Adding a caching layer within the extension adds a lot of functionality for little effort, since you won't have to change any of the said business logic.
Extracting data access into its own layer lets you test code independently, and allows you to easily mock data.
[Fact]
public void Read_ReturnsMatchingUser()
{
// Arrange
var users = new List<User>
{
new User { ID = 1, Name = "Alice", Email = "alice@email.com" },
new User { ID = 2, Name = "Bob", Email = "bob@email.com" }
}.AsQueryable();
var mockSet = new Mock<DbSet<User>>();
mockSet.As<IQueryable<User>>().Setup(m => m.Provider).Returns(users.Provider);
mockSet.As<IQueryable<User>>().Setup(m => m.Expression).Returns(users.Expression);
mockSet.As<IQueryable<User>>().Setup(m => m.ElementType).Returns(users.ElementType);
mockSet.As<IQueryable<User>>().Setup(m => m.GetEnumerator()).Returns(users.GetEnumerator());
// Act
var result = mockSet.Object.Read(u => u.Name == "Alice");
// Assert
Assert.NotNull(result);
Assert.Equal("Alice", result.Name);
Assert.Equal("alice@email.com", result.Email);
}
User requirements are constantly changing, database technologies go in and out of style, and we don't want any of those things to lead to a massive refactor of the application. Whether you want to swap MySQL for Postgres, add your own data validation middleware, or change your caching method, you would be stuck rewriting the majority of your application if your data access methods are not contained in their own layer.