Some argue that DbContext itself is a repository, but some may argue that DbContext follows a work pattern.
For me personally, I like to use a small repository, which basically is a small wrapper around DbContext . Then I expose IDbSet from the repository.
Excuse me for using my GitHub project, as it is easy to explain in real code than in words.
Repository
public class EfRepository<T> : DbContext, IRepository<T> where T : BaseEntity { private readonly DbContext _context; private IDbSet<T> _entities; public EfRepository(DbContext context) { _context = context; } public virtual IDbSet<T> Entities => _entities ?? (_entities = _context.Set<T>()); public override async Task<int> SaveChangesAsync() { try { return await _context.SaveChangesAsync(); } catch (DbEntityValidationException ex) { var sb = new StringBuilder(); foreach (var validationErrors in ex.EntityValidationErrors) foreach (var validationError in validationErrors.ValidationErrors) sb.AppendLine($"Property: {validationError.PropertyName} Error: {validationError.ErrorMessage}"); throw new Exception(sb.ToString(), ex); } } } public class AppDbContext : DbContext { public AppDbContext(string nameOrConnectionString) : base(nameOrConnectionString) {} public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity { return base.Set<TEntity>(); } }
Using the repository in a service
Then I insert the repository into the service classes through the constructor installation.
public class UserService : IUserService { private readonly IRepository<User> _repository; public UserService(IRepository<User> repository) { _repository = repository; } public async Task<User> GetUserByUserNameAsync(string userName) { var query = _repository.Entities .Where(x => x.UserName == userName); return await query.FirstOrDefaultAsync(); }
Unit Test Assistant
It's a little tricky to fool asynchronous testing methods. In my case, I use NSubstitute, so I use the following helper method NSubstituteHelper .
If you use moq platforms or other module testing modules, you can read the Entity Framework testing using the Mocking Framework (EF6 onwards) .
public static IDbSet<T> CreateMockDbSet<T>(IQueryable<T> data = null) where T : class { var mockSet = Substitute.For<MockableDbSetWithExtensions<T>, IQueryable<T>, IDbAsyncEnumerable<T>>(); mockSet.AsNoTracking().Returns(mockSet); if (data != null) { ((IDbAsyncEnumerable<T>)mockSet).GetAsyncEnumerator().Returns(new TestDbAsyncEnumerator<T>(data.GetEnumerator())); ((IQueryable<T>)mockSet).Provider.Returns(new TestDbAsyncQueryProvider<T>(data.Provider)); ((IQueryable<T>)mockSet).Expression.Returns(data.Expression); ((IQueryable<T>)mockSet).ElementType.Returns(data.ElementType); ((IQueryable<T>)mockSet).GetEnumerator().Returns(new TestDbEnumerator<T>(data.GetEnumerator())); } return mockSet; }
Unit test
// SetUp var mockSet = NSubstituteHelper.CreateMockDbSet(_users); var mockRepository = Substitute.For<IRepository<User>>(); mockRepository.Entities.Returns(mockSet); _userRepository = mockRepository; [Test] public async Task GetUserByUserName_ValidUserName_Return1User() { var sut = new UserService(_userRepository); var user = await sut.GetUserByUserNameAsync("123456789"); Assert.AreEqual(_user3, user); }
Return to the original question.
To extract data from the cache, I use this approach. Usually we do not get async when retrieving data from memory.
In official docs, we can read that the EF context is not thread safe, so should I add wait after returning to GetOrCreate?
wait keyword is required for
async (not because DbContext is not thread safe)
Would it be appropriate to install it as a Singleton service and use it in the actions and looks of a razor?
It depends on how you use it. However, we should not use a singleton service for request dependency.
For example, I use this singleton object to save settings and configurations that will never change during the life of the application.
In official docs, we can read: Avoid the "data custodian" objects that only exist to allow access to another object in DI services. I worry about this line when I see my service.
You cannot store entire article tables in the cache.
If you store too much data in server memory, the server will eventually throw an Out of Memory Exception. In some cases, it is even slower than querying a single record from the database.