EF Core Savechanges not working for Remove - c#

I am trying to remove child item(s) from my domain, but Savechanges() is not working and no exceptions occurred and I am tracing and find my entity in Dbcontext that state changed to modified. add or update working correctly. everything worked ok before I add IEventbus interface to publish an event. my remove method in the domain class.
public void RemoveItem(Guid id, IEventBus eventBus)
{
if (!string.IsNullOrWhiteSpace(this.Confirmer.UserId))
{
throw new RemoveItemOfConfirmedScrapException();
}
var scrapItem = this.ScrapItems.First(p => p.Id == id);
var assetId = scrapItem.AssetId;
this.ScrapItems.Remove(this.ScrapItems.First(p => p.Id == id));
eventBus.Publish(new ScrapItemRemovedEvent(assetId));
}
and this is my UnitOfWork for saving in the database.
public class UnitOfWork : IUnitWork
{
private readonly IDbContext dbContext;
private DbContextBase dbContextBase;
public UnitOfWork(IDbContext dbContext)
{
this.dbContext = dbContext;
this.dbContextBase = dbContext as DbContextBase;
}
public void Commit()
{
try
{
this.dbContext.SaveChanges();
}
catch
{
RollBack();
throw;
}
}
public void RollBack()
{
this.dbContextBase.ChangeTracker.Entries()
.Where(e => e.Entity != null).ToList()
.ForEach(e => e.State = EntityState.Detached);
}
}
what happens to that delete works in other domains even in parent entity but in this case not delete in database. and ChangeTracker shows that specific entity state changed to "modified" but without any exception finish job and not effected on the database.
I have an application service layer with decoration design pattern to call UnitOfWork
public void Dispatch<TCommand>(TCommand command)
where TCommand : Command
{
var commandHandler = this.diContainer.Resolve<ICommandHandler<TCommand>>();
var transactionCommandHandler = new TransactionalCommandHandler<TCommand>(commandHandler, this.diContainer);
var logCommandHandler = new LogCommandHandler<TCommand>(transactionCommandHandler);
logCommandHandler.Execute(command);
}
and transactional class that calls UnitOfWork
public class TransactionalCommandHandler<TCommand> : ICommandHandler<TCommand>
where TCommand : Command
{
private readonly ICommandHandler<TCommand> commandHandler;
private readonly IDiContainer container;
public TransactionalCommandHandler(ICommandHandler<TCommand> commandHandler, IDiContainer container)
{
this.commandHandler = commandHandler;
this.container = container;
}
public void Execute(TCommand command)
{
var unitOfWork = this.container.Resolve<IUnitWork>(); // ServiceLocator.Current.Resolve<IUnitWork>();
try
{
this.commandHandler.Execute(command);
unitOfWork.Commit();
}
catch (Exception e)
{
unitOfWork.RollBack();
throw;
}
}
}

var scrapItem = this.ScrapItems.First(p => p.Id == id);
var assetId = scrapItem.AssetId;
this.ScrapItems.Remove(scrapItem);
this.Commit(); // you weren't committing before
Removing entity from collection only marks it's state as Deleted.
You need to call SaveChanges method in order to reflect the changes to database.

A very simple carelessness had caused this problem.
in my command facade have transaction scope that forgot put "scope.complete()"
public void DeleteScrapItem(DeleteScrapItemCommand command)
{
using (var scope = new TransactionScope())
{
Action<dynamic> updateAssetStatus = p => this.CommandBus.Dispatch(new UpdateAssetStatusCommand()
{
AssetId = p.AssetId,
Status = 0
});
this.eventBus.Subscribe<ScrapItemRemovedEvent>(updateAssetStatus);
this.CommandBus.Dispatch(command);
scope.Complete(); // forgot put this ...
}
}

Related

ObjectDisposedException trying implement DDD project

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.

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 fix 'A second operation started on this context before a previous operation completed...' when working with dependency injection?

when reading data from the database I get this error:
A second operation started on this context before a previous operation
completed. Any instance members are not guaranteed to be thread safe.
I have the following ApplicationContext.cs:
public class ApplicationContext : Microsoft.EntityFrameworkCore.DbContext
{
public ApplicationContext(DbContextOptions<ApplicationContext> options)
: base(options)
{ }
public DbSet<MyClass> MyClasses{ get; set; }
}
The following ApplicationContextFactory.cs
public class ApplicationContextFactory : IDesignTimeDbContextFactory<ApplicationContext>
{
public ApplicationContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<ApplicationContext>();
var connection = "myConnectionString";
builder.UseSqlServer(connection);
return new ApplicationContext(builder.Options);
}
}
The following ServiceLoader.cs (where I declare the DI):
public static class ServiceLoader
{
public static void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IRepository, Repository>();
var connection = "myConnectionString";
services.AddDbContext<ApplicationContext>(options => options.UseSqlServer(connection));
}
}
and finally, the following Repository, where the exception is thrown:
public class Repository : IRepository
{
private ApplicationContext _db;
public Repository (ApplicationContext db)
{
_db = db;
}
public List<MyClass> Get()
{
_db.MyClasses.ToList();
}
}
I have also tried to declare the Repository as Transient instead of Singleton, but a similar error is thrown
'An attempt was made to use the context while it is being configured. A DbContext instance cannot be used inside OnConfiguring since it is still being configured at this point. This can happen if a second operation is started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.'
Any idea on how to fix this? Thanks!
In my case I found the following information helpful:
https://learn.microsoft.com/en-us/ef/core/miscellaneous/configuring-dbcontext
And changed the lifetime scope of my Db Context to transient using the overloaded AddDbContext method in startup:
services.AddDbContext<MyAppDbContext>(options => {
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection"));
}, ServiceLifetime.Transient);
You can wrap an async Task around your Get() function and await your results:
public async Task<List<MyClass>> Get()
{
return await _db.MyClasses.ToListAsync();
}
I wrote a solution, which uses a queue. It is still single threaded, but you can call it from different threads.
public class ThreadSafeDataContext
{
private Thread databaseThread;
private Queue<PendingQuery> pendingQueries = new Queue<PendingQuery>();
private DatabaseContext db = new DatabaseContext();
private bool running = true;
public ThreadSafeDataContext()
{
databaseThread = new Thread(new ThreadStart(DoWork));
databaseThread.Start();
}
public void StopService()
{
running = false;
}
private void DoWork()
{
while(running)
{
if (pendingQueries.Count > 0)
{
// Get and run query
PendingQuery query = pendingQueries.Dequeue();
query.result = query.action(db);
query.isFinished = true;
}
else
{
Thread.Sleep(1); // Waiting for queries
}
}
}
public T1 Query<T1>(Func<DatabaseContext, T1> action)
{
Func<DatabaseContext, object> a = (DatabaseContext db) => action(db);
PendingQuery query = new PendingQuery(a);
pendingQueries.Enqueue(query);
while (!query.isFinished) {
Thread.Sleep(1); // Wait until query is finished
}
return (T1)query.result;
}
}
class PendingQuery
{
public Func<DatabaseContext, object> action;
public bool isFinished;
public object result;
public PendingQuery(Func<DatabaseContext, object> action)
{
this.action = action;
}
}
Then you can just run a query from different threads by using:
TeamMembers teamMembers = threadSafeDb.Query((DatabaseContext c) => c.team.ToArray())

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.

Cordinate between my repository classes and controller classes to be using the same Context object

I am working on an asp.net mvc web application. now i have created multiple repositories classes, for example i have the following abstract repository classes:-
public interface ISkillRepository : IDisposable
{//code goes here..
&
public interface IStaffRepository : IDisposable
{//code goes here
and the model Repositories:-
public class SkillRepository : ISkillRepository , IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
//code goes here
&
public class StaffRepository : IStaffRepository , IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
now inside y controller i am intializing and creating the repo as follow:-
public class SkillController : Controller
{
private ISkillRepository skillRepository;
public SkillController() : this(new SkillRepository()) {}
public SkillController(ISkillRepository repository)
{
skillRepository = repository;
}
but currently i got the following error inside my application:
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
and the problem is that i need to be passing the same context accross the repos and controllers. so can anyone adivce on this:-
how i can inside one model repo to reference another repo using the same context class. for example inside the Staff repositoryto referecne the skill repository?
how i can inside a controller class to refer multiple repos , but at the same time pass the same context object among them , so if i issue a save() it will wrap all the statements inside one transaction. for example insie my skillController to reference both the skill & staff repos using the same context object ?
Thanks
Edit
I have created the following Unit of work class:-
public class UnitOfWork : IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
private SkillRepository skillRepository;
private StaffRepository staffRepository;
private SecurityRoleRepository securityroleRepository;
public SkillRepository SkillRepository
{
get
{
if (this.skillRepository == null)
{
this.skillRepository = new SkillRepository(context);
}
return skillRepository;
}
}
public StaffRepository StaffRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new StaffRepository(context);
}
return staffRepository;
}
}
public SecurityRoleRepository SecurityRoleRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new SecurityRoleRepository(context);
}
return securityroleRepository;
}
}
public async Task Save()
{
await context.SaveChangesAsync();
}
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);
}
}
}
and then inside my repo i did the following:-
public class SecurityRoleRepository : ISecurityRoleRepository , IDisposable
{
private SkillManagementEntities context;// = new SkillManagementEntities();
public SecurityRoleRepository(SkillManagementEntities context)
{
this.context = context;
and on the controller class i will be referencing the UnitOfWork as follow:-
public class SecurityRoleController : Controller
{
private UnitOfWork unitOfWork = new UnitOfWork();
public async Task<ActionResult> Index(string filter = null, int page = 1, int pageSize = 20, string sort = "Name", string sortdir = "ASC")
{
try
{
var records = new PagedList<SecurityRole>();
ViewBag.filter = filter;
records.Content = await unitOfWork.SecurityRoleRepository.GetSecurityRoleForGrid(filter, page, pageSize, sort, sortdir).ToListAsync();
now i am facing a problem is that how i can referecne a repo from another Repo ? for example how i can reference the Skill repo inside the SecurityRole repo ?
EDIT Final
i did the following steps:-
1. i install
Install-Package Ninject.MVC5
2. then i created the following dependency class:-
public class YourDependencyResolverClass : IDependencyResolver
{
private IKernel kernel;
public YourDependencyResolverClass()
{
kernel = new StandardKernel();
AddBindings();
}
public object GetService(Type serviceType)
{
return kernel.TryGet(serviceType);
}
public IEnumerable<object> GetServices(Type serviceType)
{
return kernel.GetAll(serviceType);
}
private void AddBindings()
{
kernel.Bind<ISkillRepository>().To<SkillRepository>();
kernel.Bind<IStaffRepository>().To<StaffRepository>();
kernel.Bind<ISecurityRoleRepository>().To<SecurityRoleRepository>();
kernel.Bind<ICustomerRepository>().To<CustomerRepository>();
kernel.Bind<ISkillVersionHistoryRepository>().To<SkillVersionHistoryRepository>();
}
}
}
3.now inside my SkillRepository class i will be referencing the StaffRepository as follow:-
public class SkillRepository : ISkillRepository , IDisposable
{
private SkillManagementEntities context ;
private IStaffRepository staffrepo = (IStaffRepository)DependencyResolver.Current.GetService(typeof(IStaffRepository));
public SkillRepository(SkillManagementEntities context)
{
this.context = context;
}
Finally inside my action method i will be calling the Uiteofwork class as follow:-
public class StaffController : Controller
{
//private SkillManagementEntities db = new SkillManagementEntities();
UnitOfWork unitofwork = new UnitOfWork();
public async Task<ActionResult> AutoComplete(string term)
{
var staff = await unitofwork.StaffRepository.GetAllActiveStaff(term).Select(a => new { label = a.SamAccUserName }).ToListAsync();
return Json(staff, JsonRequestBehavior.AllowGet);
and the unite of work class is :-
public class UnitOfWork : IDisposable
{
private SkillManagementEntities context = new SkillManagementEntities();
private SkillRepository skillRepository;
private StaffRepository staffRepository;
private SecurityRoleRepository securityroleRepository;
private CustomerRepository customerRepository;
private SkillVersionHistoryRepository SVH;
public SkillRepository SkillRepository
{
get
{
if (this.skillRepository == null)
{
this.skillRepository = new SkillRepository(context);
}
return skillRepository;
}
}
public StaffRepository StaffRepository
{
get
{
if (this.staffRepository == null)
{
this.staffRepository = new StaffRepository(context);
}
return staffRepository;
}
}
public CustomerRepository CustomerRepository
{
get
{
if (this.customerRepository == null)
{
this.customerRepository = new CustomerRepository(context);
}
return customerRepository;
}
}
public SecurityRoleRepository SecurityRoleRepository
{
get
{
if (this.securityroleRepository == null)
{
this.securityroleRepository = new SecurityRoleRepository(context);
}
return securityroleRepository;
}
}
public SkillVersionHistoryRepository SkillVersionHistoryRepository
{
get
{
if (this.SVH == null)
{
this.SVH = new SkillVersionHistoryRepository(context);
}
return SVH;
}
}
public async Task Save()
{
await context.SaveChangesAsync();
}
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);
}
}
So can you adivce if my approach of using unitefwork and DI will guarantee that all my statements will be warped inside a single DB transaction ? thnaks?
We handle this by sharing a context using a singleton that is scoped to the request using HttpContext:
public MyContext GetContext()
{
if (System.Web.HttpContext.Current.Items["MyScopedContext"] == null)
{
System.Web.HttpContext.Current.Items["MyScopedContext"] = new MyContext();
}
return (MyContext)System.Web.HttpContext.Current.Items["MyScopedContext"];
}
The context object (repository) itself essentially houses a Unit of Work. The code I added above just gives you a way to share a single repository across all code running within a request. If your repository classes are defined in the scope of a web application, you can just replace your direct instantiation of SkillManagementEntities() with a call to a GetContext() method.
On the other hand if your repositories are defined in a layer shared by heterogeneous applications, you may need to get your context from a factory object that you can inject as needed. Either way, creating a new context object per repository is what's causing your issue.
Not an answer: this "use DI" suggestion answers a bit different question - OP is looking for "unit-of-work" pattern - while basic case (lifetime of unit of work matches lifetime of request/controller) can easily be solved with any DI framework, managing multiple units of work or units of work with longer lifetime is much harder and dedicated "unit of work factory" (sample usage) is likely the solution.
Usually when you go that far with interfaces/repositories and constructor dependency injection you have some Dependency Injection framework. There is a good chance that one you are using already provides "per HTTP request" resolution or allows to easily add one.
I.e. if you using Unity there is PerRequestLifetime lifetime manager that makes all .Resolve calls for the same interface/object to return the same instance for given request. See more info in DI with Unity MSDN article.
Approximate sample:
container.RegisterType<ISkillRepository, SkillRepository>();
container.RegisterType<IOtherRepository, OtherRepository>();
container.RegisterType<TheContext, TheContext>(new PerRequestLifetime());
With such registration and assuming you've configured ASP.Net MVC to use Unity to resolve types when controller is created it will get necessary dependencies (new instances as registered with default lifetime), but both will share the same context (assuming each depends on TheContext class either via constructor or property injection).

Categories

Resources