I am unit testing ABP, but I got error below:
Cannot access a disposed object. A common cause of this error is
disposing a context that was resolved from dependency injection and
then later trying to use the same context instance elsewhere in your
application. This may occur if you are calling Dispose() on the
context, or wrapping the context in a using statement. If you are
using dependency injection, you should let the dependency injection
container take care of disposing context instances. Object name:
'XXXDbContext'.
Here are my detailed steps:
AppService
public async Task<ProductDto> CreateProduct(CreateProductInput input)
{
var existing = // await _productManager.ProductRepository.FirstOrDefaultAsync(p => p.Name == input.Name);
await _productManager.Products.Where(p => p.Name == input.Name).FirstOrDefaultAsync();
if (existing != null) throw new UserFriendlyException(L("ExistedRepeatedAd"));
var newAdEntity = ObjectMapper.Map<Product>(input);
// Rest of the code
}
ProductManager
public class ProductManager : IDomainService
{
private readonly IRepository<Product, long> _ProductRepository;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public ProductsManager(
IRepository<Product, long> ProductRepository,
IUnitOfWorkManager unitOfWorkManager)
{
_ProductRepository = ProductRepository;
_unitOfWorkManager = unitOfWorkManager;
}
#region Products
public IRepository<Product, long> ProductRepository
{
get { return _ProductRepository; }
}
public IQueryable<Product> Products
{
get { return _ProductRepository.GetAll(); }
}
public async Task<Product> CreateProduct(Product input)
{
var result = await _ProductRepository.InsertAsync(input);
await _unitOfWorkManager.Current.SaveChangesAsync();
return result;
}
#endregion
}
It will throw error this line:
await _adManager.Ads.Where(p => p.Name == input.Name).FirstOrDefaultAsync();
But if I use this instead, it will work:
await _adManager.AdRepository.FirstOrDefaultAsync(p => p.Name == input.Name);
In addition, I get _unitOfWorkManager.Current as null in the above code.
Is there any suggestion?
UnitOfWork Attribute
Add [UnitOfWork] attribute and make it a virtual method:
[UnitOfWork]
public virtual async Task<ProductDto> CreateProduct(CreateProductInput input)
{
var existing = await _productManager.Products
.Where(p => p.Name == input.Name)
.FirstOrDefaultAsync();
// ...
}
[UnitOfWork]
public virtual async Task<Product> CreateProduct(Product input)
{
var result = await _ProductRepository.InsertAsync(input);
await _unitOfWorkManager.Current.SaveChangesAsync();
return result;
}
See: UnitOfWork Attribute Restrictions
You can use UnitOfWork attribute for:
All public or public virtual methods for classes that are used over an interface (Like an application service used over a service interface).
All public virtual methods for self-injected classes (Like MVC Controllers and Web API Controllers).
All protected virtual methods.
IUnitOfWorkManager
You can inject IUnitOfWorkManager to begin a UnitOfWork explicitly:
public async Task<Product> CreateProduct(Product input)
{
using (var uow = _unitOfWorkManager.Begin())
{
var result = await _ProductRepository.InsertAsync(input);
await _unitOfWorkManager.Current.SaveChangesAsync();
await uow.CompleteAsync();
return result;
}
}
My issue is resolved by creating Interface for my appservice and then Resolve this Interface in my test projects.
Thanks for the suggestion from aaron, but it would be complex to using uniofwork in my every application service.
Related
In my ASP.NET Core 6 Web API project, I am implementing Repository and UnitOfWork:
I have this code:
IMerchantRepository:
public interface IAdminMerchantRepository : IGenericRepository<Merchant>
{
IQueryable<AllMerchantListDto> GetAllMerchantAsync(PagingFilter filter);
}
MerchantRepository:
public class AdminMerchantRepository : GenericRepository<Merchant>, IAdminMerchantRepository
{
private readonly ApplicationDbContext _dbContext;
private readonly DbSet<Merchant> _adminMerchants;
public AdminMerchantRepository(ApplicationDbContext dbContext) : base(dbContext)
{
_dbContext = dbContext;
_adminMerchants = _dbContext.Set<Merchant>();
}
public IQueryable<AllMerchantListDto> GetAllMerchantAsync(PagingFilter filter)
{
var merchants = _dbContext.Merchants
.Where(x => string.IsNullOrEmpty(filter.SearchQuery) || x.User.UserName.ToLower().Contains(filter.SearchQuery.ToLower())
|| x.User.Email.ToLower().Contains(filter.SearchQuery.ToLower())
|| x.User.FirstName.ToLower().Contains(filter.SearchQuery.ToLower())
|| x.User.LastName.ToLower().Contains(filter.SearchQuery.ToLower())
|| x.MerchantName.ToLower().Contains(filter.SearchQuery.ToLower()))
.Include(x => x.User)
.OrderByDescending(x => x.CreatedAt);
return (IQueryable<AllMerchantListDto>)merchants;
}
}
IUnitOfWork:
public interface IUnitOfWork : IDisposable
{
IAdminMerchantRepository AdminMerchants { get; }
Task Save();
}
UnitOfWork:
public class UnitOfWork : IUnitOfWork
{
private readonly ApplicationDbContext _dbContext;
private IAdminMerchantRepository _adminMerchants;
public UnitOfWork(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public IAdminMerchantRepository AdminMerchants => _adminMerchants ??= new AdminMerchantRepository(_dbContext);
public async Task Save()
{
await _dbContext.SaveChangesAsync();
}
public void Dispose()
{
_dbContext.Dispose();
GC.SuppressFinalize(this);
}
}
I got this error:
Non-invocable member 'IUnitOfWork.AdminMerchants' cannot be used like a method
Then this AdminMerchants highlighted in:
var merchant = await _unitOfWork.AdminMerchants(filter);
It look like you're calling
var merchant = await _unitOfWork.AdminMerchants(filter);
where you mean to be calling
var merchant = await _unitOfWork.AdminMerchants.GetAllMerchantAsync(filter);.
GetAllMerchantAsync is not async method. You need call await _unitOfWork.AdminMerchants.GetAllMerchantAsync(filter).ToListAsync() and better to rename method to GetAllMerchant.
Your query will fail, because you have produced not IQueryable<AllMerchantListDto> but IQueryable<Merchant>. That's why you have applied wrong explicit cast.
It should be:
return merchants.Select(m => new AllMerchantListDto
{
... // assign properties
});
Do not create additional abstractions if they are not needed. DbContext is already Unit Of Work and DbSet is already Repository. GetAllMerchant can be just extension method and no additional abstractions are needed.
So I'm trying to mock a service of mine, here's the real code:
public class PhaseService : IPhaseService
{
private readonly IRepository<Phase> _phaseRepository;
private readonly IMapper _mapper;
private readonly HrbContext _context;
public PhaseService(IRepository<Phase> phaseRepository, IMapper mapper, HrbContext context)
{
_phaseRepository = phaseRepository;
_mapper = mapper;
_context = context;
}
public async Task<PhaseDto> GetAsync(Guid id)
{
var result = await _phaseRepository.GetActiveAsync(id);
return _mapper.Map<PhaseDto>(result);
}
}
It uses an Extension Method, this is here:
namespace HRB_Server.Application.Extensions
{
public static class RepositoryExtensions
{
/// <summary>
/// Returns the entity to which the given id is a match (no navigation properties loaded). Throws exceptions if the entity is not found or if is not active.
/// </summary>
public static async Task<T> GetActiveAsync<T>(this IRepository<T> repo, Guid id)
where T : BaseEntity
{
T entity = await repo.GetAsync(id);
if (entity == null)
{
throw new EntityNotFoundException(typeof(T), id);
}
if (!entity.IsActive)
{
throw new EntityNotActiveException(typeof(T), id);
}
return entity;
}
}
Here's my xUnit test:
namespace HRB_Server.Tests.Services
{
public class PhaseServiceTest
{
private readonly Mock<IRepository<Phase>> _repository;
private readonly Mock<IMapper> _mapper;
private readonly Mock<HrbContext> _context;
public PhaseServiceTest()
{
_repository = new Mock<IRepository<Phase>>();
//_mapper = new Mock<IMapper>();
_mapper = null;
//_context = new Mock<HrbContext>(new DbContextOptions<HrbContext>(), new HttpContextAccessor());
_context = null;
}
[Fact]
public void GetPhase_ActivePhaseObject_PhaseShouldExist()
{
// Arrange
var newGuid = Guid.NewGuid();
var phase = GetSamplePhase(newGuid);
_repository.Setup(x => RepositoryExtensions.GetActiveAsync<Phase>(_repository, It.IsAny<Guid>()))
.Returns(GetSamplePhase(newGuid));
var phaseService = new PhaseService(_repository.Object, _mapper.Object, _context.Object);
// Act
var result = phaseService.GetAsync(newGuid);
// Assert (expected, actual)
Assert.Equal(phase.Result.Id, newGuid);
}
}
The error I'm getting is in the Setup of the _repository:
repository.Setup(x => RepositoryExtensions.GetActiveAsync<Phase>(_repository, It.IsAny<Guid>()))
.Returns(GetSamplePhase(newGuid));
It says it cannot convert the Mocked repository to a real one, but shouldn't I use the mocked repository here?
What I'm trying to achieve is testing my REAL service and mocking the repository, right? Am I doing it correctly here?
Assuming you are using MOQ, do not try to mock the extension method.
Since you control the code of the extension method then mock a safe path through the extension method.
The extension uses GetAsync in this case and that is what needs to be mocked assuming that is not an extension as well.
//...
_repository
.Setup(x => x.GetAsync(It.IsAny<Guid>()))
.ReturnsAsync(GetSamplePhase(newGuid));
//...
It will allow the test when exercised to go through GetActiveAsync code and if it fails, also throw the Exceptions etc as described in the code.
A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.
My unitofwork code
public class UnitOfWork : IUnitOfWork
{
private readonly CAMSDbEntities _context;
private bool _disposed;
public Dictionary<Type, object> repositories = new Dictionary<Type, object>();
private Guid _objectId;
public UnitOfWork(IContextFactory contextFactory)
{
_context = contextFactory.DbContext as CAMSDbEntities;
_objectId = Guid.NewGuid();
}
public IGenericRepository<T> Repository<T>() where T : class
{
if (repositories.Keys.Contains(typeof(T)) == true)
{
return repositories[typeof(T)] as GenericRepository<T>;
}
GenericRepository<T> repo = new GenericRepository<T>(_context);
repositories.Add(typeof(T), repo);
return repo;
}
My unity config
container.RegisterType<IHttpContext, HttpContextObject>();
container.RegisterType<IDataBaseManager, DataBaseManager>();
container.RegisterType<IContextFactory, ContextFactory>();
container.RegisterType(typeof(IGenericRepository<>), typeof(GenericRepository<>));
container.RegisterType<IUnitOfWork, UnitOfWork>();
container.RegisterType<IAnalytics, DashbordService>();
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
webApi Controller
public class DashbordController : ApiController
{
private static IAnalytics _analytics;
public DashbordController(IAnalytics dashbordService)
{
_analytics = dashbordService;
}
[HttpGet]
[Route("GetStudentAssessmentHistory")]
public IHttpActionResult GetStudentAssessmentHistory(int studentID)
{
var result = _analytics.GetStudentAssessmentHistoryGraphData(studentID);
return Ok(result);
}
[HttpGet]
[Route("GetStudentFeePaymentHistory")]
public async Task<IHttpActionResult> GetStudentFeePaymentData(int studentID)
{
var result = await _analytics.GetStudentFeePaymentData(studentID);
return Ok(result);
}
[HttpGet]
[Route("GetLedgerHitoryByDepartment")]
public async Task<IHttpActionResult> GetLedgerHitoryByDepartment(int schoolID, int departmentId)
{
var result = await _analytics.GetLedgerHitory(schoolID, departmentId);
return Ok(result);
}
[HttpGet]
[Route("GetLedgerExpenseTrendByDepartment")]
public async Task<IHttpActionResult> GetLedgerExpenseTrendByDepartment(int schoolID)
{
var result = await _analytics.GetLedgerExpenseTrend(schoolID);
return Ok(result);
}
dashboardservice Code
public async Task<List<LedgerExpense>> GetLedgerExpenseTrend(int schoolId)
{
try
{
var ledgerExpenses = new List<LedgerExpense>();
var currentDate = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, INDIAN_ZONE);
DateTime previoYearDate = currentDate.AddYears(-1);
var ledgerPayments = await _unitOfWork.Repository<LedgerDetail>().GetManyAsync(x => x.SchoolID == schoolId && x.PaymentDate <= currentDate
&& x.PaymentDate >= previoYearDate);
foreach (var ledgerPayment in ledgerPayments.OrderBy(x => x.PaymentDate).GroupBy(y => y.DepartmentID))
{
var department = await _unitOfWork.Repository<DeptartmentType>().GetAsync(x => x.ID == ledgerPayment.Key);
var ledgerData = new LedgerExpense
{
Department = department.DepartmentName,
TotalLedgerExpense = 0
};
foreach (var departmentPayment in ledgerPayment)
{
ledgerData.TotalLedgerExpense += departmentPayment.TotalPaidAmount;
}
ledgerExpenses.Add(ledgerData);
}
return ledgerExpenses;
}
catch (Exception ex)
{
logger.Log("An error occurred while fetching ledger expenses");
return null;
}
}
I have similar type of asynchronous metods implemented in my dashboardservice code. whenever I request a dashboard UI all request comes to the same controller at the same time and creates the new object for unitofwork and dbcontext for each request one by one. it works perfectly sometimes but Sometimes I think unitofwork and dbcontext object flows with the wrong thread and throws this error. I think somehow its picking wrong dbcontext which is already busy with someother api request from dashboard service.
Please remove the static keyword in your controller from this code:
private static IAnalytics _analytics;`
Once that has been created, it will never be created again unless the application pool is recycled (manual or IIS restart etc.) Since you are using the same instance for all requests, you are getting that error at random. If a request finishes before the next one arrives, it will NOT result in an error. Otherwise it will. Hence the reason for not always getting the error (as you mention in your question).
Please read about how static affects the design in a web scenario (or server).
Try and think of web requests as a single transaction, all classes are created for each request and then thrown away after the request has been served. That means if you have static or any other mechanism which is for sharing, it will be shared between requests.
I'm reading a lot about Entity Framework async lately, and I'm looking for the correct way to change my repositories to async versions. They now look more or less as follows:
public class MyRepo
{
private DbContext _context;
public MyRepo()
{
_context = new DbContext();
}
public int DoSomeStuff(int id)
{
var item = _context.MyModels.Where(x => x.Id == id).SingleOrDefault();
item.MyProperty = "abcd";
return _context.SaveChanges();
}
}
Example of calling this repository:
var myRepo = new MyRepo();
myRepo.DoSomeStuff(1);
myRepo.DoSomeStuff(2);
Can I just change the DoSomeStuff function to:
public async Task<int> DoSomeStuffAsync(int id)
{
var item = await _context.MyModels.Where(x => x.Id == id).SingleOrDefaultAsync();
item.MyProperty = "abcd";
return await _context.SaveChangesAsync();
}
or is this the wrong way of handling the DbContext instance, and should it be:
public async Task<int> DoSomeStuffAsync(int id)
{
using(var db = new DbContext())
{
// ...
}
}
You can do it either way, depending on the semantics you want. If you're using a shared DbContext, you just have to document that so your callers will only call one asynchronous method at a time.
I have a controller action that gets invoked directly, but throws this error:
The operation cannot be completed because the DbContext has been disposed.
I have only found solutions online regarding deferred excecution, but I don't think that applies here, because everywhere I use the context (in this instance) I call either .ToList() or .FirstOrDefault(). Here is my code:
CONTROLLER CONTENT
private IUnitOfWork UnitOfWork;
public MyFavouritesController(
IAccountServices accountServices,
IUnitOfWork unitOfWork
)
{
AccountServices = accountServices;
UnitOfWork = unitOfWork;
}
public ActionResult Index()
{
int? id = AccountServices.GetCurrentUserId();
if (!id.HasValue)
{
return RedirectToAction("Login", "Account", new { ReturnUrl = this.HttpContext.Request.Url.AbsolutePath });
}
var user = UnitOfWork.UserRepo.Get(id.Value, "Favourites", "Favourites.County", "Favourites.Country");
//THE ABOVE CALL GETS THE ERROR
//.....
return View();
}
REPOSITORY BASE CLASS
public class RepositoryBase<C, T> : IDisposable
where C:DbContext, new()
where T : ModelBase
{
private DbContext _context;
public DbContext Context
{
get
{
if (_context == null)
{
_context = new C();
this.AllowSerialization = true;
}
return _context;
}
set
{
_context = value;
}
}
public virtual T Get(int Id, params string[] includes)
{
if (Id > 0)
{
var result = Context.Set<T>().Where(t => t.Id == Id);
foreach (string includePath in includes)
{
result = result.Include(includePath);
}
return result.FirstOrDefault(); //This is where the error occurs.
}
else
{
throw new ApplicationException("Id is zero (0).");
}
}
//... (More CRUD methods)
public void Dispose()
{
if (Context != null)
{
Context.Dispose(); //Debugger never hits this before the error
}
}
}
UNIT OF WORK CLASS
public class UnitOfWork:IUnitOfWork
{
public UnitOfWork(
//... DI of all repos
IUserRepository userRepo
)
{
//... save repos to an local property
UserRepo = userRepo;
//create a new instance of the context so that all the repo's have access to the same DbContext
Context = new Context();
//assign the new context to all the repo's
//...
UserRepo.Context = Context;
}
public Context Context { get; set; }
public IUserRepository UserRepo { get; set; }
//... (some more repositories)
public void Dispose()
{
Context.Dispose(); //THIS IS NOT HIT AT ALL
}
}
LASTLY, THE MODEL CONTAINER HAS THIS LINE
_Instance.RegisterType<IUnitOfWork, UnitOfWork>(new PerThreadLifetimeManager());
As you can see, the index action will recieve a new instance of UnitOfWork which contains a new DbContext object. But at the first call to this context, it throws the above error. This pattern works everywhere else in my code.
Thanks
UPDATE
The answer below was to use a perRequestLifetimeManager. Here is the implimentation of one in unity:
public class HttpRequestLifetimeManager : LifetimeManager
{
private string _key = Guid.NewGuid().ToString();
public override object GetValue()
{
if (HttpContext.Current != null && HttpContext.Current.Items.Contains(_key))
return HttpContext.Current.Items[_key];
else
return null;
}
public override void RemoveValue()
{
if (HttpContext.Current != null)
HttpContext.Current.Items.Remove(_key);
}
public override void SetValue(object newValue)
{
if (HttpContext.Current != null)
HttpContext.Current.Items[_key] = newValue;
}
}
I noticed you're using a PerThreadLifetimeManager to control the creation and disposal of your unit of work class. You should probably change it to something like PerRequestLifetimeManager if your IoC container supports that.
Its because your are disposing the Unit Of Work, after wich you are requesting your data, store your data in a Variable after the query then you can release the Unit Of Work instance as well.