Home > Blockchain >  Cannot save changes to Database Context
Cannot save changes to Database Context

Time:12-23

I have a problem saving the changes to Database Context. When i don't save i can see that the listing status is successfully changed , but when i try to save it I get an error which is saying : " There is already an open DataReader associated with this Connection which must be closed first." And i don't know where that comes from. When i try to do it asynchronous i get the same error.

AdministratorController.cs

 [Route("/Admin")]
    [ApiController]


    public class AdministratorController : Controller
    {
        private readonly dbContext _dbContext;

        public AdministratorController(dbContext dbContext)
        {
            _dbContext = dbContext;
        }


        ///////////////////////////////////
        ///                             //
        ///   Accept or Reject Listings //
        ///                             //
        //////////////////////////////////
        [HttpPost]
        [Route("acceptListing/{listingId}")]
        [AllowAnonymous]
        //[Authorize(Roles="Administrator")]
        public ActionResult AcceptList([FromRoute]int listingId)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest();
            }
            if (listingId == null)
            {
                return NotFound("Listing not found.");
            }

            foreach (Listing listing in _dbContext.Listings)
            {
                Console.WriteLine(listing.Status);
                if(listing.Id == listingId)
                {
                    if(listing.Status == ListingStatus.Accepted)
                    {
                        return BadRequest("Listing already accepted.");
                    }
                    else
                    {
                        listing.Status = ListingStatus.Accepted;
                        Console.WriteLine(listing.Status);
                        _dbContext.SaveChanges();

                    }
                    return Ok();
                }
            }

            return BadRequest("Couldn't find right listing.");
        }

enter image description here

CodePudding user response:

Rather than looping through all listings looking for the one with the Id you want, just filter and get.

Listing? listing = _dbContext.Listings.FirstOrDefault(l => l.Id == listingId);

if (listing is null)
{
    return BadRequest("Couldn't find right listing.");
}     

if(listing.Status == ListingStatus.Accepted)
{
    return BadRequest("Listing already accepted.");
}
else
{
    listing.Status = ListingStatus.Accepted;
    Console.WriteLine(listing.Status);
    _dbContext.SaveChanges();

}
return Ok();

CodePudding user response:

The problem here is that you are iterating the data being fetched from database and in the same time you are trying to save something from the same context. Quick fix is to use ToList in foreach:

foreach (Listing listing in _dbContext.Listings.ToList())
{
   // ..
}

But in general you should not fetch everything from the database to process only one item. Just write query that will filter everything on database side. Something along this lines (not tested):

var listing = _dbContext.Listings.FirstOrDefault(l => l.Id == listingId);
if (listing is null)
{
    return NotFound();
}

if (listing.Status == ListingStatus.Accepted)
{
    return BadRequest("Listing already accepted.");
}
else
{
    listing.Status = ListingStatus.Accepted;
    Console.WriteLine(listing.Status);
    _dbContext.SaveChanges();
}
return Ok();

CodePudding user response:

change the code like this :

var listings = _dbContext.Listings.Tolist();
foreach (Listing listing in listings)
        {
            Console.WriteLine(listing.Status);
            if(listing.Id == listingId)
            {
                if(listing.Status == ListingStatus.Accepted)
                {
                    return BadRequest("Listing already accepted.");
                }
                else
                {
                    listing.Status = ListingStatus.Accepted;
                    Console.WriteLine(listing.Status);
                    _dbContext.Update(listing);
                    _dbContext.SaveChanges();
                }
                return Ok();
            }
        }

CodePudding user response:

The problem is occurring because you are using _dbContext.Listings, which is an IQueryable, in a foreach loop. This will keep the connection open until all of the Listing entities have been enumerated, which is causing the "There is already an open DataReader associated with this Connection which must be closed first" error when you try to call _dbContext.SaveChanges().

To fix this issue, you can materialize the query results into a list by calling ToList() on _dbContext.Listings, and then use the list in the foreach loop. This will retrieve all of the Listing entities from the database at once and store them in memory, allowing you to iterate over the collection multiple times without hitting the database again. This will also close the connection associated with the DataReader, allowing you to call _dbContext.SaveChanges() without encountering an error.

Here's the revised code:

if (!ModelState.IsValid)
{
    return BadRequest();
}

if (listingId == null)
{
    return NotFound("Listing not found.");
}

var listings = _dbContext.Listings.ToList();
foreach (Listing listing in listings)
{
    Console.WriteLine(listing.Status);
    if(listing.Id == listingId)
    {
        if(listing.Status == ListingStatus.Accepted)
        {
            return BadRequest("Listing already accepted.");
        }
        else
        {
            listing.Status = ListingStatus.Accepted;
            Console.WriteLine(listing.Status);
            _dbContext.SaveChanges();

        }
        return Ok();
    }
}

return BadRequest("Couldn't find right listing.");

However, it's important to keep in mind that materializing the query results into a list can have performance implications if the collection of Listing entities is large. Materializing the results into a list will retrieve all of the Listing entities from the database at once, which can be inefficient if you only need to access a small subset of the entities.

In this case, you might want to consider using a different approach, such as filtering the Listing entities using a Where clause and then using FirstOrDefault to retrieve the single Listing entity that you are interested in. This will allow you to retrieve only the Listing entity that you need, rather than retrieving all of the Listing entities and then filtering them in memory.


if (listingId == null)
{
    return NotFound("Listing not found.");
}
var listing = _dbContext.Listings.Where(l => listingId == listingId).FirstOrDefault();

if (listing == null)
{
    return NotFound("Listing not found.");
}

if (listing.Status == ListingStatus.Accepted)
{
    return BadRequest("Listing already accepted.");
}

listing.Status = ListingStatus.Accepted;
_dbContext.SaveChanges();

return Ok();

  • Related