How to use TryUpdateModel - c#

I am readying this tutorial. I see from this tutorial that for update the author is using the following code:
....
var studentToUpdate = db.Students.Find(id);
if (TryUpdateModel(studentToUpdate, "",
new string[] { "LastName", "FirstMidName", "EnrollmentDate" }))
{
try
{
db.Entry(studentToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
...
}
But I don't understand why the following line is needed:
db.Entry(studentToUpdate).State = EntityState.Modified;
When I remove this line, the code still works well and update is done perfectly.
Can someone help me with whether that line is needed? If so, why when I remove it, the update works well.

It works well because you find the studentToUpdate from your context, that's way the entity is attached and the changes that are made by the TryUpdateModel method are saved when you call the SaveChanges method.
If you were working with a detached entity, for example doing this:
var studentToUpdate=new Student(){Id=id};
if (TryUpdateModel(studentToUpdate, "",
new string[] { "LastName", "FirstMidName", "EnrollmentDate" }))
{
try
{
db.Entry(studentToUpdate).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
...
}
In this case you have to call the Entry method to attach the entity to your context and change its state.

That line is explicitly telling the EF context that the entity has been modified and needs to be updated the next time SaveChanges() is called. The reason everything still works when you remove that line is that the context usually tracks those changes automatically for you. I have yet to come across a situation where I have needed to fiddle with EF's automatic change tracking in production, it seems to work well.
See How change tracking works in Entity Framework for a bit more info.

Related

Entity Framework tracks change after second update

The application was built on a bunch of asp .net core mvc and entity framework.
I have a map with markers on it. I want to change the parameters of a certain object through textboxes. The request from the frontend is written in axios, and it works flawlessly. From the first time I get the changes in the database. (mysql, provider: pomelo.mysql).
When I try to access the get request for the first time, I get the old state of the object.
HttpGet request is described here:
public async Task<IEnumerable<Poi>> GetPois()
{
var pois = await _poiService.GetPois();
if (pois.Status == Domain.Enum.StatusCode.Ok)
{
return pois.Data;
}
else { return null; }
}
I have an interface that describes the necessary set of manipulations with the Poi object.
IPoiService is described here:
public interface IPoiService
{
Task<BaseResponse<IEnumerable<Poi>>> GetPois();
Task<BaseResponse<Poi>> GetPoi();
Task<BaseResponse<bool>> DeletePoi();
Task<BaseResponse<Poi>> CreatePoi();
Task<BaseResponse<Poi>> UpdatePoi(Poi entity);
}
The service for working with the Poi object is described here:
public async Task<BaseResponse<IEnumerable<Poi>>> GetPois()
{
try
{
return new BaseResponse<IEnumerable<Poi>>
{
Data = await _poiRepository.GetAll().ToListAsync(),
Status = Domain.Enum.StatusCode.Ok
};
}
catch(Exception ex)
{
return new BaseResponse<IEnumerable<Poi>>
{
Status = Domain.Enum.StatusCode.InternalServerError,
Description = $"[GetPois]: {ex.Message}"
};
}
}
BaseResponse and the corresponding interface represents the response from the database, so it doesn't affect the update problem in any way.
I also have a repository that directly implements instance operations at the database level.
The repository is described here:
public class PoiRepository : IBaseRepository<Poi>
{
private readonly ApplicationDbContext db;
public PoiRepository(ApplicationDbContext db)
{
this.db = db;
db.Database.OpenConnection();
}
public Task Create(Poi entity)
{
throw new NotImplementedException();
}
public Task Delete(Poi entity)
{
throw new NotImplementedException();
}
public IQueryable<Poi> GetAll()
{
return db.Pois;
}
public Poi Update(Poi entity)
{
db.Pois.Update(entity);
db.SaveChanges();
return entity;
}
}
Thus, I get the problem that in order to get the current data, I need to perform two HttpGet requests, and only after that EF Core will return its current value to me.
The reason that Update(entity) sends off warning bells is that you are passing entities between server and client and back. When a controller returns a View(entity) you are sending a reference entity to the view engine to build the view. The view's #Model allows you to apply bindings but it is not a client-side copy of the entity. However, when your form submit or Ajax call etc. calls back with the #model that is NOT an entity, let alone the entity the view engine was given. It will only be a copy of data and only as complete as the view bindings could populate.
So it's hard to deduce what exactly you are witnessing without stepping through the application, but my gut says you are most likely getting confused by what you think is passing entity references around. Think of it this way, in your POST actions you could accept a set of ints, strings, and such for each of the values of the model, or a completely different class definition (DTO/ViewModel) with the same fields as the entity. ASP.Net would attempt to fill in using the data submitted with a Form POST or Ajax call. By accepting an "Entity" you are just telling EF to populate the data into a new untracked entity class. It's not the same instance as a DbContext originally loaded, and the DbContext is a different instance (or should be) than when the entity was originally loaded, it isn't tracking the entity that was originally loaded.
The resulting object will only contain the details that the view happened to have stored in the individual bound controls, pieced back together behind the scenes.
My recommendation is simply to never pass entities to, and especially from a view. Use an explicit ViewModel to represent the state sent to and from a view, then in your Update method:
Fetch the actual entity using the ViewModel ID,
Check a concurrency token (RowVersionNumber / Timestamp) to ensure no changes were made to the DB since you originally fetched the data to populate the View. (optional, but recommended)
Validate the data in your view model
Copy the data from the view model into the Entity. (Automapper can help here)
SaveChanges()
No use of Update or Attach in the DbContext/DbSet.

Auto detach entity from dbcontext

services.AddDbContextPool<SecurityDBContext>(options =>
options.UseSqlServer(GlobalConfig.Configuration["ConnectionStrings:DefaultConnection"],
b => b.UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery))
);
This is how I added dbcontext,I used AddDbContextPool so the instance will be use over and over again for performance.
db.Entry(new AdminBlockClientConfig()
{
ActionId = input.aid.ToLongReturnZiro(),
MaxValue = input.value.ToIntReturnZiro(),
IsActive = input.isActive.ToBooleanReturnFalse(),
SiteSettingId = siteSettingId.ToIntReturnZiro()
}).State = EntityState.Added;
db.SaveChanges();
This is my code for adding new entity.
readonly SecurityDBContext db = null;
static List<AdminBlockClientConfig> AdminBlockClientConfigs = null;
public AdminBlockClientConfigService(SecurityDBContext db)
{
this.db = db;
}
This is my service constructor
services.AddScoped<IAdminBlockClientConfigService, AdminBlockClientConfigService>();
And this is my service injection config
The problem:
I don't have any validation for ActionId inside of my add new entity so if the user posts -1 for ActionId the entity will not be insert into SQL Server (relation problem) the system raises an exception and everything is as planed but the main problem is that one of the instance of SecurityDBContext become corrupted and I am no longer be able to call save change on that instance because the entity instance is still attached to dbcontext.
What is need:
It would be great if I can detach the entity after an error automatically so I can save the context.
I know how to detach entity from dbcontext, I need to its happen automatically (there is so many validation need to be added to project and I can not put time for those validation and if I put that time there will be high change of missing some place and its will be bug that can destroy my application and for performance I don't like to change the way I added dbcontext instance).
Thanks for your time.
edited: AddDbContextPool is not the problem, if one of my services add invalid data to dbcontext the other services can not use that dbcontext

SaveChanges only works in debug

I have a MVC controller method that looks like this:
[HttpPost]
public async Task<JsonResult> Edit(Foo input)
{
if (ModelState.IsValid)
{
Foo current = await db.foos.FindAsync(input.Id);
current.SomeProp = input.SomeProp; // etc.
db.Entry(current).State = EntityState.Modified;
await db.SaveChangesAsync();
return Json(true, JsonRequestBehavior.AllowGet);
}
return Json(false, JsonRequestBehavior.AllowGet);
}
This works when I'm debugging in Visual Studio. When I'm not debugging, I get a 500. What difference would debugging make here?
Note: I'm not using db.Entry(current).CurrentValues.SetValues(input); because there are some properties of Foo I don't want to change here.
Edit: I can't figure out how to get the exception details when not in debug mode. I tried writing it to a file. This works in debug mode (with throw new Exception("test"); taking the place of the statement that only works in debug mode) but I get a 401 when not debugging. The IIS user does have write permission on the directory.
The exception was a DbEntityValidationException indicating that a required field was missing. The missing field is a relation of Foo.
Based on Entity Framework Loading Related Entities, I believe my code loads Foo's relations lazily. I didn't even consider this possibility because I misunderstood comments like "EF doesn't support asynchronous lazy loading." Inspecting the object in debug mode seemed to confirm that its relations were being loaded eagerly. But now I realize that inspecting the object may have triggered lazy loading!

About EntityFramework AsNoTracking

I am using EntityFramework v6.1.2.
I read some articles and know about AsNoTracking extension.
When AsNoTracking is called, it means that if the entity is not attached, the context and the entity updated "should fail".
But I have tried and updated successfully, my code is in below:
private readonly DemoObjectContext _objectContext = new DemoObjectContext();
var order = _objectContext.Orders.AsNoTracking().FirstOrDefault(x => x.Id == 1);(1 is the primary key)
order.OrderStatus = OrderStatus.Processing; // change the orderstatus
//_objectContext.Set<Order>().Attach(order);
_objectContext.Entry(order).State = EntityState.Modified;
_objectContext.SaveChanges();
Is something wrong or did EntityFramework(6.1.2) changed something?
Please help me
No issues with EF. It works because Entry attaches the entity to the context.
See here for more details.
And on this SO specifically about Entry

Breeze SaveChanges always throws DbUpdateConcurrencyException when deleting entity

I have just enabled the "Concurrency Mode" property to fixed of one of my entity.
Everything works great when I try to update.
But when I try to delete the entity, I always get this error :
DBUpdateConcurrencyException
Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded. Refresh ObjectStateManager entries.
Is there any way to disable DBUpdateConcurrencyException for delete operation? If not, how can I manage this type of exception?
[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
try
{
return _breezeComponent.SaveChanges(saveBundle);
}
catch (DbUpdateConcurrencyException ex)
{
//Workaround needed
}
}
BTW, I have already looked at these kinds of solution : How to ignore a DbUpdateConcurrencyException when deleting an entity . Is there any way I can integrate this code with Breeze engine?
EDIT:
I have upgraded from version 1.4.5 to 1.4.7 and I still have the same problem.
If I look at the JSON object, would changing the entityState from "Deleted" to "Detached" would be a solution? Is there any setting in Breeze that can help me do that?
{
"entities": [
{
"EventId": 11111,
"EventName": "Jon Doe",
"EventCity": "Montreal",
"EventDate": "2014-01-24T00:00:00Z",
"TermDate": "2014-01-08T00:00:00Z",
"Insertedby": "Terry",
"InsertDate": "2014-01-06T14:31:14.197Z",
"Updatedby": "Terry",
"UpdateDate": "2014-01-07T15:50:53.037Z",
"entityAspect": {
"entityTypeName": "Event:#Cds.Corpo.GuestList.Models",
"defaultResourceName": "Events",
"entityState": "Deleted",
"originalValuesMap": {},
"autoGeneratedKey": {
"propertyName": "EventId",
"autoGeneratedKeyType": "Identity"
}
}
}
],
"saveOptions": {}
}
Just a guess, but do you have some form of cascaded delete turned on in your database. If so, then the issue may be that the delete of a parent is causing the child to also be deleted and when the breeze server code kicks in it tries to delete the 'already' deleted child again. When this delete cannot find the child it throws the concurrency exception.
The way to avoid this is to use the BeforeSaveEntities interception point and remove all of the children of any deleted entities ( that are part of the cascade delete relationship) from the 'saveMap'. See the Breeze documentation on BeforeSaveEntities on this page: http://www.breezejs.com/documentation/contextprovider
There was a mecanism hidden in our application framework that did update the property before saving changes to the database. So this wasn't a Breeze issue.
A filter has now been added to the solution to exclude this pattern when deleting.

Categories

Resources