Is there a way I can accomplish something like this:
var builder = new ContainerBuilder();
builder.Register(c => c.Resolve<DbContext>().Set<TEntity>()).As(IDbSet<TEntity>);
Sure, and there's even a pattern for that. It's called the repository pattern:
public interface IRepository<TEntity>
{
IQueryable<TEntity> GetAll();
TEntity GetById(Guid id);
}
public class EntityFrameworkRepository<TEntity> : IEntity<TEntity>
{
private readonly DbContext context;
public EntityFrameworkRepository(DbContext context) {
this.context = context;
}
public IQueryable<TEntity> GetAll() {
return this.context.Set<TEntity>();
}
public TEntity GetById(Guid id) {
var item = this.context.Set<TEntity>().Find(id);
if (item == null) throw new KeyNotFoundException(id.ToString());
return item;
}
}
You can register it as follows:
builder.RegisterGeneric(typeof(EntityFrameworkRepository<>)).As(typeof(IRepository<>));
Related
My Generic repository class
public class Repository<TEntity, TId> : IRepository<TEntity, TId> where TEntity : class, IEntity<TId>
{
protected readonly CBSContext _context;
//private DbSet<TEntity> _entities;
public Repository(CBSContext context)
{
_context = context ?? throw new ArgumentNullException(nameof(context));
// _entities = _context.Set<TEntity>();
}
public async Task<TEntity> AddAsync(TEntity entity)
{
await Task.Run(() => _context.Add(entity));
return entity;
}
}
Am using ef core 5 Rc.. After insert id is not returning ? Any thing else need to be done..Thanks
EDIT:
After code change to not working..
public async Task<TEntity> AddAsync(TEntity entity)
{
await _context.AddAsync(entity);
return entity;
}
You need to use _context.SaveChangesAsync() after Add(). Then you can get id.
I am working on an API and am having problems with making multiple calls to a service and it's different methods, I have each method creating and using new DBContext (or at least that's the intention), but after the first service call the others complain that the DBContext has been disposed, I was hoping you could point me in the right direction, because as far as I can see I am creating a new context for each of these calls - obviously I am doing something wrong here, any help would be much appreciated.
The actual error I am getting is "Cannot access a disposed object."
I know I can maybe pull the db interaction and context creation code out of the service and into the controller method here (it's a simplified example), but will need to use more services in other parts of the application and have encountered the problem there also, so would like to try and identify what is causing my problem in this example so that I can apply the fix elsewhere.
Here are the simplified classes involved.
public class UserController : Controller
{
private readonly IUserService userService;
public UserController(IUserService userService)
{
this.userService = userService;
}
[HttpPost]
[ActionName("PostUserDetails")]
public async Task<IActionResult> PostUserDetails([FromBody]UserDetailsContract userDetailsContract)
{
// this call is fine
var user = await userService.GetUserByCode(userDetailsContract.Code);
if (user == null)
{
return BadRequest("User not found");
}
// this call fails with the object disposed error
var userDetails = await userService.GetUserDetailsByCode(userDetailsContract.Code);
if (userDetails != null)
{
return BadRequest("UserDetails already exists");
}
// .. go on to save new entity
return Ok();
}
}
public class UserService : IUserService
{
private readonly IDatabaseFactory databaseFactory;
public UserService(IDatabaseFactory databaseFactory)
{
this.databaseFactory = databaseFactory;
}
public async Task<User> GetUserByCode(string code)
{
using (var db = databaseFactory.Create())
{
return await db.Users.GetByCode(code);
}
}
public async Task<IEnumerable<UserDetail>> GetUserDetailsByCode(string code)
{
using (var db = databaseFactory.Create())
{
return await db.UserDetails.GetByCode(code);
}
}
}
public class ApiDbContext : DbContext, IApiDbContext
{
public DbSet<User> Users { get; set; }
public DbSet<UserDetail> UserDetails { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=192.168.1.1;Database=dbname;User Id=user; Password=pwd; MultipleActiveResultSets=True;");
}
}
public class DatabaseFactory : IDatabaseFactory
{
public IApiDatabase Create()
{
return new ApiDatabase(new ApiDbContext());
}
}
public class ApiDatabase : RepositoriesBase, IApiDatabase
{
private IUserRepository userRepository;
private IUserDetailsRepository userDetailsRepository;
public ApiDatabase(ApiDbContext context) : base(context)
{
}
public IUserRepository Users => userRepository ?? (userRepository = new UserRepository(context));
public IUserDetailsRepository UserExchange => userDetailsRepository ?? (userDetailsRepository = new UserDetailsRepository(context));
}
public abstract class RepositoriesBase : IRepositories
{
internal readonly ApiDbContext context;
private bool isDisposing;
protected RepositoriesBase(ApiDbContext context)
{
}
public void Dispose()
{
if (!isDisposing)
{
isDisposing = true;
context?.Dispose();
}
}
public Task SaveChanges() => context.SaveChangesAsync();
}
public class UserRepository : Repository<User>, IUserRepository
{
public UserRepository(ApiDbContext context) : base(context)
{
}
public async Task<User> GetByCode(string code)
{
return Filter(x => x.code == code).Result.FirstOrDefault();
}
}
public class UserDetailsRepository : Repository<UserDetail>, IUserDetailRepository
{
public UserExchangeRepository(ApiDbContext context) : base(context)
{
}
public async Task<IEnumerable<UserDetail>> GetByUserId(int userId)
{
return await Filter(x => x.UserId == userId);
}
}
public class Repository<T> : IRepository<T> where T : class, IEntity
{
private readonly ApiDbContext context;
public Repository(ApiDbContext context) => this.context = context;
public async Task Add(T entity)
{
context.Set<T>().Add(entity);
}
public async Task Add(IEnumerable<T> entities)
{
foreach (var entity in entities)
{
context.Set<T>().Add(entity);
}
}
public async Task Delete(T entity)
{
context.Set<T>().Remove(entity);
}
public async Task Delete(IEnumerable<T> entities)
{
foreach (var entity in entities)
{
context.Set<T>().Remove(entity);
}
}
public async Task Delete(int id)
{
var entityToDelete = context.Set<T>().FirstOrDefault(e => e.Id == id);
if (entityToDelete != null)
{
context.Set<T>().Remove(entityToDelete);
}
}
public async Task Update(T entity)
{
context.Set<T>().Update(entity);
}
public async Task Edit(T entity)
{
var editedEntity = context.Set<T>().FirstOrDefault(e => e.Id == entity.Id);
editedEntity = entity;
}
public async Task<IEnumerable<T>> GetAll(Expression<Func<T, bool>> predicate = null)
{
var query = context.Set<T>().Include(context.GetIncludePaths(typeof(T)));
if (predicate != null)
{
query = query.Where(predicate);
}
return await query.ToListAsync();
}
public async Task<T> GetById(int id)
{
return context.Set<T>().FirstOrDefault(e => e.Id == id);
}
public async Task<IEnumerable<T>> Filter()
{
return context.Set<T>();
}
public virtual async Task<IEnumerable<T>> Filter(Func<T, bool> predicate)
{
return context.Set<T>().Where(predicate);
}
public async Task SaveChanges() => context.SaveChanges();
}
In my DI config I have DatabaseFactory and UserService defined as singletons.
Error: "Cannot access a disposed object."
More error details: " at
Microsoft.EntityFrameworkCore.DbContext.CheckDisposed() at
Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies()
at Microsoft.EntityFrameworkCore.DbContext.get_Model() at
Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.get_EntityType()
at
Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.get_EntityQueryable()
at
Microsoft.EntityFrameworkCore.Internal.InternalDbSet1.System.Collections.Generic.IEnumerable<TEntity>.GetEnumerator()
at System.Linq.Enumerable.WhereEnumerableIterator1.MoveNext() at
System.Linq.Enumerable.Any[TSource](IEnumerable1 source, Func2
predicate) at
App.Api.Controllers.UserController.PostUserDetail(UserDetailContract
userDetailContract) in
D:\Repositories\application\src\App\Api\Controllers\UserController.cs:line
89"
Thank you
I think you may be a victim of delayed execution. The following piece of code creates an instance of of ApiDatabase which in turn creates a new ApiDbContext:
public IApiDatabase Create() //in DatabaseFactory
{
return new ApiDatabase(new ApiDbContext());
}
I detect a code smell here, by the way, as ApiDbContext is disposable so you should be tracking this reference and disposing of it properly.
Anyways, ApiDatabase is disposable since it's wrapped in a using statement, so I think the the context is being disposed after the call to GetByUserId:
public async Task<IEnumerable<UserDetail>> GetByUserId(int userId)
{
return await Filter(x => x.UserId == userId);
}
Notice you are returning an enumeration. I think it may not be materialized by the time you use it, hence the error. Add a cast to an array to force materialization:
return await Filter(x => x.UserId == userId).ToArray();
Your problem is the signature of this method:
public async Task<IEnumerable<UserDetail>> GetUserDetailsByCode(string code)
{
using (var db = databaseFactory.Create())
{
return await db.UserDetails.GetByCode(code);
}
}
IEnumerable<T> is an enumerable, which are generally lazy-evaluated. In the meantime, the Task<T> is considered complete once the enumerable is defined (not when it is completed). And the context is disposed once that enumerable is defined. You would have the same problem if the code was synchronous.
The fix is to "reify" (evaluate) the enumerable before the context is disposed:
public async Task<IReadOnlyCollection<UserDetail>> GetUserDetailsByCode(string code)
{
using (var db = databaseFactory.Create())
{
return await db.UserDetails.GetByCode(code).ToList();
}
}
I am following the Repository pattern and implementing UnitOfWork.
Here is the issue:
Within the EmployeeController, there is a post call titled AddEmployee(). This action method gets the appropriate data and the result comes back successful, but the data is not getting saved to the database. The action method is also calling the SaveEmployee() method, which should in theory save the data.
Git Repo: https://bitbucket.org/ChaseHardin/myapp
Question: Why isn't the UnitOfWork saving the database changes?
Controller:
[HttpPost]
public HttpResponseMessage AddEmployee([FromBody]Employee employee)
{
if (ModelState.IsValid)
{
_employeeService.AddEmployee(employee);
_employeeService.SaveEmployee();
return new HttpResponseMessage(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
}
Service:
public class EmployeeService : IEmployeeService
{
private readonly IEmployeeRepository _employeeRepository;
private readonly IUnitOfWork _unitOfWork;
public EmployeeService(IUnitOfWork unitOfWork, IEmployeeRepository employeeRepository)
{
_unitOfWork = unitOfWork;
_employeeRepository = employeeRepository;
}
public Employee GetEmployee(int id)
{
return _employeeRepository.GetById(id);
}
public void SaveEmployee()
{
_unitOfWork.Commit();
}
public void AddEmployee(Employee employee)
{
_employeeRepository.Add(employee);
}
}
public interface IEmployeeService
{
Employee GetEmployee(int id);
void SaveEmployee();
void AddEmployee(Employee employee);
}
UnitOfWork
public class UnitOfWork : IUnitOfWork
{
private readonly IDbFactory dbFactory;
private MyAppEntities dbContext;
public UnitOfWork(IDbFactory dbFactory)
{
this.dbFactory = dbFactory;
}
public MyAppEntities DbContext
{
get { return dbContext ?? (dbContext = dbFactory.Init()); }
}
public void Commit()
{
DbContext.Commit();
}
}
public interface IUnitOfWork
{
void Commit();
}
MyAppEntities:
public class MyAppEntities : DbContext
{
public MyAppEntities() : base("MyAppEntities") { }
public DbSet<Employee> Employees { get; set; }
public virtual void Commit()
{
SaveChanges();
}
}
EmployeeRepository
public class EmployeeService : IEmployeeService
{
private readonly IEmployeeRepository _employeeRepository;
private readonly IUnitOfWork _unitOfWork;
public EmployeeService(IUnitOfWork unitOfWork, IEmployeeRepository employeeRepository)
{
_unitOfWork = unitOfWork;
_employeeRepository = employeeRepository;
}
public Employee GetEmployee(int id)
{
return _employeeRepository.GetById(id);
}
public void SaveEmployee()
{
_unitOfWork.Commit();
}
public void AddEmployee(Employee employee)
{
_employeeRepository.Add(employee);
}
}
public interface IEmployeeService
{
Employee GetEmployee(int id);
void SaveEmployee();
void AddEmployee(Employee employee);
}
Base Repo
public abstract class BaseRepository <T> where T : class
{
private MyAppEntities _dataContext;
private readonly IDbSet<T> _dbSet;
protected IDbFactory DbFactory { get; private set; }
protected MyAppEntities DbContext
{
get { return _dataContext ?? (_dataContext = DbFactory.Init()); }
}
protected BaseRepository(IDbFactory dbFactory)
{
DbFactory = dbFactory;
_dbSet = DbContext.Set<T>();
}
#region Implementation
public virtual void Add(T entity)
{
_dbSet.Add(entity);
}
public virtual void Update(T entity)
{
_dbSet.Attach(entity);
_dataContext.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete(T entity)
{
_dbSet.Remove(entity);
}
public virtual void Delete(Expression<Func<T, bool>> where)
{
IEnumerable<T> objects = _dbSet.Where<T>(where).AsEnumerable();
foreach (T obj in objects)
_dbSet.Remove(obj);
}
public virtual T GetById(int id)
{
return _dbSet.Find(id);
}
public virtual IEnumerable<T> GetAll()
{
return _dbSet.ToList();
}
public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where)
{
return _dbSet.Where(where).ToList();
}
public T Get(Expression<Func<T, bool>> where)
{
return _dbSet.Where(where).FirstOrDefault<T>();
}
#endregion
}
public interface IBaseRepository<T> where T : class
{
void Add(T entity);
void Update(T entity);
void Delete(T entity);
void Delete(Expression<Func<T, bool>> where);
T GetById(int id);
T Get(Expression<Func<T, bool>> where);
IEnumerable<T> GetAll();
IEnumerable<T> GetMany(Expression<Func<T, bool>> where);
}
The issue is your IOC scope. In App_Start you are binding your objects with the default Transient Scope. This is causing multiple contexts to be created, and you are adding the employee in one context and calling SaveChanges() on another.
Update your Ninject bindings to use InSingletonScope() or InRequestScope().
As #TimS mentioned it is totally about how you register your services at application start. I had same problem with similar pattern while I was using autofac instead of Ninject.
With autofac if you don't choose lifetime scope it is Instance Per Dependency scope as default. By this scope for each request an instance of registered object returns; this cause multiple context at same scope and when you commit your changes by unitofwork instance it just commit wrong instance of Context. To avoiding this you have to use single instance scope or better for this case Instance Per Lifetime Scope
This scope applies to nested lifetimes. A component with per-lifetime
scope will have at most a single instance per nested lifetime scope.
This is useful for objects specific to a single unit of work that may
need to nest additional logical units of work. Each nested lifetime
scope will get a new instance of the registered dependency.
var builder = new ContainerBuilder();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerLifetimeScope();
builder.RegisterType<DbFactory>().As<IDbFactory>().InstancePerLifetimeScope();
I am working on implementing a repository and unit of work pattern; but instead of putting my unit of work in the controller and having business logic in the controller I am implementing a request/handler to divide this logic. Is there any downside to splitting the unit of work and having my controllers access through the request/handler? See code example below:
Generic Repo:
public class GenericRepository<TEntity> where TEntity : class
{
internal MyContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(MyContext 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);
}
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);
}
The Unit of Work:
public class UnitOfWork : IDisposable
{
private MyContext context = new MyContext();
private GenericRepository<User> userRepository;
public GenericRepository<User> UserRepository
{
get
{
if (this.userRepository == null)
{
this.userRepository = new GenericRepository<User>(context);
}
return userRepository;
}
}
Then I am creating a request/handler for each of my models and instantiating Unit of Work in there:
public class UserRequest
{
private UnitOfWork unitOfWork = new UnitOfWork();
public User GetById(int id)
{ //more business logic would go here in the handlers...
return unitOfWork.UserRepository.GetById(id);
}
The controller will access the request/handlers:
public class HomeController : Controller
{
private UserRequest userRequest = new UserRequest();
public ActionResult Index()
{
var user = userRequest.GetById(1);
ViewBag.UserEmail = user.Email;
return View();
}
Note: there will be multiple request/handlers that are instantiating the unit of work instance. Could this cause issues since unit of work's purpose is to keep the context to one instance?
Thank you in advance for your feedback!
I have followed this tutorial.
I got to a stage of calling a repository using _unitOfWork.XYZRepository.Get(), now to take it further I want to write an interface for my UnitOfWork class and inject it to my controller.
I am not sure whether I need write interface for GenericRepository or UnitofWork class or both.
Can some one guide me in this as to what needs to be done to instantiate a repository with interface instead of private readonly UnitOfWork _unitOfWork = new UnitOfWork(); as shown in the link above.
Modify your repository constructor to accept a unit of work, via its interface:
public MyRepository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork;
}
Then you instantiate your repository, passing the appropriate unit of work in via the constructor. Alternatively, wire-up your IoC container of choice and let it do the heavy lifting.
Here's a nice tutorial on using Castle Windsor with ASP.NET MVC.
I have used Autofac for this purpose. In my Global.asax.cs file
var builder = new ContainerBuilder();
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerApiRequest();
builder.RegisterAssemblyTypes(typeof (LocationTypesRepository).Assembly).Where(
type => type.Name.EndsWith("Repository")).AsImplementedInterfaces();
and then in my controller
public class LocationTypesController : ApiController
{
private readonly ILocationRepository _locationRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IAuthenticatedUser _user;
public LocationTypesController(ILocationRepository locationRepository,
IUnitOfWork unitOfWork,
IAuthenticatedUser user)
{
if (locationRepository == null)
throw new ArgumentNullException("locationRepository");
if (unitOfWork == null)
throw new ArgumentNullException("unitOfWork");
if (user == null)
throw new ArgumentNullException("user");
_locationRepository = locationRepository;
_unitOfWork = unitOfWork;
_user = user;
}
public IEnumerable<LocationType> Get()
{
try
{
IEnumerable<Location> locations = _locationRepository.GetAllAuthorizedLocations(_user.UserName);
_unitOfWork.Commit();
return locations.Select(location => location.LocationType).Distinct().OrderBy(location => location.LocationTypeId);
}
catch (Exception)
{
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.BadRequest));
}
}
Essentially leveraging a DI framework and placing the interfaces as parameters to your repositories (or in my case a WebApi controller)
Based on suggestions I have made following changes...
public interface IGenericRepository<T> where T : class
{
IQueryable<T> Get();
IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
void Insert(T entity);
void Delete(T entity);
void Update(T entity);
void Save();
T GetByID(Object id);
}
public class GenericRepository<C, T> : IGenericRepository<T>
where T : class
where C : EFDbContext, new()
{
private C _entities = new C();
public C Context
{
get { return _entities; }
set { _entities = value; }
}
public virtual IQueryable<T> Get()
{
IQueryable<T> query = _entities.Set<T>();
return query;
}
public virtual T GetByID(object id)
{
return Context.Set<T>().Find(id);
}
}
//NinjectControllerFactory
private void AddBindings()
{
_ninjectKernel.Bind<IGenericRepository<Product>>().To<GenericRepository<EFDbContext, Product>>();
}
//Controller
[Inject]
public IGenericRepository<Product> ProductRepo;
public ProductController(IGenericRepository<Product> ProductRepository )
{
ProductRepo= ProductRepository ;
}
//Inside Action
model.Products = ProductRepo.Get();
Everything works now... Thanks for the help...