I am working with .Net Core and EF Core and have an issue where context.Entry(original).CurrentValues.SetValues(model); is not setting the values.
Some items in original are null, where they are not null in the model and I would have expected that they would be updated.
I also have string values that are not being set too.
There is no error either.
When stepping through the code, there is nothing to suggest that things are going wrong.
If I were to do
Original.Competencies = model.competencies and then context.SaveChangesAsync() this works.
this is the code block in its totality.
using (var context = GetDbContext())
{
var model = reportLesson.Adapt<RepositoryReportLesson>();
var original = context.ReportLessons.Find(reportLesson.Id);
context.Entry(original).CurrentValues.SetValues(model);
context.Entry(original).State = EntityState.Modified;
await context.SaveChangesAsync();
_logger.LogInformation($"->> Updated report lesson id: {id}");
return context.Entry(model).Entity.Adapt<ReportLessonLogicModel>();
}
So the question is, what is causing this to not set the values, and how do I get around it?
Related
I had a similar issue with this question, when updating a record using EF6.
I really thought I had cracked the whole updating thing, but now have to almost identical functions updating in what I think was an identical way. One works, the other doesn't. I have fixed the one that doesn't work by using Jamie's comment in the above question, but I'd like to understand if the function that works, really shouldn't and so is on borrowed time and I should make more like the 'fixed' one. Or, why the 'fixed' one didn't work in the first place. I even moved them into the same controller so that the database (DB) context was guaranteed the same. Have I missed something and they are not identical (functionally) at all?
It might also help some others out there that struggle with this as I did.
The function that works (cut down) is:
[HttpPost]
[Route("UpdateAddBusinessService")]
public async Task<IHttpActionResult> UpdateAddBusinessService(BusinessServiceDTO servicetoupdateoradd)
{
... pre check stuff...
try
{
if (servicetoupdateoradd.Id != null) // This is an existing service to be updated - if Is Null then create new
{
BusinessService businessService = await db.BusinessServices.FindAsync(servicetoupdateoradd.Id);
if (businessService != null)
{
Mapper.Map(servicetoupdateoradd, businessService);
db.Entry(businessService).State = EntityState.Modified;
await db.SaveChangesAsync();
return Ok("Service Updated");
}
else
The function that doesn't work is:
[HttpPost]
[Route("UpdateImage")]
public async Task<IHttpActionResult> UpdateImage(ImageDTO imageDTO)
{
... pre check stuff ...
try
{
// First find the image
// Image imagetoupdate = await db.Images.FindAsync(imageDTO.Id); <<-This FAILS.
Image imagetoupdate = db.Images.AsNoTracking().Single(x => x.Id == imageDTO.Id); <<- This WORKS
if (imagetoupdate != null)
{
imagetoupdate = Mapper.Map<ImageDTO, Image>(imageDTO); // Move the stuff over..
db.Entry(imagetoupdate).State = EntityState.Modified;
await db.SaveChangesAsync();
return Ok();
}
I wondered (as you will no doubt), if my Mapper function was doing anything, but I suspect not (without digging too deep, but I guess it could be), my Mapper.Config functions for the two DTO's are very similar:
cfg.CreateMap<Image, ImageDTO>();
cfg.CreateMap<ImageDTO, Image>();
and:
cfg.CreateMap<BusinessService, BusinessServiceDTO>();
cfg.CreateMap<BusinessServiceDTO, BusinessService>();
I would really just like to understand the 'correct' way of doing this so it doesn't bite me again. Thanks in advance.
EDIT: I was asked (quite reasonably) if the 'pre-check stuff' does anything to fetch the data, it doesn't, but there is a subtle difference, that I might have missed...
This is from the BusinessService function that works:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
string userid = User.Identity.GetUserId(); //Check user is valid
if (servicetoupdateoradd.UserId != userid)
{
var message = "User Id Not found - Contact support";
HttpResponseMessage err = new HttpResponseMessage() { StatusCode = HttpStatusCode.ExpectationFailed, ReasonPhrase = message };
return ResponseMessage(err);
}
This is from the UpdateImage function that didn't work:
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
string userid = User.Identity.GetUserId();
SGGUser user = db.Users.Find(userid); // Use this find and not UserManager becuase its a different context and buggers up the file save
if (user == null)
{
var message = "User Id Not found - Contact support";
HttpResponseMessage err = new HttpResponseMessage() { StatusCode = HttpStatusCode.ExpectationFailed, ReasonPhrase = message };
return ResponseMessage(err);
}
I see that in this one, though I don't fetch the relevant data, I do use the 'db' context.. could that be it?? The Image object does contain a reference to the user, so maybe that does some magic in the background code?
Appologies, I just didn't want to clutter up the question too much...
This line:
Mapper.Map(servicetoupdateoradd, businessService);
and this line:
imagetoupdate = Mapper.Map<ImageDTO, Image>(imageDTO); // Move the stuff over..
look similar, but do two different things.
The first line will tell Automapper to copy values from the first object over to the second object reference using the mapping rules.
The second line will tell Automapper to make a completely new reference to the entity with the mapped over values from the provided object and return it.
So in the first case, the entity reference is preserved to the one the DbContext knows about. The reference was loaded from a DbContext and should be tracking changes so you shouldn't even need to set it's entity state. In the second case, Automapper is creating an entirely new reference and assigning it over top the original reference. EF is treating that as a completely new instance and trying to attach it, resulting in it complaining because the context had already loaded that entity, you just overwrote the reference.
It should work if you change the second instance to:
Mapper.Map(imageDTO, imagetoupdate);
I have an external API resource that I need to use. The API sends me trigger as a post request. What I need to do is get the values, and update the current corresponded record in the SQL. The code that I came up with keeps adding a new value to the database.
[HttpPost("status")]
public async Task<IActionResult> Post(CompletionForCreateDto
completionForCreateDto)
{
// che ck the model state
if (!ModelState.IsValid)
return BadRequest(ModelState);
// find the incoming trigger information and map them to the disctionary
var form = Request.ReadFormAsync();
var formData = form.Result;
Dictionary<string, string> ddata = new Dictionary<string, string>();
foreach ( var key in formData.Keys)
{
var value = formData[key.ToString()];
ddata.Add(key, value);
}
// find the existed completion by ID
int id = Int32.Parse(ddata["record"]);
var completion = await repository.GetCompletion(id);
completion = mapper.Map<CompletionForCreateDto, Completion>(completionForCreateDto);
if (completion == null)
return NotFound();
// some test values to make sure it works.
completion.VitalSignBLA = ddata["vital_signs_spring_complete"];
completion.VitalSignBLADateTime = DateTime.Now;
// repository.Add(completion); ==> adds value as a new record
// repository.Update(completion);==> adds value as a new record
repository.Attach(completion); // ==> adds value as a new record
// SaveChanges()
await unitOfWork.CompleteAsync();
return Ok();
}
Above, first I get the trigger and put them into a dicmtionary so that I can use. Then, I tried to find the existed trigger record in my sql servere using their record value. Trigger's record and SQL record value are the same values. After that, I use some data to test it. Lastly, I used context Add, Update and Attach to update the SQL database. For all cases, the post request is kept adding a new row.
I am new to the dotnet core. I am not sure it this because I used HttpPost attribute or the code itself. Any help wpuld be appriciated!
The most probable cause for EF generating an insert statement for an entity when attaching it to a context (Through Attach, Update, Add or by setting its State) is missing values from the PK (most commonly the ID, but the PK can be more complex in other cases) for that entity.
When no PK is present, EF cannot know which entity you are referring to, so it sets the state to New. In your code, I suspect this is generating your issues:
var completion = await repository.GetCompletion(id);
completion = mapper.Map<CompletionForCreateDto, Completion>(completionForCreateDto);
You are getting some data from the repository, but then generating a new entity through mapper.Map(completionForCreateDto);. I suspect that this code is not setting the PK values, causing your issues. A naive fix would be to set these values after mapper.map. That, or update the mappings to map the values.
Using NopCommerce 3.8, Visual Studio 2015 proff.
I have created a plugin that is responsible for making restful calls to my Web API that exposes a different DB to that of Nop.
The process is run via a nop Task, it successfully pulls the data back and i can step through and manipulate as i see fit, no issues so far.
Issue comes when i try to update a record on the product table, i perform the update... but nothing happens no change, no error.
I believe this is due to the Context having no idea about my newly instantiated product object, however I'm drawing a blank on what i need to do in relation to my particular example.
Similar questions usually reference a "model" object that is part of the parameter of the method call, "model" has the method ToEntity which seems to be the answer in similar question in stack.
However my example doesn't have the ToEntity class/method possibly because my parameter is actually a list of products. To Clarify here my code.
Method in RestClient.cs
public async Task<List<T>> GetAsync()
{
try
{
var httpClient = new HttpClient();
var json = await httpClient.GetStringAsync(ApiControllerURL);
var taskModels = JsonConvert.DeserializeObject<List<T>>(json);
return taskModels;
}
catch (Exception e)
{
return null;
}
}
Method in my Service Class
public async Task<List<MWProduct>> GetProductsAsync()
{
RestClient<MWProduct> restClient = new RestClient<MWProduct>(ApiConst.Products);
var productsList = await restClient.GetAsync();
InsertSyncProd(productsList.Select(x => x).ToList());
return productsList;
}
private void InsertSyncProd(List<MWProduct> inserted)
{
var model = inserted.Select(x =>
{
switch (x.AD_Action)
{
case "I":
//_productService.InsertProduct(row);
break;
case "U":
UpdateSyncProd(inserted);
.....
Then the method to bind and update
private void UpdateSyncProd(List<MWProduct> inserted)
{
var me = inserted.Select(x =>
{
var productEnt = _productRepos.Table.FirstOrDefault(ent => ent.Sku == x.Sku.ToString());
if(productEnt != null)
{
productEnt.Sku = x.Sku.ToString();
productEnt.ShortDescription = x.ShortDescription;
productEnt.FullDescription = x.FullDescription;
productEnt.Name = x.Name;
productEnt.Height = x.Pd_height != null ? Convert.ToDecimal(x.Pd_height) : 0;
productEnt.Width = x.Pd_width != null ? Convert.ToDecimal(x.Pd_width) : 0;
productEnt.Length = x.Pd_depth != null ? Convert.ToDecimal(x.Pd_depth) : 0;
productEnt.UpdatedOnUtc = DateTime.UtcNow;
}
//TODO: set to entity so context nows and can update
_productService.UpdateProduct(productEnt);
return productEnt;
});
}
So as you can see, I get the data and pass data through to certain method based on a result. From that list in the method I iterate over, and pull the the entity from the table, then update via the product service using that manipulated entity.
So what am I missing here, I'm sure its 1 step, and i think it may be either be because 1) The context still has no idea about the entity in question, or 2) Its Incorrect calls.
Summary
Update is not updating, possibly due to context having no knowledge OR my methodology is wrong. (probably both).
UPDATE:
I added some logger.inertlog all around my service, it runs through fine, all to the point of the call of update. But again I check the product and nothing has changed in the admin section.
plugin
I have provided the full source as i think maybe this has something to do with the rest of the code setup possibly?
UPDATE:
Added the following for testin on my execute method.
var myprod = _productRepos.GetById(4852);
myprod.ShortDescription = "db test";
productRepos.Update(myprod);
This successfully updates the product description. I moved my methods from my service into the task class but still no luck. The more i look at it the more im thinking that my async is killing off the db context somehow.
Turned of async and bound the getbyid to a new product, also removed the lambda for the switch and changed it to a foreach loop. Seems to finally update the results.
Cannot confirm if async is the culprit, currently the web api seems to be returning the same result even though the data has changed (some wierd caching by deafult in .net core? ) so im creating a new question for that.
UPDATE: It appears that the issue stems from poor debugging of async. Each instance I am trying to iterate over an await call, simply put im trying to iterate over a collection that technically may or may not be completed yet. And probably due to poor debugging, I was not aware.
So answer await your collection Then iterate after.
I am having an infuriating issue where one paticular column in my entity will not save/update. I have tried numerous methods of updating the row such as manally assigning each property I want to update, to where I am now (see code block below). 2 columns update and save as expected (absent_type_id, and point_value) however no matter what I do the "description" column just will not save.
I have checked the debugger to see what the value is before the db.SaveChanges(); and confirmed it is set to the new value; however, selecting the row after the save (and checking the database) shows that the others values are updated, but the description reverts back to what it was...why could this be?
[HttpPost]
public JsonResult UpdateOccurrence(int occ_id,
string absent_type,
string description,
int point_value)
{
try
{
// Get id for the absent type
int absent_type_id = db.AT_absent_types.Single(a => a.absent_type == absent_type).absent_type_id;
var occurrenceToUpdate = new AT_occurrences
{
occ_id = occ_id,
absent_type_id = absent_type_id,
description = description,
point_value = point_value
};
db.AT_occurrences.Attach(occurrenceToUpdate);
db.SaveChanges();
//return call omitted
}
catch (Exception ex)
{
return Json(new {Result = "ERROR", Message = ex.Message});
}
}
As I mentioned - posting the point_value and absent_type save and update the entity perfectly, the description just will not save! i have no idea why. Any help or insight would be very much appreciated - this is driving me crazy! Cheers!
This may not be the answer you're looking for, but whenever I face a similar issue with EF, I delete the table completely, then build. If you're using TFS, you can always undo your pending changes if this doesn't work, which is the caveat I use when I do this for myself.
You use "Attach" to attach a disconnected property to the context. My understanding is Entity Framework has no way of knowing that the properties you're changing are different from those in the DB. So you have to explicitly tell the EF that you've changed a property.
Like this:
db.Entry(occurenceToUpdate).Property(o=>o.occ_id).IsModified = true;
or like this:
db.Entry(occurenceToUpdate).Property("occ_id").IsModified = true;
You'll have to repeat it for each properties you're setting.
I know for sure the above works but you can also try the following. If you're setting all the properties of the entity object, check if something like this works:
db.Entry(occurenceToUpdate).State = EntityState.Modified.
var paymentAttempt = _auctionContext.PaymentAttempts.Where(o => o.Id == paymentAttemptId).SingleOrDefault();
if (paymentAttempt != null)
{
paymentAttempt.PaymentAttemptStatusId = (int)PaymentAttemptStatus.Defunct;
paymentAttempt.PaymentAttemptStatus = _auctionContext.PaymentAttemptStatuses.Where(pas => pas.Id == paymentAttempt.PaymentAttemptStatusId).First();
var relevantWinningBidsTotalPrices = _auctionContext.GetWinningBidsTotalPricesForPaymentAttempt(paymentAttemptId).ToArray();
foreach (var winningBid in relevantWinningBidsTotalPrices)
{
winningBid.Locked = false;
_auctionContext.UpdateObject(winningBid);
}
_auctionContext.SaveChanges();
}
In the above code after
_auctionContext.SaveChanges();
is called winningBid is updated as expected but paymentAttempt isn't. Why is this? It is really frustrating. There is no error either. I would expect a failure to occur if there was a problem like EF wasn't tracking the object or something like that, but no such error is happening.
That's because you need to pass the paymentAttempt object to your context, to let it know that it is an object that needs to be updated.
For example, assuming that _auctionContext is an instance of DbContext:
// any changes related to the paymentAttempt object
_auctionContext.Entry(paymentAttempt).State = EntityState.Modified;
foreach (var winningBid in relevantWinningBidsTotalPrices)
{
winningBid.Locked = false;
_auctionContext.UpdateObject(winningBid);
}
_auctionContext.SaveChanges();
Another option is the Attach method:
_auctionContext.Attach(paymentAttempt);
_auctionContext.ObjectStateManager.ChangeObjectState(paymentAttempt, System.Data.EntityState.Modified);
If you don't have Entry try adding:
using System.Data.Entity.Infrastructure;
using System.Data.Entity;
then you may simply use:
_auctionContext.Entry(paymentAttempt).State = EntityState.Modified;
_auctionContext.SaveChanges();
I fell on this question but for a different problem. I discovered that if you call SaveChanges() on an object that hasn't been modified, EF will not update anything. This makes sense, but I needed the DB to be updated so that other users would see that a SaveChanges() had been executed, regardless of whether any fields had changed. To force an update without changing any fields:
Dim entry As DbEntityEntry = entities.Entry(myentity)
entry.State = Entity.EntityState.Modified
I know this is late but there's another explanation worth mentioning. Even though your field name contains ID and may be set to autoincrement, be sure to verify that you declared it in your table the primary key.