简体   繁体   中英

C# ASP.Net MVC Updating Multiple rows in database is creating extra rows

I have in my database one to two rows that can and will be updated at a given time based on the Id . This is a List<> of data that I'm able to send to the View and submit it to the [HttpPost] method in the controller just fine but when I call the update method I've created, It finds the corresponding rows and goes thru the foreach loop supposedly updating them but really just adds the updated data as two new rows to the table. I cant seem to figure out what Im doing wrong.

Table before update

+------+--------+-------------+------------+-------------+
|  ID  | ItemID | PriceTypeID | ListTypeID | DirectPrice |
+------+--------+-------------+------------+-------------+
| 1021 |  62238 |           2 |          1 |        0.00 |
| 1022 |  62238 |           3 |          1 |        0.00 |
+------+--------+-------------+------------+-------------+

Update Function

public static void UpdateItem(List<PricingOptionDTO> pricingOptionDTO)      
using (DBContext context = new DBContext()) {

 foreach (var item in pricingOptionDTO)
 {
     Entities.PricingOption pricingOptionTb = context.PricingOptions.Where(a => a.ID.Equals(item.ID)).FirstOrDefault();

        if (pricingOptionTb != null)
           {                                                
            pricingOptionTb .DirectPrice = item.DirectPrice;                
            pricingOptionTb .Increment = item.BidIncrement;
            pricingOptionTb .WarningCap = item.BidWarningCap;
           }

         context.PricingOptions.Add(pricingOptionTb);
         context.SaveChanges(); 
   }
       
      context.SaveChanges(); 
}

Table after update

+------+--------+-------------+------------+-------------+
|  ID  | ItemID | PriceTypeID | ListTypeID | DirectPrice |
+------+--------+-------------+------------+-------------+
| 1021 |  62238 |           2 |          1 |        0.00 |
| 1022 |  62238 |           3 |          1 |        0.00 |
| 1023 |  62238 |           2 |          1 |        1.00 |
| 1024 |  62238 |           3 |          1 |        2.00 |
+------+--------+-------------+------------+-------------+

Something is missing from your example:

context.PricingOptions.Add(PricingOption);

What is "PricingOption" here?

The most obvious looking thing missing from the example that might explain the behaviour you are seeing is the need for an else condition:

 Entities.PricingOption pricingOptionTb = context.PricingOptions.Where(a => a.ID.Equals(item.ID)).SingleOrDefault();

if (pricingOptionTb != null)
{                                                
    pricingOptionTb .DirectPrice = item.DirectPrice;                
    pricingOptionTb .Increment = item.BidIncrement;
    pricingOptionTb .WarningCap = item.BidWarningCap;
}
else 
    context.PricingOptions.Add(PricingOption);

However it's not clear what "PricingOption" is here given you were iterating over a set of DTO. Where this issue commonly appears is when using Automapper to construct an entity from a DTO thinking that because it has an existing ID, adding it to the DbContext will be interpreted as an update. Unfortunately EF only tracks updates against tracked entity references so if you give it a different reference to the same data it will treat it as an insert.

On a side note: When loading an entity where you expect 0 or 1 entity to come back, use SingleOrDefault rather than FirstOrDefault . This provides an assertion that only 1 record should be found (or not). When using the First* methods you should always be expecting and allowing for the possibility of multiple matches, and that being the case always include an OrderBy* clause to ensure your execution and selection are predictable.

Update: To handle update or insert scenarios you would want something like:

 Entities.PricingOption pricingOptionTb = context.PricingOptions.Where(a => a.ID.Equals(item.ID)).SingleOrDefault();

if (pricingOptionTb != null)
{                                                
    pricingOptionTb .DirectPrice = item.DirectPrice;                
    pricingOptionTb .Increment = item.BidIncrement;
    pricingOptionTb .WarningCap = item.BidWarningCap;
}
else 
{
    pricingOptionTb = new PricingOption
    {
        DirectPrice = item.DirectPrice,
        Increment = item.BidIncrement,
        WarningCap = item.BidWarningCap
    }
    context.PricingOptions.Add(pricingOptionTb);
}

Ideally though you should either separate Add vs Update calls explicitly as separate methods and guard against Add requests where a row already exists, or Updates where a row doesn't exist, or consider inspecting the passed in ID or a state value on the DTO. Ie If ID = 0 it is a new record, otherwise treat it as an existing item that should exist in the DB.

if (item.ID == 0) // New item.
{
    // Consider adding a validation check for duplicate pricing options.
   var duplicatePricingOption == context.PricingOptions
       .Any(x => x.DirectPrice == item.DirectPrice
           && x.Increment == item.Increment
           && x.WarningCap == item.WarningCap);
   if (duplicatePricingOption)
       throw new ArgumentException("An existing pricing option exists for this combination.");

   var pricingOption = new PricingOption
   {
       DirectPrice = item.DirectPrice,
       Increment = item.BidIncrement,
       WarningCap = item.BidWarningCap
   };
   context.PricingOptions.Add(pricingOption);
}
else
{
    var pricingOption = context.PricingOptions.Where(a => a.ID.Equals(item.ID)).Single();

    pricingOption.DirectPrice = item.DirectPrice;                
    pricingOption.Increment = item.BidIncrement;
    pricingOption.WarningCap = item.BidWarningCap;
}

context.SaveChanges();

You should only use the Add method for new objects. In order to update existing objects, you need to call the Update method.

foreach (var item in pricingOptionDTO)
 {
     Entities.PricingOption pricingOptionTb = context.PricingOptions.Where(a => a.ID.Equals(item.ID)).FirstOrDefault();

        if (pricingOptionTb != null)
           {                                                
            pricingOptionTb .DirectPrice = item.DirectPrice;                
            pricingOptionTb .Increment = item.BidIncrement;
            pricingOptionTb .WarningCap = item.BidWarningCap;

            context.Set<PricingOptions>().Update(pricingOptionTb);
           }
         context.SaveChanges(); 
   }

You can also set the state of the object to 'Modified'

You need to do it this way:

if (pricingOptionTb != null)
{                                                
    pricingOptionTb.DirectPrice = item.DirectPrice;                
    pricingOptionTb.Increment = item.BidIncrement;
    pricingOptionTb.WarningCap = item.BidWarningCap;

    context.Set<Entities.PricingOption>().Attach(pricingOptionTb);
    context.Entry(entity).State = EntityState.Modified;
    context.SaveChanges();
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM