locked
entity object cannot be referenced by multiple instances of IEntityChangeTracker. RRS feed

  • Question

  • User799396372 posted

    Hello 

    the Adding/updating of expense entity occurs in 2 steps

    when I have tried to Add the expense entity, i got this exception 

    this my controller code:

    public ActionResult _CreateUpdateDepense(int id, int? invoiceId)
    {
                Expense expense = null;
                Invoice invoice = uniteOfWorkBll.DraBLL.GetById(invoiceId.Value);
                
                //rest of code 
                return PartialView(depense);
     
             
     }
    

    Step 1 check if warningMessagesExist is true: In this case, the post method return a confirmation partial view else the expense entity is Added or updated

    [HttpPost]
     [ValidateAntiForgeryToken]
    public ActionResult _CreateUpdateDepense(DepenseDRA depense, string sortBy, string match, int? page, int pageSize = 5)
      {        
    	
    	var warningMessages = this.WarningDepense;
            bool warningMessagesExist=uniteOfWorkBll.ExpenseBLL.ExistWarningDepense(expense.InvoiceId, expense.Debt, expense.Facture);
                ViewBag.WarningMessagesExist = warningMessagesExist;
                expense.Invoice = uniteOfWorkBll.InvoiceBLL.GetById(expense.InvoiceId);
                try
                {
                    if (ModelState.IsValid)
                    {
                        if (warningMessagesExist)
                        {
                            this.WarningExpense = warningMessages;
                            //stocker expense object in session
                            TempoDepense = depense;                        
                            return PartialView("_CreateUpdateDepenseConfirm");
                        }
                        else
                        {
                            if (expense.Id <= 0)
                                uniteOfWorkBll.ExpenseBLL.Inserer(expense);
     	
                            else
                                uniteOfWorkBll.ExpenseBLL.Modifier(expense);
     
     
                            var model = GetPagedListInitialzed(depense.DRAId, match, sortBy, page, 5);
                            return PartialView("_ListExpensesByInvoice", model);
                        }
                        
                       
                    }
                    else
                    {
                        ViewBag.WarningMessagesExist = false;
                        //
                                            
                        return PartialView(depense);
     
     
                    }
                }
     
                catch (BusinessLayerException e)
                {                
                   //the rest of code
                }
            }
    

    Step 2 the submit of confirmation form view: if the button of type submit that’s its name “btnConfirm” the expense entity is added or updated.

    [HttpPost]
            [ValidateAntiForgeryToken]
            public ActionResult _CreateUpdateExepenseAfterConfirm(Expense expense, string btnConfirm, string sortBy, string match, int? page, int pageSize = 5)
            {
                        var warningMessages = this.WarningDepense;
                       //bool warningMessagesExist=true;
                        
                        //depense.DRA = uniteOfWorkBll.DraBLL.GetById(depense.DRAId); 
                        ViewBag.InvoiceId = depense. InvoiceId ;
                        
                        try
                        {
                            if (btnConfirm.Equals("Continuer"))
                            {
                                //recuperer  l'objet de depense stocké dans la session de depense de formulaire de cofirmation
                                expense = TempoExpense;
                                if (expense.Id <= 0)
                                	uniteOfWorkBll.ExpenseBLL.Inserer(expense);
     	
                           	   else
                                	uniteOfWorkBll.ExpenseBLL.Modifier(expense);
                                         
                                var model = GetPagedListInitialzed(depense.DRAId, match, sortBy, page, 5);
                                return PartialView("_ListExpensesByInvoice ", model);        
                          }    
                                
                               return PartialView("_CreateUpdateExpense", expense);
                           
                        }
    //the rest of code

    in BLL project

    public class UniteOfWorkBLL
    {
            UniteOfWork unitOfWork = new UniteOfWork();
            private InvoiceBLL _invoiceBll;
            private ExpenseBLL _expenseBll;
    
    	public InvoiceBLL InvoiceBLL 
            {
                get
                {
                    return _invoiceBll ?? new InvoiceBLL(unitOfWork);
                }
            }
     
            public ExpenseBLL  ExpenseBLL 
            {
                get
                {
                    return _depenseBll ?? new ExpenseBLL(unitOfWork);
                }
            }
    
    }
    

    in DAL project

    public class UniteOfWork : IDisposable 
    {
            private InvoiceContext context = new InvoiceContext ();
           
            private IGenericRepository<Invoice> _invoiceRepository;
            private IGenericRepository<Expense> _expenseRepository;
    
    	public IGenericRepository<Invoice> InvoiceRepository
            {
                get
                {
                    return _invoiceRepository ?? new GeneriqueRepository<Invoice>(context);
                }
            }
     
            public IGenericRepository< Expense > ExpenseRepository
            {
                get
                {
                    return _expenseRepository ?? new GeneriqueRepository<Expense>(context);
                }
            }
    
    
    }
    

    Why and how i fix that ?

    Friday, May 29, 2020 6:45 PM

All replies

  • User475983607 posted

    The error means the code created two DbContext instances and you tried to save an entity that was not created by the DbContext.   Most likely a design bug in the Unit-of-Work design or how you are using the pattern.  Entity Framework is already a unit of work pattern so there's no good reason to write your own.  You really need to know what you're doing when you wrap a Unit-of-Work in another Unit-of-Work because it is very easy to paint yourself into a corner. 

    Friday, May 29, 2020 6:58 PM
  • User799396372 posted

    The error means the code created two DbContext instances and you tried to save an entity that was not created by the DbContext.

    OK, how to know that code created two DbContext instances?

    Friday, May 29, 2020 11:39 PM
  • User475983607 posted

    Beginner32

    OK, how to know that code created two DbContext instances?

    Instantiating two unit of works classes can also cause this error.  You'll need to debug your code using standard tooling like the Visual Studio debugger or just review the code.  You built this rather complex pattern so you should understand how it works and be able to troubleshoot.

    Saturday, May 30, 2020 10:22 AM
  • User799396372 posted

    So what is the solution please?Is there a best practice for that case ?

    i have implemented repository pattern and unit of work:

    in BLL project

    public class UniteOfWorkBLL
    {
            UniteOfWork unitOfWork = new UniteOfWork();
            private InvoiceBLL _invoiceBll;
            private ExpenseBLL _expenseBll;
    
    	public InvoiceBLL InvoiceBLL 
            {
                get
                {
                    return _invoiceBll ?? new InvoiceBLL(unitOfWork);
                }
            }
     
            public ExpenseBLL  ExpenseBLL 
            {
                get
                {
                    return _depenseBll ?? new ExpenseBLL(unitOfWork);
                }
            }
    
    }

    in DAL project

    public class UniteOfWork : IDisposable 
    {
            private InvoiceContext context = new InvoiceContext ();
           
            private IGenericRepository<Invoice> _invoiceRepository;
            private IGenericRepository<Expense> _expenseRepository;
    
    	public IGenericRepository<Invoice> InvoiceRepository
            {
                get
                {
                    return _invoiceRepository ?? new GeneriqueRepository<Invoice>(context);
                }
            }
     
            public IGenericRepository< Expense > ExpenseRepository
            {
                get
                {
                    return _expenseRepository ?? new GeneriqueRepository<Expense>(context);
                }
            }
    
    
    }

    Generic repository:

    public class GeneriqueRepository<T> :  IGenericRepository<T> where T : class //, IDisposable
        {
            private InvoiceContext _context;
            private DbSet<T> _dbSet;
    
            public GeneriqueRepository(InvoiceContext context)
            {
                this._context = context;
                this._dbSet = context.Set<T>();
                //this._dbSet.Create();
            }
    //the rest of code
    }

    Monday, June 1, 2020 5:29 PM
  • User475983607 posted

    So what is the solution please? i have implemented repository pattern and unit of work:

    The error is very very clear.  The code is trying to save an entity using a Dbcontext that did not create the entity.  The error indicates your design allows multiple DbContexts to get created.  If we look at your design, there is nothing that stops any of the classes from instantiated multiple DbContexts. The UnitOfWork class creates a new DbContext member every time it is instantiated and the GeneriqueRepository creates a completely separate DbContest type???  

    What you are doing is also referred to as an anti-pattern.  Once you get the unit-of-work functioning, the first thing you'll find is it falls apart if you need to join multiple table and have complex business logic.

    If you are still compelled to go down the anti-pattern path, then I recommend taking the time to learn how to implement the pattern correctly.  

    https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing-the-repository-and-unit-of-work-patterns-in-an-asp-net-mvc-application

    Monday, June 1, 2020 7:03 PM
  • User799396372 posted

    The error is very very clear.  The code is trying to save an entity using a Dbcontext that did not create the entity.  The error indicates your design allows multiple DbContexts to get created.  If we look at your design, there is nothing that stops any of the classes from instantiated multiple DbContexts. The UnitOfWork class creates a new DbContext member every time it is instantiated and the GeneriqueRepository creates a completely separate DbContest type???  

    Sorry i have edited my repository

    Tuesday, June 2, 2020 11:04 AM
  • User1120430333 posted

    Why and how i fix that ?

    Stop using the generic repositroy..

    https://programmingwithmosh.com/net/common-mistakes-with-the-repository-pattern/

    https://www.infoworld.com/article/3117713/design-patterns-that-i-often-avoid-repository-pattern.html

    There is nothing wrong with the repository pattern when used properly as a domain pattern and not a data persistence pattern. 

    https://martinfowler.com/eaaCatalog/repository.html

    Tuesday, June 2, 2020 6:14 PM