I've got a generic repository to either get en entity by ID or to get all entities:
internal class Repository<TEntity> : IRepository<TEntity>
where TEntity : BaseEntity
{
protected SaiContext Context { get; }
/// <summary>Gets the entity set.</summary>
protected virtual DbSet<TEntity> Set => Context.Set<TEntity>();
public Repository(SaiContext context)
{
Context = context;
}
public async Task<TEntity> GetAsync(int entityId, IEnumerable<string> includeProperties = null)
{
try
{
return await GetQueryableWithIncludes(includeProperties).SingleAsync(entity => entity.Id == entityId);
}
catch (InvalidOperationException)
{
throw new EntityNotFoundException(typeof(TEntity), entityId);
}
}
public async Task<IEnumerable<TEntity>> GetAllAsync(IEnumerable<string> includeProperties = null)
{
return await GetQueryableWithIncludes(includeProperties).ToListAsync();
}
protected IQueryable<TEntity> GetQueryableWithIncludes(IEnumerable<string> includeProperties = null)
{
IQueryable<TEntity> queryable = Set;
if (includeProperties == null)
{
return queryable;
}
foreach (var propertyName in includeProperties)
{
queryable = queryable.Include(propertyName);
}
return queryable;
}
}
After having configured the DbContext for entity relations, navigation properties along with all the rest is being loaded correctly for all entities.
Now I've been asked to use temporal SQL tables so that all entities have a validity range.
With SQL I'd include FOR SYSTEM_TIME AS OF #validityDate in the query.
What is the easiest way (if there is any) to adapt the existing implementation in order to respect #validityDate?
What I've tried:
Look for a way to configure the wanted system time when performing the SQL query. ISSUE: I couldn't find a way.
Expose the query through a table valued function allowing to pass #validityDate as a parameter. ISSUE: I can't pass the parameter using Linq2Sql (or at least I didn't figurte out how).
Create a table valued function performing the joins (instead of letting EF do them) so it can be called with context.FromSqlRaw(<query>). ISSUE: How to create the c# object tree? (multiple rows are being returned as there are 1 to many relations)
All examples using temporal tables I've found use FromSqlRaw. If possible I'd like to avoid it, as it means that the entire DB context sonfiguration becomes useless and additional code for the mappings has to be included.
I've found the solution with the efcore-temporal-query (nuget) library.
The code has been adapted to use temporal tables as described in the README.
The repository methods now accept an optional parameter validityDate:
public async Task<TEntity> GetAsync(int entityId, DateTime? validityDate = null, IEnumerable<string> includeProperties = null)
{
try
{
var query = GetQueryableWithIncludes(includeProperties);
query = GetQueryableWithValidityDate(query, validityDate);
return await query.SingleAsync(entity => entity.Id == entityId);
}
catch (InvalidOperationException)
{
throw new EntityNotFoundException(typeof(TEntity), entityId);
}
}
protected IQueryable<TEntity> GetQueryableWithIncludes(IEnumerable<string> includeProperties = null)
{
IQueryable<TEntity> queryable = Set;
if (includeProperties == null)
{
return queryable;
}
foreach (var propertyName in includeProperties)
{
queryable = queryable.Include(propertyName);
}
return queryable;
}
private static IQueryable<TEntity> GetQueryableWithValidityDate(IQueryable<TEntity> query, DateTime? validityDate)
{
return validityDate.HasValue ? query.AsOf(validityDate.Value) : query;
}
Where te relevant part for the historized query is query.AsOf(validityDate.Value).
Related
I wrote an application using repository pattern and unit of work, it is also using entity framework in order to interact with db (mssql in my case).
Right now I have two projects in my application: 1-DAL which stands for data access layer, and 2-BLL which stands for bisnel logic layer.
I have my unitofwork, repository (I have one generic repo for all classes), and basic data access controllers(like GetAll, or GetById, Edit, Delete, and Create) in DAL project. Also, I have DTO mapper, and more "logic" controllers in BLL project.
I need some explanation on writing tests for this, like: 1) Do I have to write tests for data access layer? 2) If I have to test the data access layer, do I have to write tests for only controller, or for repository as well (as they are pretty much the same)? 3) Do I have to seed the database in the test cases themselves, or use the "pre seeded db"? 4) Do I have to write tests for mapper (I am using Mapper extension)
p.s.: If there is an article for this theme, I'll be grateful.
I will attach some of my code bellow:
Generic repository:
public class GenericRepository<TEntity> where TEntity : class
{
internal WarehouseContext context;
internal DbSet<TEntity> dbset;
public GenericRepository(WarehouseContext context)
{
this.context = context;
this.dbset = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> GetAll()
{
return dbset;
}
public virtual TEntity GetById(Guid id)
{
return dbset.Find(id);
}
public virtual void Add(TEntity entity)
{
dbset.Add(entity);
}
public virtual void Delete(Guid id)
{
TEntity entityToDelete = dbset.Find(id);
dbset.Remove(entityToDelete);
}
public virtual void Edit(TEntity entityToUpdate)
{
dbset.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
DAL controller for CategoryClass:
private readonly UnitOfWork _unitOfWork;
public CategoryController()
{
_unitOfWork = new UnitOfWork();
}
public List<Category> GetCategories()
{
return _unitOfWork.CategoryRepository.GetAll().ToList();
}
public Category GetCategoryById(Guid id)
{
return _unitOfWork.CategoryRepository.GetById(id);
}
public void AddCategory(Category category)
{
_unitOfWork.CategoryRepository.Add(category);
_unitOfWork.Save();
}
public void EditCategory(Category category)
{
_unitOfWork.CategoryRepository.Edit(category);
_unitOfWork.Save();
}
public void DeleteCategory(Guid id)
{
_unitOfWork.CategoryRepository.Delete(id);
_unitOfWork.Save();
}
Example of what I have in BLL controller for CategoryClass:
private readonly CategoryController _categoryController;
private readonly IMapper _mapper;
public CategoryLogicContoller()
{
_categoryController = new CategoryController();
var profile = new CategoryProfile();
_mapper = profile.CategoryMapper;
}
public List<CategoryDTO_short> GetAllCategories_shortDescription()
{
var categories = _categoryController.GetCategories().ToList();
var categoriesDTO = new List<CategoryDTO_short>();
foreach (var category in categories)
{
var categoryDTO = _mapper.Map<Category, CategoryDTO_short>(category);
categoriesDTO.Add(categoryDTO);
}
return categoriesDTO;
}
I am also attaching the screenshot of my app architecture:
Thanks for answers!
What a coincidence, I implemented unit testing for this pattern a few days ago.
The UnitOfWork should use a generic repository, so you can define a repository for Entity Framework, and a repository that stores data in a list, e.g. MemoryRepository. This way the unit testing is not depending on the database, or actually, also not depending on Entity Framework at all.
Since the code is quite long, I have added a few excerpts and removed a lot of code.
Unit of work with a repository factory
The unit of work receives an instance of DB context and a factory that creates instances of repositories, with the given instance of DB context.
The unit of work will receive a factory instead of a repository. The idea is to give it a different factory when unit testing.
public class UnitOfWork
{
IDbContext _db { get; }
IRepositoryFactory _repositoryFactory { get; }
IRepository<Category> _categoryRepository { get; set; } = null!;
public UnitOfWork(IDbContext db, IRepositoryFactory repositoryFactory)
{
_db = db;
_repositoryFactory = repositoryFactory;
}
public IRepository<Question> Questions
{
get => (_categoryRepository ??= _repositoryFactory.CreateInstance<Category>(_db));
}
public void Commit()
{
_db.Commit();
}
public async Task CommitAsync()
{
await _db.CommitAsync();
}
}
Repository factory pattern
The interface uses an IKey interface, which says that the implementation should have a Guid Id { get; set; } defined on it. I have added this to all the entities, so the memory repository has an index for the dictionary.
IRepositoryInterface
public interface IRepositoryFactory
{
public IRepository<TEntity> CreateInstance<TEntity>(IDbContext db) where TEntity : class, IKey;
}
EFRepositoryFactory
This factory will create an instance of the EF repository, which is used in the application, and added to the DI container in the startup of the application.
public class EFRepositoryFactory : IRepositoryFactory
{
public IRepository<TEntity> CreateInstance<TEntity>(IDbContext db) where TEntity : class, IKey
{
return new EFRepository<TEntity>((FetchDbContext)db);
}
}
MemoryRepositoryFactory
This factory will create an instance of the memory repository, that is used in unit testing.
public class MemoryRepositoryFactory : IRepositoryFactory
{
public IRepository<TEntity> CreateInstance<TEntity>(IDbContext db) where TEntity : class, IKey
{
return new MemoryRepository<TEntity>();
}
}
Repository implementations
EFRepository
This is the repository that interacts with the database.
public class EFRepository<TEntity> : IRepository<TEntity> where TEntity : class, IKey
{
internal FetchDbContext _db;
internal DbSet<TEntity> _entities;
public EFRepository(FetchDbContext context)
{
_db = context;
_entities = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
int skip = -1,
int take = -1,
string includeProperties = "")
{
IQueryable<TEntity> query = _entities;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty.Trim());
}
if (orderBy != null)
{
query = orderBy(query);
}
if (skip >= 0)
{
query = query.Skip(skip);
}
if (take >= 0)
{
query = query.Take(take);
}
return query.ToList();
}
public virtual async Task<IEnumerable<TEntity>> GetAsync(
Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
int skip = -1,
int take = -1,
string includeProperties = "")
{
...
}
public virtual TEntity GetById(object id)
{
var result = _entities.Find(id);
if (result is null)
{
throw new KeyNotFoundException(nameof(id));
}
return result;
}
public virtual async Task<TEntity> GetByIdAsync(object id)
{
...
}
...
}
MemoryRepository
This repository saves the data in a dictionary instead of a database;
public class MemoryRepository<TEntity> : IRepository<TEntity> where TEntity : class, IKey
{
// Dictionary
Dictionary<Guid, TEntity> _entities = new();
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
int skip = -1,
int take = -1,
string includeProperties = "")
{
IQueryable<TEntity> query = _entities.Values.AsQueryable();
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual async Task<IEnumerable<TEntity>> GetAsync(
Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null,
int skip = -1,
int take = -1,
string includeProperties = "")
{
...
}
public virtual TEntity GetById(object id)
{
Guid guid = (Guid)id;
var result = _entities.Values.FirstOrDefault(e => e.Id == guid);
if (result is null)
{
throw new KeyNotFoundException(nameof(id));
}
return result;
}
public virtual async Task<TEntity> GetByIdAsync(object id)
{
...
}
public virtual void Insert(TEntity entity)
{
if (entity.Id == default)
{
entity.Id = Guid.NewGuid();
}
_entities[entity.Id] = entity;
}
...
}
IDbContext
I have implemented the following methods on the DbContext class, and defined them as an interface.
public interface IDbContext
{
void Commit();
Task CommitAsync();
}
Interface of IRepository
public interface IRepository<TEntity> where TEntity : class, IKey
{
int Count(Expression<Func<TEntity, bool>>? filter = null);
Task<int> CountAsync(Expression<Func<TEntity, bool>>? filter = null);
void Delete(object id);
void Delete(TEntity entityToDelete);
Task DeleteAsync(object id);
IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null, int skip = -1, int take = -1,
string includeProperties = "");
Task<IEnumerable<TEntity>> GetAsync(Expression<Func<TEntity, bool>>? filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>>? orderBy = null, int skip = -1, int take = -1,
string includeProperties = "");
TEntity GetById(object id);
Task<TEntity> GetByIdAsync(object id);
void Insert(TEntity entity);
Task InsertAsync(TEntity entity);
void Update(TEntity entityToUpdate);
}
Unit testing
In the unit test we can define a unit of work in the following way:
var dbContext = new MockDbContext();
var repositoryFactory = new MemoryRepositoryFactory();
var unitOfWork = new UnitOfWork(dbContext, repositoryFactory);
And we will pass that mocked instance of DB context
private class MockDbContext : IDbContext
{
public void Commit()
{
return;
}
public async Task CommitAsync()
{
await Task.Delay(0); // Probably something better applies here
}
}
I use this to create instances of the services in the unit tests so I can test the business logic in the services.
Caveats
Storing the entities in memory is a lot different than from a database. To give an example, if we have multiple nested entities, EF will create entities in the appropriate tables. However, this does not happen in the memory repository, so if you'd insert something in table A, and expect something in table B as well, then that will not be there!
If you have entities that derive from a common base entity, then this approach gives some issues to the reason above.
Hope this helps!
I am working with EF Core and I created a generic repository. I have a method which returns all the entities with their child entities. Here is my method:
public Repository(DevbAERPContext dbContext)
{
_dbContext = dbContext;
Table = _dbContext.Set<T>();
}
public async Task<IEnumerable<T>> GetAllWithInclude(Expression<Func<T, bool>> where, string[] includeProperties)
{
var result = includeProperties.Aggregate(Table.Where(where), (query, path) => query.Include(path)).AsNoTracking();
return await result.ToListAsync();
}
While using this method I don't want to get the soft deleted data. I can filter the parent entity by writing where expression but I also want to do the same for the child entities. Currently I can handle the issue in controller like this:
var roles = await _roleRepository.GetAllWithInclude(x => !x.IsDeleted, new string[] { "RoleTemplateSkills", "RoleTemplateSkills.Skill" }).ConfigureAwait(false);
var mappedRoles = _mapper.Map<List<RoleTemplateViewModel>>(roles);
foreach(var mappedRole in mappedRoles)
{
mappedRole.RoleTemplateSkills = mappedRole.RoleTemplateSkills.Where(x => !x.IsDeleted).ToList();
}
What I want is to do this filtering in my generic repository method. Is there any way to do it?
I'm not sure if I got your problem exactly. But here below is a sample code I used in one generic repository may help you.
public TEntity FindByInclude(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includeProperties)
{
IQueryable<TEntity> result = dbSet.Where(predicate);
if (includeProperties.Any())
{
foreach (var includeProperty in includeProperties)
{
result = result.Include(includeProperty);
}
}
var firstResult = result.FirstOrDefault(predicate);
if (firstResult != null)
dbSet.Attach(firstResult);
return firstResult;
}
public IEnumerable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] includeProperties)
{
IQueryable<TEntity> result = dbSet.AsNoTracking();
return includeProperties.Aggregate(result, (current, includeProperty) => current.Include(includeProperty));
}
However, In the constructor, I didn't use Table. Instead I used as the following
public class FitnessRepository<TEntity> : IFitnessRepository<TEntity> where TEntity :class {
private readonly FitnessDbContextt fitnessDbContext;
private readonly DbSet<TEntity> dbSet;
public FitnessRepository (FitnessDbContextt context) {
fitnessDbContext = context;
dbSet = context.Set<TEntity> ();
}
}
I hope it helps.
Have you tried to add a filter to the Entity it self in the Entity designer? so you can skip adding the condition here.
Thanks
Hi Im developing an WPF application using Entity Framework Code first method and Generic Repository pattern. My problem is at the time WPF application is running when I do a change to the data in the data base, WPF application data not showing that modified latest data. it is keeping all the data in the cache and not getting synch with the database. I have to close my application and need to rerun it to get the modified data. How can i resolve this problem. please help
public class GenericRepository<TEntity> where TEntity : class
{
internal RcerpDbContext Context;
internal DbSet<TEntity> DbSet;
public GenericRepository(RcerpDbContext context)
{
this.Context = context;
this.DbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = Null,
string includeProperties = "")
{
IQueryable<TEntity> query = DbSet;
if (filter != null)
{
query = query.Where(filter);
}
query = includeProperties.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries).Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetById(object id)
{
return DbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
DbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = DbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (Context.Entry(entityToDelete).State == EntityState.Detached)
{
DbSet.Attach(entityToDelete);
}
DbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
var entry = Context.Entry(entityToUpdate);
var primaryKey = DbSet.Create().GetType().GetProperty("Id").GetValue(entityToUpdate);
if (entry.State != EntityState.Detached) return;
var set = Context.Set<TEntity>();
var attachedEntity = set.Find(primaryKey);
if (attachedEntity != null)
{
var attachedEntry = Context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entityToUpdate);
}
else
{
entry.State = EntityState.Modified;
}
}
}
You need to create a new DbContext once you know it is refreshed (or if you want to check if any data has changed).
Your repository should ask for a Func<RcerpDbContext> to be injected, so it can dispose of the current context, and create a new one using the Func<>, once it knows it needs reload collections of data from the database.
Otherwise if you only want to reload a single entity, you can use:
Context.Entry<T>(entity).Reload()
Also if you do not want EF to keep a local copy you can use
Context.Set<TEntity>.AsNoTracking()
which will force EF not to store any local copies. Beware, that if you change your entity, and try to call SaveChange() NOTHING will happen. You will manually have to tell EF which propertied have been updated.
EDIT 1
If you want to reload a whole set:
Context.Set<Car>().Local.ToList().ForEach( x=>
{
Context.Entry(x).State = EntityState.Detached;
}
Although I would highly recommend either
Using a new Context and disposing of current one.
Using the AsNoTracking() modifier for your queries.
I'm using generic repository pattern with unit of work implementation for my project.
Recently I've come to an issue which I could not solve. When I try to update an entity's collection property (i.e: Add a new associated entity) and call update on my UoW (to delegate it to repository and obviously EF) it does not save to the database.
My generic repository:
public class GenericRepository<TEntity> where TEntity : class
{
internal MyDBContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(MyDBContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
internal virtual IQueryable<TEntity> BuildQuery(Expression<Func<TEntity,bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = dbSet.AsNoTracking();
foreach (var include in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(include);
}
if (filter != null)
query = query.Where(filter);
if (orderBy != null)
return orderBy(query);
return query;
}
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = BuildQuery(filter, orderBy, includeProperties);
return query.ToList();
}
public virtual void Update(TEntity entity)
{
dbSet.Attach(entity);
context.Entry<TEntity>(entity).State = EntityState.Modified;
}
}
My Unit of Work implementation
public class UnitOfWork : IDisposable
{
//code removed for clarity
public GenericRepository<CorporateServiceCategory> ServiceCategories
{
get
{
if(this.serviceCategoryRepository == null)
{
serviceCategoryRepository = new GenericRepository<CorporateServiceCategory>(context);
}
return serviceCategoryRepository;
}
}
public void Commit()
{
context.SaveChanges();
}
}
What I'm trying to do is:
using(var unit = new UnitOfwork())
{
//unit.Companies is a generic repository instance for Company entities.
var company = unit.Companies.Get(filter: f => f.Id == 1).SingleOrDefault();
company.ServiceCategories.Add(new ServiceCategory {Name = "Demo"});
unit.Companies.Update(company);
//This is a call to context.SaveChanges();
unit.Commit();
}
I expect this code to create a new Company -> ServiceCategory association and add a record to the database. When I do the same operation without Unit of Work but using DbContext itself, it works.
What am I doing wrong with my UoW & Generic Repository implementation?
Thanks to SOfanatic's comment, the problem has solved now.
I've updated my GenericRepository's BuildQuery method to reflect SOfanatic's suggestion and it worked.
Here is the updated BuildQuery method:
internal virtual IQueryable<TEntity> BuildQuery(Expression<Func<TEntity,bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = this.context.IsReadOnly ? dbSet.AsNoTracking() : dbSet;
foreach (var include in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(include);
}
if (filter != null)
query = query.Where(filter);
if (orderBy != null)
return orderBy(query);
return query;
}
DbContext.IsReadOnly is a custom property I added to my DbContext implementation. That way if I want to load entities in a kind of "read-only" mode (only selects) I disable lazy loading, proxy generation and change tracking so it increases EF performance a bit.
today I wanted to start unit-testing a little asp.net MVC 3 Web (test) application to learn some new stuff.
But things went worse then I had expected ...
I've now read some threads about unit testing in relation with Entity framework and now I first want to implement interfaces for my entity framework related classes so that I can implement an in memory "database" for my unit tests.
My code base is from ASP.NET MVC tutorial. I've read MSDN but it doesn't help me in my cases.
I'd like to show you my code. I'm using a unit of work pattern with repositories:
Unit of work:
public class SqlUnitOfWork : IUnitOfWork, IDisposable
{
private SqlContext context = new SqlContext();
private IGenericRepository<Message> messageRepository;
private IGenericRepository<Receipt> receiptRepository;
private IGenericRepository<Useraccount> useraccountRepository;
private bool disposed = false;
public IGenericRepository<Message> MessageRepository
{
get
{
if (this.messageRepository == null)
{
this.messageRepository = new SqlGenericRepository<Message>(context);
}
return messageRepository;
}
}
public IGenericRepository<Receipt> ReceiptRepository
{
get
{
if (this.receiptRepository == null)
{
this.receiptRepository = new SqlGenericRepository<Receipt>(context);
}
return receiptRepository;
}
}
public IGenericRepository<Useraccount> UseraccountRepository
{
get
{
if (this.useraccountRepository == null)
{
this.useraccountRepository = new SqlGenericRepository<Useraccount>(context);
}
return useraccountRepository;
}
}
public SqlUnitOfWork()
{
}
~SqlUnitOfWork()
{
}
public virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public void Save()
{
context.SaveChanges();
}
}
This one implements an interface I've created.
My generic repository for sql:
public class SqlGenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
internal SqlContext context;
internal DbSet<TEntity> dbSet;
public SqlGenericRepository(SqlContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
~SqlGenericRepository()
{
}
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
It implements an interface I've programmed:
public interface IGenericRepository<TEntity> where TEntity : class
{
IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "");
TEntity GetByID(object id);
void Insert(TEntity entity);
void Delete(object id);
void Delete(TEntity entityToDelete);
void Update(TEntity entityToUpdate);
}
I would now like to implement an "InMemoryGenericRepository" for my unit tests, then an "InMemoryUnitOfWork".
How would those "InMemoryGenericRepository" look like?
I think I would use a generic List inside this repository where all data is stored:
IEnumerable<TEntity> List { get; set; }
But how can I adapt this method:
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
so it's working with my
IEnumerable<TEntity> List { get; set; }
I hope you made it until the end of my question.
If your classes are POCO then they're looking like this:
namespace Project.Model // auto-generated Foo.cs
{
public partial class Foo // notice partiality
{
}
}
Then you write this:
namespace Project.Model // custom Foo.cs in another folder
{
public partial class Foo : IEntity // notice interface
{
}
}
The generic repository pattern is not a good pattern. It gets you a lot of pain in exchange for storage independence (which you likely don't need. You like to have it but you don't really need it). You will really have a hard time to write a queryable in-memory repository for a few reasons:
the entities have relationships which must be setup correctly
in-memory queries behave differently than SQL queries (for example, they are case-sensitive)
in-memory queries have no optimizer which can lead to excessive runtime of tests
you now have to test the tests, basically, so you can be sure that no difference in behavior exists
Also, by abstracting away your ORM you can use none of its features. You can only use the most generic and general features.
I have done exactly this a few years ago and the pain just doesn't end. I found it to be a good pattern to use a real SQL database for tests. That way you don't need any repository and can test the real thing. This has its problems but it is workable.
The solution to this question is to abandon the generic repository pattern.
Also you could rewrite your lazy properties with:
private IGenericRepository<Message> messageRepository;
public IGenericRepository<Message> MessageRepository
{
get
{
return messageRepository ?? (messageRepository = new IGenericRepository<Message>());
}
}
or
private Lazy<IGenericRepository<Message>> messageRepository = new Lazy<IGenericRepository<Message>>(new IGenericRepository<Message>()));
public IGenericRepository<Message> MessageRepository
{
get
{
return messageRepository.Value;
}
}