ObjectDisposedException trying implement DDD project - c#

When I try to return my orderList as IQueryable, I get the following error:
"ObjectDisposedException: 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: 'KeplerContext'"
Ps. If I work with the DbContext directly at my controller, everything works fine.
Some help? Thanks in advance.
My controller class is:
public class OrderController : Controller
{
public readonly IAppOrder _IAppOrder;
public readonly UserManager<User> _userManager;
// Ps.: Directly for the context works fine
//private readonly KeplerContext _context;
public OrderController(IAppOrder IAppOrder, UserManager<User> userManager) //, KeplerContext context)
{
_IAppOrder = IAppOrder;
_userManager = userManager;
// Ps.: Directly for the context works fine
//_context = context;
}
public async Task<IActionResult> Index(string sortOrder, int? pageNumber)
{
var user = await _userManager.GetUserAsync(User);
var orderList = _IAppOrder.OrderList(user);
^^^^
if (!orderList.Any())
{
TempData["info"] = "no orders.";
}
else
{
var orderActive = await _IAppOrder.GetLastOrder(user);
if (orderActive.IdOrderStatus <= 5)
{
return RedirectToAction(nameof(OrderController.Order), "Order", new { Area = "App", Id = orderActive.IdOrder });
}
}
// paging
ViewData["CurrentSort"] = sortOrder;
ViewData["NumSort"] = String.IsNullOrEmpty(sortOrder) ? "num_asc" : "";
switch (sortOrder)
{
case "num_asc":
orderList = orderList.OrderBy(s => s.IdOrder);
break;
default:
orderList = orderList.OrderByDescending(s => s.IdOrder);
break;
}
int pageSize = 8;
return View(await PagingHelper<Order>.CreateAsync(orderList.AsNoTracking(), pageNumber ?? 1, pageSize));
}
}
My application class: (Ps. I am working with DDD, others classes I have omitted to simplify the question):
public IQueryable<Order> OrderList (User user)
{
return _IOrder.OrderList(user);
}
My repository class:
public class RepositoryOrder : RepositoryGenerics<Order>, IDomOrder
{
private readonly DbContextOptions<KeplerContext> _optionsBuilder;
public RepositoryOrder()
{
_optionsBuilder = new DbContextOptions<KeplerContext>();
}
public IQueryable<Order> OrderList(User user)
{
using var db = new DbContext(_optionsBuilder);
var result = db.Order
.Include(p => p.IdOrderStatusNavigation)
.Include(p => p.IdNavigation)
.Where(u => u.Id == user.Id)
.AsQueryable();
return result;
^^^^^^ <<< Error happens here!!!
}
}
My repository generics class:
public class RepositoryGenerics<T> : IDomGeneric<T>, IDisposable where T : class
{
private readonly DbContextOptions<KeplerContext> _OptionsBuilder;
public RepositoryGenerics()
{
_OptionsBuilder = new DbContextOptions<KeplerContext>();
}
public async Task Add(T obj)
{
using var data = new KeplerContext(_OptionsBuilder);
await data.Set<T>().AddAsync(obj);
await data.SaveChangesAsync();
}
public async Task Delete(T obj)
{
using var data = new KeplerContext(_OptionsBuilder);
data.Set<T>().Remove(obj);
await data.SaveChangesAsync();
}
public async Task<T> GetEntityById(int Id)
{
using var data = new KeplerContext(_OptionsBuilder);
return await data.Set<T>().FindAsync(Id);
}
public async Task<List<T>> List()
{
using var data = new KeplerContext(_OptionsBuilder);
return await data.Set<T>().AsNoTracking().ToListAsync();
}
public async Task Update(T obj)
{
using var data = new KeplerContext(_OptionsBuilder);
data.Set<T>().Update(obj);
await data.SaveChangesAsync();
}
// https://learn.microsoft.com/pt-br/dotnet/standard/garbage-collection/implementing-dispose
bool disposed = false;
SafeHandle handle = new SafeFileHandle(IntPtr.Zero, true);
// "Dispose Pattern"
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposed)
return;
if (disposing)
{
handle.Dispose();
}
disposed = true;
}
}
Dependency injection inside the Startup.cs:
// Repository
services.AddSingleton(typeof(IDomGeneric<>), typeof(RepositoryGenerics<>));
...
services.AddSingleton<IDomOrder, RepositoryOrder>();
...
// Inteface da Application
...
services.AddSingleton<IAppOrder, AppOrder>();
...
// Service Domain
...
services.AddSingleton<IServiceOrder, ServiceOrder>();
...

Your RepositoryOrder.OrderList is returning an IQueryable meaning you are not executing the query within the scope of the method. So I believe what you are seeing is that when the query is eventually executed the DbContext has already been disposed of, because it is disposed as soon as you leave the method scope.
Instead you could call ToList rather than AsQueryable.

Related

Implementing repositories with EF Core without creating multiples methods

I've been using EF core in my project for years without repositories layer and now I decided to implement repositories pattern for one of my projects which became very big. We have more than 30 entity models and a huge list of API endpoints.
The thing is, each endpoint returns to the client the necessary data from DB formatted by the frontend needs. Some times we want just a list of an entity, other times the same list with some related data and sometimes use some SQL aggregate functions to do some calculations.
We just use the DBContext directly in each endpoint to perform the queries as we need, but when implementing the repositories, we faced an effort obstacle which is coding several methods to get the different data formatted to our needs. Not only basic CRUD and some more operations.
My question is, this really how thing are done (creating as much methods as needed) or is there any best practices to this? Is there some way "rewrite" the DBContext so that I can use expressions and turn it generic avoiding creating so mach methods?
Thank you very much!
Share my actual BaseRepo
public class BaseRepository<TEntity> : IBaseRepository<TEntity> where TEntity : class
{
internal ApplicationDbContext Context;
internal DbSet<TEntity> dbSet;
public BaseRepository(ApplicationDbContext context)
{
this.Context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual async Task AddAsync(TEntity entity)
{
await dbSet.AddAsync(entity);
await SaveAsync();
}
public virtual async Task AddRangeAsync(IEnumerable<TEntity> entities)
{
await dbSet.AddRangeAsync(entities);
await SaveAsync();
}
public virtual async Task<IEnumerable<TEntity>> GetAllAsync()
{
return await dbSet.ToListAsync();
}
public virtual async Task<IEnumerable<TEntity>> GetAsync(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 await orderBy(query).ToListAsync();
else
return await query.ToListAsync();
}
public virtual async Task<TEntity> GetByIdAsync(int? id)
{
return await dbSet.FindAsync(id);
}
public async Task Remove(TEntity entity)
{
dbSet.Remove(entity);
await SaveAsync();
}
public async Task RemoveRange(IEnumerable<TEntity> entities)
{
dbSet.RemoveRange(entities);
await SaveAsync();
}
public virtual async Task<TEntity> SingleOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
{
return await dbSet.SingleOrDefaultAsync(predicate);
}
public virtual async Task Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
Context.Entry(entityToUpdate).State = EntityState.Modified;
await SaveAsync();
}
public virtual async Task UpdateRange(IEnumerable<TEntity> entitiesToUpdate)
{
dbSet.AttachRange(entitiesToUpdate);
Context.Entry(entitiesToUpdate).State = EntityState.Modified;
await SaveAsync();
}
public async Task SaveAsync()
{
await Context.SaveChangesAsync();
}
public virtual async Task AddUpdateOrDeleteRange(IEnumerable<TEntity> entitiesToAddOrUpdate)
{
await Context.BulkInsertOrUpdateOrDeleteAsync<TEntity>(entitiesToAddOrUpdate.ToList(), new BulkConfig { SetOutputIdentity = false });
await SaveAsync();
}
public virtual async Task AddOrUpdateRange(IEnumerable<TEntity> entitiesToAddOrUpdate)
{
await Context.BulkInsertOrUpdateAsync<TEntity>(entitiesToAddOrUpdate.ToList(), new BulkConfig { SetOutputIdentity = false });
await SaveAsync();
}
}
The bulk ones are extensions from EFCore.BulkExtensions;
Unit of Work
public class UnitOfWork : IUnitOfWork, IDisposable, IAsyncDisposable
{
private readonly ApplicationDbContext _context;
private ExampleRepository _exampleRepository;
IDisposable _disposableResource = new MemoryStream();
IAsyncDisposable _asyncDisposableResource = new MemoryStream();
public UnitOfWork(ApplicationDbContext context)
{
_context = context;
}
public IExampleRepository ExampleRepository=> _exampleRepository = _exampleRepository ?? new ExampleRepository(_context);
public async Task<int> CommitAsync()
{
return await _context.SaveChangesAsync();
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public async ValueTask DisposeAsync()
{
await DisposeAsyncCore();
Dispose(disposing: false);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_disposableResource?.Dispose();
(_asyncDisposableResource as IDisposable)?.Dispose();
}
_disposableResource = null;
_asyncDisposableResource = null;
}
protected virtual async ValueTask DisposeAsyncCore()
{
if (_asyncDisposableResource is not null)
{
await _asyncDisposableResource.DisposeAsync().ConfigureAwait(false);
}
if (_disposableResource is IAsyncDisposable disposable)
{
await disposable.DisposeAsync().ConfigureAwait(false);
}
else
{
_disposableResource?.Dispose();
}
_asyncDisposableResource = null;
_disposableResource = null;
}
}
ApplicationDbContext:
public class ApplicationDbContext : DbContext
{
public DbSet<Example> Examples { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
Hope it help's you!
Create a base Crud class, something like this:
public class BaseRepository<TEntity> where TEntity : class, new()
Then create a set of standard methods:
public virtual TEntity Get(Expression<Func<TEntity, bool>> predicate)
{
using (var scope = ScopeFactory.CreateScope())
{
var Context = scope.ServiceProvider.GetRequiredService<ubContext>();
var item = Context.Set<TEntity>().FirstOrDefault(predicate);
return item;
}
}
public List<TEntity> GetList(Expression<Func<TEntity, bool>> predicate)
{
using (var scope = ScopeFactory.CreateScope())
{
var Context = scope.ServiceProvider.GetRequiredService<ubContext>();
var item = Context.Set<TEntity>().Where(predicate).AsNoTracking().ToList();
return item;
}
}
public IQueryable GetListQueryable<TContext>(Expression<Func<TEntity, bool>> predicate)
{
using (var scope = ScopeFactory.CreateScope())
{
var Context = scope.ServiceProvider.GetRequiredService<ubContext>();
var item = Context.Set<TEntity>().Where(predicate);
return item;
}
}
You can also do an insert, or update:
public virtual void Update(TEntity input, Expression<Func<TEntity, bool>> predicate)
{
using (var scope = ScopeFactory.CreateScope())
{
var Context = scope.ServiceProvider.GetRequiredService<ubContext>();
if (input == null)
return;
var existing = Context.Set<TEntity>().FirstOrDefault(predicate);
if (existing != null)
{
Context.Entry(existing).CurrentValues.SetValues(input);
Context.SaveChanges();
}
}
}
public virtual void Insert(TEntity input)
{
using var scope = ScopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ubContext>();
context.Set<TEntity>().Add(input);
context.SaveChanges();
}
Now, if you need to make something specific in terms of an overload, or some "wonky" handling, you create a class that inherits from this base class.
(But with the specific name of table as the TEntity)
And now you can employ polymorphism to change the behaviour or create new behavior however you like.
Also you may not be required to make a scoped approach to processing the requests, this is just "yoinked" out of an existing code base I already made that did have it as a requirement.
If this doesn't make sense, let me know, and I will give you more code, from the project.
public abstract class DBAccess
{
private readonly DbContext _db;
public DBAccess(DbContext db)
{
_db = db;
}
protected virtual IQueryable<T> Get<T>() where T : class
{
return _db.Set<T>().AsQueryable();
}
}
Then you can use it like this:
private ApplicationUser GetUser(int id)
{
return _dbAccess.Get<ApplicationUser>().Where(w => w.Id == id).FirstOrDefault();
}

Conflict with mocking service when seeding database for testing

I have a multi tenant web API where I seed a database with initial data.
I also have a transient IUserService which has a GetCustomerId function to retrieve the current customerId. This service is used in the databaseContext to store the CustomerId foreign key on the created domain entity "under the hood".
So when I seed the database I create a new scope and use a ICurrentUserInitializer to set the CustomerId in the IUserService for that scope, so the CustomerId is valid when the database context stores the entity.
This works just fine in development, but not for testing. Since I want to mock the IUserService when I test, this means that Moq overrides the GetCustomerId. But I only want to mock that service AFTER I've finished seeding the test database.
I've also tried not mocking the IUserService, and instead use a ICurrentUserInitializer for every test that runs, i.e. for every test, create a new scope, set the CustomerId with the ICurrentUserInitializer in that scope, and run the test in that scope, and then reset for the next test. This seems to work, but isn't as flexible when you want to run tests as different users and it doesn't seem as elegant, since I have to write more code to handle the scope correctly.
I Use xUnit, Moq, Respawn, and Microsoft.AspNetCore.Mvc.Testing
DbContext :
public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
{
int? customerId = CurrentUser.GetCustomerId();
HandleAuditingBeforeSaveChanges(customerId);
int result = await base.SaveChangesAsync(cancellationToken);
return result;
}
private void HandleAuditingBeforeSaveChanges(int? customerId)
{
foreach (var entry in ChangeTracker.Entries<IMustHaveTenant>().ToList())
{
entry.Entity.CustomerId = entry.State switch
{
EntityState.Added => customerId.Value,
_ => entry.Entity.CustomerId
};
}
}
DatabaseInitializer :
public async Task InitializeApplicationDbForCustomerAsync(Customer Customer, CancellationToken cancellationToken)
{
// First create a new scope
using var scope = _serviceProvider.CreateScope();
// This service injects a CustomerId, so that ICurrentUser retrieves this value, but
// doesn't work, since Moq overrides the value
scope.ServiceProvider.GetRequiredService<ICurrentUserInitializer>()
.SetCurrentCustomerId(customer.Id);
// Then run the initialization in the new scope
await scope.ServiceProvider.GetRequiredService<ApplicationDbSeeder>()
.SeedDatabaseAsync(_dbContext, cancellationToken);
}
CustomWebApplicationFactory:
public class CustomWebApplicationFactory : WebApplicationFactory<Program>
{
protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.ConfigureAppConfiguration(configurationBuilder =>
{
var integrationConfig = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.AddEnvironmentVariables()
.Build();
configurationBuilder.AddConfiguration(integrationConfig);
});
builder.ConfigureServices((context, services) =>
{
services
.Remove<DbContextOptions<ApplicationDbContext>>()
.AddDbContext<ApplicationDbContext>((sp, options) =>
{
options.UseSqlServer(context.Configuration.GetConnectionString("DefaultConnection"),
builder => builder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName));
});
});
builder.ConfigureTestServices(services =>
{
services
.Remove<ICurrentUser>()
.AddTransient(_ => Mock.Of<ICurrentUser>(s =>
s.GetCustomerId() == GetCurrentCustomerId()));
});
}
}
Testing / CollectionFixture :
public class DatabaseCollection : ICollectionFixture<Testing>
{
}
public partial class Testing : IAsyncLifetime
{
private static WebApplicationFactory<Program> _factory = null!;
private static IConfiguration _configuration = null!;
private static IServiceScopeFactory _scopeFactory = null!;
private static Checkpoint _checkpoint = null!;
private static int? _currentCustomerId = null;
public Task InitializeAsync()
{
_factory = new CustomWebApplicationFactory();
_scopeFactory = _factory.Services.GetRequiredService<IServiceScopeFactory>();
_configuration = _factory.Services.GetRequiredService<IConfiguration>();
_checkpoint = new Checkpoint
{
TablesToIgnore = new[] { new Table("__EFMigrationsHistory") },
};
return Task.CompletedTask;
}
public static int? GetCurrentCustomerId()
{
return _currentCustomerId;
}
public static void RunAsDefaultUserAsync()
{
_currentCustomerId = DefaultValues.Customer.Id;
}
public static async Task<TResponse> SendAsync<TResponse>(IRequest<TResponse> request)
{
using var scope = _scopeFactory.CreateScope();
var mediator = scope.ServiceProvider.GetRequiredService<ISender>();
return await mediator.Send(request);
}
public static async Task<TEntity?> FindAsync<TEntity>(params object[] keyValues)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
return await context.FindAsync<TEntity>(keyValues);
}
public static async Task AddAsync<TEntity>(TEntity entity)
where TEntity : class
{
using var scope = _scopeFactory.CreateScope();
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
context.Add(entity);
await context.SaveChangesAsync();
}
public static async Task ResetState()
{
await _checkpoint.Reset(_configuration.GetConnectionString("DefaultConnection"));
await _factory.Services.InitializeDatabasesAsync();
_currentCustomerId = null;
}
public Task DisposeAsync()
{
return Task.CompletedTask;
}
}
DepartmentTest:
[Collection("Database collection")]
public class GetDepartmentsTest : BaseTestFixture
{
[Fact]
public async Task ShouldReturnDepartments()
{
RunAsDefaultUserAsync();
var query = new ListDepartmentRequest();
var result = await SendAsync(query);
result.ShouldNotBeNull();
}
[Fact]
public async Task ShouldReturnAllDepartments()
{
RunAsDefaultUserAsync();
await AddAsync(new Department
{
Description = "Department 1",
});
await AddAsync(new Department
{
Description = "Department 2",
});
var query = new ListDepartmentRequest();
var result = await SendAsync(query);
result.ShouldNotBeNull();
result.Count.ShouldBe(2);
}
}
BaseTestFixture:
public class BaseTestFixture : IAsyncLifetime
{
public async Task InitializeAsync()
{
await ResetState();
}
public async Task DisposeAsync()
{
await ResetState();
//return Task.CompletedTask;
}
}

Issues with DbContext getting disposed after multiple calls to service

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();
}
}

How to avoid not-safe context operations in EF Core? [duplicate]

This question already has answers here:
Entity Framework Core: A second operation started on this context before a previous operation completed
(20 answers)
Closed 4 years ago.
I'd want to know how why creating instances of other classes with current database context instances as a parameter and using that db context causes this exception to be raised
'A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
Imma use this sample code to show the problem
public class TestController : Controller
{
private readonly DbContext dbContext;
public Controller(DbContext ctx)
{
dbContext = ctx;
}
public async Task<IActionResult> Test(string id)
{
var isValid = new otherClass(dbContext).Validate(id);
if (!isValid)
{
return View("error");
}
var user = dbContext.Users.FirstOrDefault(x => x.Id == id);
user.Age++;
dbContext.SaveChanges(); // exception is being raised here. It is second .SaveChanges() here
return View();
}
}
public class otherClass
{
private readonly DbContext dbContext;
public otherClass(DbContext ctx)
{
dbContext = ctx;
}
public bool Validate(string id)
{
var user = dbContext.Users.FirstOrDefault(x => x.Id == id);
user.ValidationAttempt = DateTime.Now;
dbContext.SaveChanges();
return user.HasConfirmedEmail;
}
}
Generally in an MVC fashion youre going to want a DbContext on a per request basis but when using threading more control through using blocks can be beneficial, an easy way to set that up would be something along the lines of
public class TestController : Controller
{
private readonly Func<DbContext> dbContext;
public Controller(Func<DbContext> ctx)
{
dbContext = ctx;
}
public async Task<IActionResult> Test(string id)
{
using(var cntx = dbContext())
{
var isValid = new otherClass(cntx).Validate(id);
if (!isValid)
{
return View("error");
}
var user = cntx.Users.FirstOrDefault(x => x.Id == id);
user.Age++;
cntx.SaveChanges();
return View();
}
}
}
that essentially resolves a new DbContext per using block - and since each thread is then handling its own DbContext - shouldnt have any issues

Why is the unit of work repository i created causing a System.InvalidOperationException when inserting a new item

After writing many repositories and interfaces for my data access i understood that ive been rewriting many codes over and over so i sought to understand the generic repository pattern and the unit of work. I followed the tutorial here.
After implementing the example and incorporating the needed part to my project. I faced the problem of
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
From my research i was made to understand that i could be using two database contexts hence the error. I have this GenericRepository class and the Unit of work class where i register all my repository below
public class GenericRepository<TEntity> where TEntity : class
{
internal ApplicationDbContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(ApplicationDbContext 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);
}
public virtual void Insert(TEntity entity)
{
if (dbSet != null) 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;
}
}
Here is the unit of work class
public class UnitOfWork : IDisposable
{
private ApplicationDbContext context = new ApplicationDbContext();
private GenericRepository<Transactions> transactionRepository;
private GenericRepository<PendingReason> penReasonRepository;
private GenericRepository<DeclineReason> decReasonRepository;
private GenericRepository<LoanStatus> loanStatusRepository;
private GenericRepository<SalesAgent> salesAgentRepository;
public GenericRepository<Transactions> TransactionRepository
{
get
{
if (this.transactionRepository == null)
{
this.transactionRepository = new GenericRepository<Transactions>(context);
}
return transactionRepository;
}
}
public GenericRepository<PendingReason> PenReasonRepository
{
get
{
if (this.penReasonRepository == null)
{
this.penReasonRepository = new GenericRepository<PendingReason>(context);
}
return penReasonRepository ;
}
}
public GenericRepository<DeclineReason> DecReasonRepository
{
get
{
if (this.decReasonRepository == null)
{
this.decReasonRepository = new GenericRepository<DeclineReason>(context);
}
return decReasonRepository;
}
}
public GenericRepository<LoanStatus> LoanStatusRepository
{
get
{
if (this.loanStatusRepository == null)
{
this.loanStatusRepository = new GenericRepository<LoanStatus>(context);
}
return loanStatusRepository;
}
}
public GenericRepository<SalesAgent> SalesAgentRepository
{
get
{
if (this.salesAgentRepository == null)
{
this.salesAgentRepository = new GenericRepository<SalesAgent>(context);
}
return salesAgentRepository;
}
}
public void Save()
{
context.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
In my the create action of my controller i have a point where i use the usermanager object to get the id of the current user. In that point there is a reference to the ApplicationDbContext which i think is causing the problem. However i may be wrong. Below is my controller action
[HttpPost]
public ActionResult Create(Transactions transactions)
{
using (var unit = new UnitOfWork())
{
if (ModelState.IsValid)
{
MapTransactions(new CreateTrnVM(), transactions);
unit.TransactionRepository.Insert(transactions);
unit.Save();
return RedirectToAction("Index");
}
ViewBag.DeclineReasonId = new SelectList(unit.DecReasonRepository.Get(), "DeclineReasonId", "DecReason");
ViewBag.PendingReasonsId = new SelectList(unit.PenReasonRepository.Get(), "PendingReasonId", "PenReason");
ViewBag.StatusId = new SelectList(unit.LoanStatusRepository.Get(), "StatusId", "Status");
return View(new CreateTrnVM());
}
This is the MapTransactions Method.
public void MapTransactions(CreateTrnVM model, Transactions source)
{
source.TrnDate = DateTime.Now;
ApplicationUser currentUser;
using (var manager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(new ApplicationDbContext())))
{
currentUser = manager.FindById(User.Identity.GetUserId());
}
source.Agent = currentUser.SalesAgent;
}
When trying to create a Transaction, This error keeps coming out
System.InvalidOperationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
A further research led me to this statement on this link.
In a real application you’ll have to decide if you want to mingle your
data context with IdentityDbContext. One issue to be aware of is that
the UserStore class does not play well when using the unit of work
design pattern. Specifically, the UserStore invokes SaveChanges in
nearly every method call by default, which makes it easy to
prematurely commit a unit of work. To change this behavior, change the
AutoSaveChanges flag on the UserStore.
var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
store.AutoSaveChanges = false;
This still didnt work. or maybe something else is the problem.
From various posts and research i finally got to know that the problem was that i was using a separate instance of the ApplicationDbContext as it was clashing with the one instantiated in the Unit of work class. This occured in the point where i was trying to get the User id of the current logged in user by using the UserManager class. A little research showed me this from K Scot Allen blog
In a real application you’ll have to decide if you want to mingle your
data context with IdentityDbContext. One issue to be aware of is that
the UserStore class does not play well when using the unit of work
design pattern. Specifically, the UserStore invokes SaveChanges in
nearly every method call by default, which makes it easy to
prematurely commit a unit of work. To change this behavior, change the
AutoSaveChanges flag on the UserStore.
var store = new UserStore<ApplicationUser>(new ApplicationDbContext());
store.AutoSaveChanges = false;
The suggestion above didnt help me but it drove me to find out more about the problem and i got to this stackoverflow link which suggested that i create the UserManager class in the Unit of work class. Modifying my code above to include
private UserManager<ApplicationUser> _userManager;
public UserManager<ApplicationUser> UserManager
{
get
{
if (this._userManager == null)
{
this._userManager = new UserManager<ApplicationUser>(new UserStore<ApplicationUser>(context));
}
return _userManager;
}
}
I was able to get access to the UserManager class in the controller without using the ApplicationDbContext directly. I modified my controller like this
public ActionResult Create(Transactions transactions)
{
using (var unit = new UnitOfWork())
{
if (ModelState.IsValid)
{
transactions.TrnDate = DateTime.Now;
var manager = unit.UserManager;
var currentUser = manager.FindById(User.Identity.GetUserId());
transactions.Agent = currentUser.SalesAgent;
unit.TransactionRepository.Insert(transactions);
unit.Save();
return RedirectToAction("Index");
}
ViewBag.StatusId = new SelectList(unit.LoanStatusRepository.Get(), "StatusId", "Status");
return View(new CreateTrnVM());
}
Take note of this three lines
var manager = unit.UserManager;
var currentUser = manager.FindById(User.Identity.GetUserId());
transactions.Agent = currentUser.SalesAgent;
I hope it helps other people.
The problem is on this line source.Agent = currentUser.SalesAgent;
You're setting an entity SalesAgent that was retrieved by one instance of ApplicationDbContext that is different from the instance that was used to get the source.

Categories

Resources