locked
Problem with batch updating for master-details entities. RRS feed

  • Question

  • User-79977429 posted

    Hi

    I have a master-detail view with 2 tables :

    DefaultVisitProductHeaders -> Master table
    DefaultVisitProductDetails -> Detail table

    In my view, i've using ajax request to give end-user to add/update/remove details, then save them in tempData (not in _dbContext). Here is my master-detail view (for edit mode):

    @model DefaultVisitProductHeaders
    
    <h4>Edit Drugs Package</h4>
    <hr />
    
    <div class="row">
        <div class="col-md-6">        
            <form id="frmEditPackageHeaderItem" asp-action="Update" method="post">
                <div asp-validation-summary="All" class="text-danger"></div>
                <input type="hidden" id="hdnDefaultVisitProductHeaderRowID" asp-for="DefaultVisitProductHeaderRowID" value="@Model.DefaultVisitProductHeaderRowID" />
                <div class="form-group">
                    <label asp-for="DefaultVisitProductHeaderName" class="control-label"></label>
                    <input asp-for="DefaultVisitProductHeaderName" class="form-control" value="@Model.DefaultVisitProductHeaderName" />
                    <span asp-validation-for="DefaultVisitProductHeaderName" class="text-danger"></span>
                </div>
    
                <br />
                <h4>Package Details List</h4>
                <hr />
    
                <div id="divPackageDetails" class="container">
                    @Html.RenderAction("GetPackageDetails", "Package", new { id = Model.DefaultVisitProductHeaderRowID })
                </div>
    
                <div class="text-right">
                    <button type="submit" id="btnUpdate" class="btn btn-success">Save</button>
                    @Html.ActionLink("Back", "Index", "Package")
                </div>
            </form>
        </div>
    </div>
    
    <div id="divPopup"></div>
    
    @section Scripts{
        <script src="~/js/jquery.validate.min.js"></script>
        <script src="~/js/jquery.validate.unobtrusive.min.js"></script>
    }

    My problem is that when, click on save button to update database, i'm facing this error (for added entities) :

    InvalidOperationException: The instance of entity type 'DefaultVisitProductHeaders' cannot be tracked because another instance with the same key value for {'DefaultVisitProductHeaderRowID'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
    Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap<TKey>.ThrowIdentityConflict(InternalEntityEntry entry)

    Here is my update action :

    [HttpPost]
            [ValidateAntiForgeryToken]
            public IActionResult Update([Bind("DefaultVisitProductHeaderRowID, DefaultVisitProductHeaderName")] DefaultVisitProductHeaders packageHeader)
            {
                if (this.ModelState.IsValid)
                {
                    var curPackageHeader = _dbContext.DefaultVisitProductHeaders.Find(packageHeader.DefaultVisitProductHeaderRowID);
                    curPackageHeader.DefaultVisitProductHeaderName = packageHeader.DefaultVisitProductHeaderName;
                    this.AttachPackageDetailsToDbContext(curPackageHeader);
                    _dbContext.SaveChanges();
    
                    return RedirectToAction("Index", "Package");
                }
    
                return View("Edit", packageHeader);
            }

    And here is my helper methods (AttachPackageDetailsToDbContext, GetPackageDetailsList) :

    private void AttachPackageDetailsToDbContext(DefaultVisitProductHeaders packageHeader)
            {
                List<DefaultVisitProductDetails> lstPackageDetails = this.GetPackageDetailsList();
    
                List<DefaultVisitProductDetails> lstItemsAdded = lstPackageDetails.Where(d => d.IranHealthEntityState == IranHealthEntityState.Added).ToList();
                List<DefaultVisitProductDetails> lstItemsModified = lstPackageDetails.Where(d => d.IranHealthEntityState == IranHealthEntityState.Modified).ToList();
                List<DefaultVisitProductDetails> lstItemsDeleted = lstPackageDetails.Where(d => d.IranHealthEntityState == IranHealthEntityState.Deleted).ToList();
                
                foreach (DefaultVisitProductDetails detailItem in lstItemsAdded)
                {
                    _dbContext.DefaultVisitProductDetails.Add(detailItem); // Cause error!!
                }
    
                foreach (DefaultVisitProductDetails detailItem in lstItemsModified)
                    _dbContext.Entry(detailItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
    
                foreach (DefaultVisitProductDetails detailItem in lstItemsDeleted)
                    _dbContext.Entry(detailItem).State = Microsoft.EntityFrameworkCore.EntityState.Deleted;
            }
    
    
    private List<DefaultVisitProductDetails> GetPackageDetailsList()
            {
                List<DefaultVisitProductDetails> lstResult = null;
                
                #region Getting lstPackageDetails from tempData, if does not exists, generate new list!
    
                var tmp = TempData.Peek("_lstPackageDetails");
                if (tmp != null)
                {
                    lstResult = JsonConvert.DeserializeObject<List<DefaultVisitProductDetails>>(tmp.ToString(), new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
                }
    
                if (lstResult == null)
                {
                    lstResult = new List<DefaultVisitProductDetails>();
                }
    
                #endregion
    
                return lstResult;
            }

    Where is my problem & how to solve it?
    Thanks in advance.

    Thursday, May 28, 2020 10:02 AM

All replies

  • User1120430333 posted

    Your code in using EF is in a disconnected state at the time you're trying to do a SaveChanges(), change tracking of the EF entities does not apply, EF doesn't know what entities are dirty due an entity being changed,  change tracking doesn't know the state of the entity so that it can be persisted to the database and you have to set the state of the EF entity or entities before you do the Savechange(), which happens in a Web program becuase a Web program runs in a stateless environment of a Web server..

    https://www.c-sharpcorner.com/UploadFile/d87001/connected-and-disconnected-scenario-in-entity-framework/

    https://whatis.techtarget.com/definition/stateless-app

    It  looks to me that you missed the Added state if everything is supposed to added.

     

    Thursday, May 28, 2020 12:46 PM
  • User-79977429 posted

    Thanks for reply

    i've changed my code to attach my detailObject along with changing state to Added as follow :

    private void AttachPackageDetailsToDbContext(DefaultVisitProductHeaders packageHeader)
    {
    	 ...
    	foreach (DefaultVisitProductDetails detailItem in lstItemsAdded)
            {                
                    _dbContext.DefaultVisitProductDetails.Attach(detailItem).State = EntityState.Added; // Cause error
            }
    	...
    }

    But i'm facing the same error in the given line!

    It was strange to me, i've add detailObject (DefaultVisitProductDetails) to dbContext, but the error message is related to masterObject (DefaultVisitProductHeaders)!

    Another item that is strange to me is that, when i modified a detailObject, this code works & the given detailObject add to changeTracker.entries correctly as just single object :

    foreach (DefaultVisitProductDetails detailItem in lstItemsModified)
                    _dbContext.Entry(detailItem).State = Microsoft.EntityFrameworkCore.EntityState.Modified;

    But when i add new detailObject to my dbContext, multiple details added to changeTracker.entries (I think all DefaultVisitProductDetails rows which exists in database added to my dbContext)!

    Why this happen?

    Thursday, May 28, 2020 1:50 PM
  • User1120430333 posted

    I think part of your problem is that you are doing data persistence in the presentation project instead of calling out to a WebAPI for CRUD operations and implementing some kind of SoC where EF is always in a disconnected state and change tracking cannot possibly be involved. 

    ASP.NET MVC allows the developer to easily violate MVC and SoC principles. 

    https://www.c-sharpcorner.com/UploadFile/56fb14/understanding-separation-of-concern-and-Asp-Net-mvc/

    Maybe you can be helped by MSDN support at MSDN EF forums that is still active.

    Thursday, May 28, 2020 3:28 PM
  • User-79977429 posted

    Any help ?

    Thursday, May 28, 2020 3:46 PM
  • User711641945 posted

    Hi hamed_1983,

    From this thread,I test the code,but do not make error in your line.What operation did you do?Could you share more details?

    Best Regards,

    Rena

    Friday, May 29, 2020 9:41 AM