locked
Problem in JsonConvert.SerializeObject to serialize list of entities to TempData. RRS feed

  • Question

  • User-79977429 posted

    Hi

    I have a main problem which i created in thread (please read it before this thread). Anyway, After a lot of digging & debugging my code, I Think the problem is related to serializing lstPackageDetails to TempData via this method :

    private void SetPackageDetailsList(List<DefaultVisitProductDetails> lstPackageDetails)
    {
    	TempData["_lstPackageDetails"] = JsonConvert.SerializeObject(lstPackageDetails, Formatting.Indented, new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects });
    }

    I call above method in my Edit action to serialize my packageDetails & store in TempData, :

    public IActionResult GetPackageDetails(string id)
            {
                if (id == null)
                {
                    return NotFound();
                }
    
                Guid gidPackageHeaderRowID = Guid.Parse(id);
                List<DefaultVisitProductDetails> packageDetails = _dbContext.DefaultVisitProductDetails.Where(d => d.DefaultVisitProductHeaderRowID == gidPackageHeaderRowID).OrderBy(d => d.ProductID).Include(d => d.Product).ToList();            
                this.SetPackageDetailsList(packageDetails); // Call it!
    
                return PartialView("_PackageDetailsList", packageDetails);
            }

    The problem is that, after calling 'SetPackageDetailsList' method, all packageDetails which have my productIDs being loaded into dbContext!

    I think there is a bug in JsonConvert (or someThing else).

    Can anybody have idea to store my packageDetails list into TempData without JsonConvert (or solve above problems)?

    Thanks in advance

    Thursday, May 28, 2020 4:31 PM

All replies

  • User475983607 posted

    It's difficult to understand the design intention.  What is the purpose of serializing a type that was just deserialized from the client?  Then you send the type back to the client?  The design does not make logical sense IMHO. 

    Thursday, May 28, 2020 4:42 PM
  • User753101303 posted

    Hi,

    all packageDetails which have my productIDs being loaded into dbContext!

    Not sure to get what you mean. You are loading data and then serialize them. Ah a common problem when debugging is that looking at DefaultVisitProductDetails would trigger a request without any selection criteria which can give the impression that your code loads all data (while it is just because you triggered that behavior by using the debugger). Is this the problem you have?

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

    I have a view which contains master-detail data, Here is my tables (which i described in my another thread) :

    DefaultVisitProductHeaders -> Master table
    DefaultVisitProductDetails -> Detail table

    In my view, user can edit master & details data all together, Then save all to db in a single transaction.

    User can add/update/delete each detail row via ajaxrequest which makes changes to list of details row(s), i'm storing details list in TempData to access it via my Actions & Views. But (as far i found out) i have to serialize/deserialize details list which capable to store in TempData.

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

    Not sure to get what you mean. You are loading data and then serialize them. Ah a common problem when debugging is that looking at DefaultVisitProductDetails would trigger a request without any selection criteria which can give the impression that your code loads all data (while it is just because you triggered that behavior by using the debugger). Is this the problem you have?

    The behavior is the same, even out of debugging. i've checked it out in sql server profiler!

    Thursday, May 28, 2020 5:00 PM
  • User475983607 posted

    hamed_1983

    In my view, user can edit master & details data all together, Then save all to db in a single transaction.

    User can add/update/delete each detail row via ajaxrequest which makes changes to list of details row(s), i'm storing details list in TempData to access it via my Actions & Views. But (as far i found out) i have to serialize/deserialize details list which capable to store in TempData.

    Still, the design makes little sense.  My best guess is you are caching the data rather than simply committing the Update/Delete when the Update/Delete event happens on the client.   The actual problem is syncing the cached data with the database.

    Again, I would simply update the record when the user makes an update and delete a record when user does a delete.  Simplify the logic.

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

    Hi again!

    For the best clarification, i've created a sample project which demonstrate what's the my problem.

    Please download it from this link. Then, unzip & run db script to generate database with sample data. Then you can open the project & test it in edit mode.

    In PackageHeader edit mode, please add/update/delete some packageDetail item & then click Save to update whole data! if you add a new detail item, you facing the same error which i posted ago.

    Please test & fix my problem if you can.

    Thanks in advance

    Thursday, May 28, 2020 9:08 PM
  • User711641945 posted

    Hi hamed_1983,

    I download the project and test the code.Firstly,You need to add the following line.Then I could not get into the else statement when the given productID does not exists in the list because if I do not select a productID,it would makes error to say the filed should be required.So I could not get to the line:`_dbContext.DefaultVisitProductDetails.Add(detailItem);`.Maybe you need to share more details about what operation did you do.

    private void AddToDetailsList(DefaultVisitProductDetails detailItem)
    {
        Products product = null;
    
        List<DefaultVisitProductDetails> lstPackageDetails = this.GetPackageDetailsList();
        var foundDetailItem = lstPackageDetails.Find(p => p.ProductID == detailItem.ProductID);
        if (foundDetailItem != null)
        {
            #region The given productID already exists in the lstPackageDetails, So update list
    
            foundDetailItem.Quantity += detailItem.Quantity;
            foundDetailItem.InternalHelperEntityState = InternalHelperEntityState.Modified;
            #endregion
        }
        else
        {
            #region The given productID does not exists in the list, So add detailItem to the list!
    
            product = _dbContext.Products.Find(detailItem.ProductID);
    
            detailItem.DefaultVisitProductDetailRowID = Guid.NewGuid();
            detailItem.Product = product;
            detailItem.InternalHelperEntityState = InternalHelperEntityState.Added;
            lstPackageDetails.Add(detailItem);
    
            #endregion
        }

    Best Regards,

    Rena

    Friday, May 29, 2020 9:48 AM
  • User-79977429 posted

    Hi hamed_1983,

    I download the project and test the code.Firstly,You need to add the following line.Then I could not get into the else statement when the given productID does not exists in the list because if I do not select a productID,it would makes error to say the filed should be required.So I could not get to the line:`_dbContext.DefaultVisitProductDetails.Add(detailItem);`.Maybe you need to share more details about what operation did you do.

    Hi

    Please run the following items :

    1. Run project
    2. Select a packageHeader & go to edit
    3. Click New button & enter appropriate values (for example, select a productID which does not exists in detailsList) then Save it to close popup dialog (You see it added to details list)
    4. Click Save to Save whole data to database. If added new product in detailsList,, the following error display & saving changes failed :
    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)

    Friday, May 29, 2020 10:04 AM
  • User475983607 posted

    Why must the user click save twice to save a record?  What I find confusing is the UI adds a record to the list which indicates the save was successful but in reality the user must click save again.  Why?

    Same issue with delete.  The user clicks delete, then clicks Ok in response to a delete warning, the record is removed from the list, and the user must click the Save button to delete the record.  This is a very confusing UI design.  Secondly, it is overly complicated because you need to snyc the UI with the database.

    As recommended in my first post, simply save the record when the user clicks save in the modal.  The first Save.  That gives you the opportunity to process the record and return either success or failure.  On success update the list with the new record.  Otherwise; provide an error message.

    Friday, May 29, 2020 11:20 AM
  • User-79977429 posted

    Why must the user click save twice to save a record?  What I find confusing is the UI adds a record to the list which indicates the save was successful but in reality the user must click save again.  Why?

    The Save is just name! Indeed, The save button which click on popoUp dialog, submit packageDetail item into memory (not database). The Save button in Edit view just save to database. Then :

    1. Save button in popup just submit data to memory (TempData)
    2. Save button in Edit view, save whole data (packageHeader + packageDetails which stored in TempData) to database.
    Friday, May 29, 2020 11:41 AM
  • User475983607 posted

    The Save is just name! Indeed, The save button which click on popoUp dialog, submit packageDetail item into memory (not database). The Save button in Edit view just save to database. Then :

    1. Save button in popup just submit data to memory (TempData)
    2. Save button in Edit view, save whole data (packageHeader + packageDetails which stored in TempData) to database.

    Your response does not change the fact that the user must click two buttons to save a record and three to delete.  Be aware, I downloaded your code and I can see that you are caching the data in the UI and by UI I mean the MVC application.  It is much easier, more efficient, and less error prone, to make an async call to the database and simply Save, Delete, or Update a record.  You get the full power of two servers executing code rather than one.  

    I would fail this design in a code review for the simple well-known fact that a web application should be able to function if Session is empty.  Your design fails this fundamental check.  If Session is empty, you just lost all the changes.   Cookie are more durable form of persistence but limited in size and not a good option for caching records.

    Anyway, the error is very clear.  You have a bug syncing Session with the database.  

    Friday, May 29, 2020 12:08 PM
  • User-79977429 posted

    Maybe you are true, but my main problem was not solved.

    Suppose, i have a list of details which should save to database (in disconnected mode).

    I don't looking of that this design is good or bad. I just to find the problem & solve it.

    Friday, May 29, 2020 12:30 PM
  • User475983607 posted

    Maybe you are true, but my main problem was not solved.

    Suppose, i have a list of details which should save to database (in disconnected mode).

    I don't looking of that this design is good or bad. I just to find the problem & solve it.

    My best guess is you have a list of entities in various states, Update, Insert, and Delete and you want to sync the entities with the DbContext.  Please read the official documentation as it covers various methods and designs for detecting insert/update and soft deletes. https://docs.microsoft.com/en-us/ef/core/saving/disconnected-entities
    Friday, May 29, 2020 2:09 PM
  • User-79977429 posted

    Thanks for reply

    After read your link, I've made change to 'AttachPackageDetailsToDbContext' method which call in update action as follow :

    private void AttachPackageDetailsToDbContext(DefaultVisitProductHeaders packageHeader)
            {
                List<DefaultVisitProductDetails> lstPackageDetails = this.GetPackageDetailsList();
    
                List<DefaultVisitProductDetails> lstItemsAdded = lstPackageDetails.Where(d => d.InternalHelperEntityState == InternalHelperEntityState.Added).ToList();
                List<DefaultVisitProductDetails> lstItemsModified = lstPackageDetails.Where(d => d.InternalHelperEntityState == InternalHelperEntityState.Modified).ToList();
                List<DefaultVisitProductDetails> lstItemsDeleted = lstPackageDetails.Where(d => d.InternalHelperEntityState == InternalHelperEntityState.Deleted).ToList();
    
                var existingPackageHeader = _dbContext.DefaultVisitProductHeaders.Include(h => h.DefaultVisitProductDetails).FirstOrDefault(h => h.DefaultVisitProductHeaderRowID == packageHeader.DefaultVisitProductHeaderRowID);
                foreach (DefaultVisitProductDetails detailItem in lstItemsAdded)
                {
                    var existingPackageDetail = existingPackageHeader.DefaultVisitProductDetails.FirstOrDefault(d => d.DefaultVisitProductDetailRowID == detailItem.DefaultVisitProductDetailRowID);
                    if (existingPackageDetail == null)
                        existingPackageHeader.DefaultVisitProductDetails.Add(detailItem);
                }
    
                foreach (DefaultVisitProductDetails detailItem in lstItemsModified)
                {
                    var existingPackageDetail = existingPackageHeader.DefaultVisitProductDetails.FirstOrDefault(d => d.DefaultVisitProductDetailRowID == detailItem.DefaultVisitProductDetailRowID);
                    if (existingPackageDetail != null)
                        _dbContext.Entry(existingPackageDetail).CurrentValues.SetValues(detailItem);
                }
    
                foreach (DefaultVisitProductDetails detailItem in lstItemsDeleted)
                    _dbContext.Entry(detailItem).State = Microsoft.EntityFrameworkCore.EntityState.Deleted;            
            }

    Please not at this section :

    var existingPackageHeader = _dbContext.DefaultVisitProductHeaders.Include(h => h.DefaultVisitProductDetails).FirstOrDefault(h => h.DefaultVisitProductHeaderRowID == packageHeader.DefaultVisitProductHeaderRowID);
                foreach (DefaultVisitProductDetails detailItem in lstItemsAdded)
                {
                    var existingPackageDetail = existingPackageHeader.DefaultVisitProductDetails.FirstOrDefault(d => d.DefaultVisitProductDetailRowID == detailItem.DefaultVisitProductDetailRowID);
                    if (existingPackageDetail == null)
                        existingPackageHeader.DefaultVisitProductDetails.Add(detailItem);
                }

    Based on your link, I've reload my current defaultHeader along with all related details to handle crud operation (for this case, Insert). Unfortunately the same problem is already exists & was not solved.

    Please take a look at my debugging for 'AttachPackageDetailsToDbContext' method , when i add new detailItem & hit save to save all data to database :

    As you see in above image, before execute the give breakPointed line, we have only 1 entity in changeTracker (it's our current packageHeader entity).

    Now, After execute the given line :

    We have 3 entities in changeTracker.

    Now, After execute 'existingPackageHeader.DefaultVisitProductDetails.Add(detailItem)' line :

    After refresh 'watch' window, we'are facing a given error, Now, after some seconds, when click on refresh again :

    Our _dbContext loads unwanted packageHeaders & packageDetails which is not related to my packageHeader & details!

    Please check it & solve my problem, I appreciate.

    Thanks in advance

    Friday, May 29, 2020 4:42 PM
  • User711641945 posted

    Hi hamed_1983,

    Could you update your whole code then we should reproduce your issue and helped you.

    Best Regards,

    Rena

    Tuesday, June 16, 2020 2:00 AM