I'm new to EF, and I'm trying to understand the best way to handle inserting and updating data. Just for some context, I'm using the .net mvc website boiler plate, and I've created a customers table with a 1:1 relationship to aspnetusers. I've created a view to manage customer data.
Here is my HttpPost ActionResult:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> AddCard(External.Stripe.Customer customer)
{
if (!ModelState.IsValid)
{
return View(customer);
}
External.Stripe.CustomerOperations co = new External.Stripe.CustomerOperations();
customer = co.Create(customer);
var user = UserManager.FindById(User.Identity.GetUserId());
customer.UserId = user.Id;
var context = new External.Stripe.CustomerContext();
context.Customers.Add(customer);
context.SaveChanges();
return RedirectToAction("Index", "Manage");
}
I feel like I'm going down the wrong path, because if I make any updates to the customer model, I actually need to check EF if my key already exists, and if it does, run an update instead. I figured EF would have a native way of saving my model if it needs to be updated. I also don't know if this is even where I would persist my model, or if it is better placed somewhere else in my code.
Short Answer
...if I make any updates to the customer model, I actually need to check EF if my key already exists, and if it does, run an update instead.
AddOrUpdate() does just this. From the docs:
Adds or updates entities by key when SaveChanges is called. Equivalent to an "upsert" operation from database terminology
Example
In other words, change your code to this:
context.Customers.AddOrUpdate(c => c.UserId, customer);
context.SaveChanges();
In the above example, the c => c.UserId is an expression specifying that the UserId should be used when determining whether an Add or Update operation should be performed.
Considerations
AddOrUpdate() matches database tables based on an arbitrary, user supplied key value. In the example, this key is UserId. Be sure to use an appropriate key.
AddOrUpdate() updates all entity values and sets database cells to null for properties that a lack value. Be sure to set all the property values of the object (e.g. customer) before using it.
See also
Update Row if it Exists Else Insert Logic with Entity Framework It has a few answers that talk about four or more different approaches.
I personally like a long winded approach of pulling the data object and using if null to determine if it should be added or updated. However, I do have a table with a lot of columns that gets changed frequently due to new or deprecated business rules. So I use the below
var currentModel = dbContext.Table.First(t => t.Id == _id);
dbContext.Entry(currentModel).CurrentValues.SetValues(newModel);
dbContext.SaveChanges();
I get my newModel from my viewModel where I have a ToModel method that returns the DTO. Now all I have to update is my view and viewModel when a change to the database table is made.
Related
I'm surprised that despite spending quite some time looking for the answer I haven't found it on the internet myself. What's even more surprising to me is that people tend to compare Attach to Add, and not to the Update. Furthermore, I've found very little mentions of the actual Update method, despite it being seemingly most obvious choice for the desired modify operation.
From the materials I managed to find I figured, that Attach should be used when you know the object exists somewhere in the database. If you don't make changes to it, no operations will be performed on the entity, and if you do - it will be updated. But then why would you need Update if it does the same thing from the sound of it?
I have this code snippet which I took from udemy course.
MovieController.cs
[HttpPut("{id}")]
public IActionResult ReplaceMovie(long id, [FromBody] Movie m)
{
if (ModelState.IsValid)
{
////
// logic in question starts here vvv
////
m.MovieId = id;
if (m.Studio != null && m.Studio.StudioId != 0)
{
context.Attach(m.Studio);
}
context.Update(m);
context.SaveChanges();
return Ok();
}
else
{
return BadRequest(ModelState);
}
}
But then why not just make it this way?
m.MovieId = id;
context.Update(m);
context.SaveChanges();
return Ok();
Would it not result in the same outcome, since we know that in case the Movie already has it's Studio assigned, then it (the Studio) exists in the database and we don't need to apply any changes to it?
Now if we applied some changes to the movie AND to the studio objects, then wouldn't it be safer to just perform the update modifications in their individual controllers? For example - if I want to update the movie and the studio, then I would first call the StudioController which would perform the modify operation on the Studio and then call the MovieController to update the movie.
What approach would be optimal for the best flexibility and clarity? What should I know about Attach and Update in general?
.Update will mark the entity as being in a Modified state. Update can be used to start tracking a mix of new and existing entities where the existing entities may have some modifications. The new entities will be inserted while the existing entities will be updated.
.Attach will mark the entity as being in an Unchanged state. However, entities will be put in the Added state if they have store-generated keys (e.g. Identity column) and no key value has been set. This means that when exclusively using store-generated keys, Attach can be used to start tracking a mix of new and existing entities where the existing entities have not changed. The new entities will be inserted while the existing entities will not be saved other than to update any necessary FK values.
Source Info:
Why use Attach for update Entity Framework 6?
https://blog.oneunicorn.com/2016/11/17/add-attach-update-and-remove-methods-in-ef-core-1-1/
I have tried this using a List and it retains the value fine, but when i change it to a stack the value is lost. I have been stepping through but I can't understand why its gone.
public ActionResult AddToOrder()
{
//Get logged in student from db
var student = _db.Students.Find(User.Identity.GetUserId());
//Create a new Order
var order = new Order();
//add all elements from Student collection to Order collection
foreach (var app in student.Applications)
{
order.Applications.Add(app);
}
//assign order FK
order.StudentId = User.Identity.GetUserId();
//Push the order to the students Stack collection
student.Orders.Push(order); //(When the Student implements a List collection the value
//passes to the Stripe controller with no problems)
//add order to db & save changes
_db.Orders.Add(order);
_db.SaveChanges();
//redirect to stripe controller
return RedirectToAction("Create", "Stripe");
}
//STRIPE CONTROLLER
public ActionResult Create(string stripeToken)
{
var student = _db.Students.Find(User.Identity.GetUserId());
student.Orders.Count // = 0 with Stack
// = 1 with List
}
The only thing I change is Stack to List in the Student entity and push to add. Anybody any ideas whats going on?
I guess I could achieve what I want (LIFO) by using a list in this way. I would still like to know what's going on with my stack though.
myList.Insert(0, order) - - - my List.First()
Student is an Entity Framework entity - an object representation of a database table. Its Orders property represents a relationship to another table. When you add order objects to a Student's Orders property, Entity Framework does a ton of stuff under the hood to make sure that all of the object data is correctly written to the database as table data, with all of the proper foreign keys and such, when you call SaveChanges. Same thing when you call Students.Find: There's a lot of stuff going on to translate your code to a SQL query, run it, then turn the resulting SQL table data into a convenient Student object.
Entity Framework does all of that automatically, but in order for it to work, entity classes like Student need to be defined in a particular way such that EF knows how to handle them. To put it succinctly: properties like Orders that represent relationships to another table need to be Lists. When you redefine Student.Orders to be a Stack, the Entity Framework machinery breaks, and EF can no longer use Orders to work with that portion of the database.
When your app redirects from one controller to another, the orders data is not retained in memory in the app, only in the database. When the target controller loads, your code is trying to load the data from the database that the previous controller was supposed to have saved there. One of two things is happening:
Your change to use Stack broke the Students entity such that the data doesn't get saved to the database at all, OR
Entity Framework can manage to handle Stack well enough to save the data, but not load it.
I'm not sure which is happening with this particular change, but either one is undesirable.
Orders needs to be a List. If you need to preserve some kind of ordering to the list, that needs to be explicit within the data model so that it ends up in the database. Maybe your Order class needs a LineNumber property or something similar. If you do something like that, keep in mind that both the code and the database have to be updated. Search for "Entity Framework migrations" for information about that, and ask a separate question if you get stuck on it.
I know it can be silly question, but although ...
Im learning ASP.NET MVC 4 and got one question about the dbcontext.Entry method, lets assume we got such method :
public ActionResult Edit(Movie movie)
{
if (ModelState.IsValid)
{
db.Entry(movie).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
How the db.Entry(movie) determine which exactly row from the database is this "movie" instance? If all properties of this movie parameter object will be changed, how will it be possible to determine which row this instance should be attached to? I got smth on my head like it will determine by the Id which we cant change but not sure, so better if I ask :)
When EF generates the models it includes information on what column is the primary key in the database. db.Entry(model) binds the model to the context using that primary key and whatever primary key it shares in the database will be overwriten by the data in the model when SaveChanges() is called. If the primary key is 0, or empty you must set the State to EntityState.Added instead of EntityState.Modified.
As another answer said this can fail. The reason that binding can fail is that a context can only track a single distinct entity once (entity being a distinct class and id). If you attempt to do the following you'll get an exception:
var dbModel = db.Movie.Find(movie.ID);
db.Entry(movie.ID) // Doesn't matter what you do, this will always fail
Because the db context is already tracking a Movie with the same ID.
This is one of the larger reasons that you don't want to share db contexts between users, because another user attempting to update the same entry will cause the second one to throw an exception. While the transaction will be ACID, you can end up with edge case race conditions (for lack of a better term) between users.
Every Table that used by EF must have a Primary key, unless it is a collection , so the EF know the row by the primary key that stay the same.
This method has a parameter Movie, but it has nothing to do with database. You must update object that you will get from db like:
public ActionResult Edit(Movie updatedMovie)
{
if (ModelState.IsValid)
{
//db=your entities context
//here you will get instance of record from db that you want to update
Movie movie = db.Movie.Find(updatedMovie.Id);
TryUpdateModel(movie);
ctx.SaveChanges();
return RedirectToAction("Index");
}
return View(movie);
}
Maybe there is more efficient way , but I'm using it like this and it works fine.
If code in your question is a version that is supposed to be a working version, then it wasn't working for me when I have been trying to solve a similar problem so I had to solve it by using code above finally.
So I'm trying to do an update to an Entity model but it's not going as well as I'd like. I'm only trying to update a few fields in my model, but when I have the following code run, I get a SqlException complaining that there are fields (that I'm not trying to edit) that do not allow nulls. It's seeming like it's trying to create a new row in the database, instead of just updating the existing one? Not too sure what to make of it. Any help would be appreciated.
[HttpPost]
public ActionResult Budget(Proposal proposal)
{
if (ModelState.IsValid)
{
db.Proposals.Attach(proposal);
db.ObjectStateManager.ChangeObjectState(proposal, EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Details", new {id = proposal.ProposalID});
}
return View(proposal);
}
Here's the SqlException that I'm getting: "System.Data.SqlClient.SqlException: Cannot insert the value NULL into column 'Ulid', table 'casurg2_dev2.dbo.Proposals'; column does not allow nulls. UPDATE fails.
The statement has been terminated."
And here's the view in question: http://pastie.org/7984222
Ok, the source didn't really help as much (and btw - this is SO, you can paste your code here; no need to be pasting it elsewhere ;)
I did ask for the class definition of the Proposal entity aswell... but in it's absence i can only suggest [at a guess] that your put the original value of Ulid as a hidden field alongside your ProposalId like such:
#Html.HiddenFor(model => model.ProposalID)
#Html.HiddenFor(model => model.Ulid)
so that the model binder can wire it up correctly on the post.
There are 2 ways i can think of to get around the effort with all the hiddenfor's
Use the Add Controller Wizard to set the template to EntityFramework - then modify the related View code. throw away what you don't want etc.
Change the Action method to re-fetch the Proposal record (using its ProposalId) and then bind the changed data to it. This is not the kinda advice i should be giving (an extra, unnecessary round trip to the db)
[HttpPost]
public ActionResult Budget(Proposal proposal)
{
if (ModelState.IsValid)
{
var proposalToMerge = db.Proposals.Find(proposal.ProposalId);
UpdateModel(proposalToMerge);
// don't need this anymore as entity instance is being tracked
// db.Proposals.Attach(proposalToMerge );
// db.ObjectStateManager.ChangeObjectState(proposalToMerge , EntityState.Modified);
db.SaveChanges();
return RedirectToAction("Details", new {id = proposal.ProposalID});
}
return View(proposal);
}
Or summing like that. Untested.
I have an issue with my method that updates the database with user information.
during HttpGet it uses a view model to ensure the integrity of the data, and in HttpPost, the data is passed back and is there. (ran some checks with breakpoints and everything was holding the correct user data that they should have been)
However, when I run my .Save() method, this information is not stored into the DB.
I've checked to see if its pointing to the correct database by changing the data manually, it comes up in a list view just fine. I'M missing something, just can't figure out what ><
Below is the code there is form data in all of the relevant view models, but it just doesn't save!
[HttpPost]
public ActionResult HouseCreate(CreateHouseViewModel viewModel)
{
if (ModelState.IsValid)
{
var house = new House();
house.address = viewModel.address;
house.postCode = viewModel.postCode;
house.noOfDisputes = viewModel.noOfDisputes;
_db.Save();
return RedirectToAction("House");
}
return View(viewModel);
}
house is an object of my house database, modelled correctly with correct Primary keys in place (double checked this in the database viewer.)
save calls:
void DataSources.Save()
{
SaveChanges();
}
No errors come up, which makes it even worse.
using (var context = new UnicornsContext())
{
var unicorn = new Unicorn { Name = "Franky", PrincessId = 1};
context.Unicorns.Add(unicorn); // your missing line
context.SaveChanges();
}
Source
Your "_db" object, I assume is entity framework.
This will have a collection on it, maybe _db.Houses ? You need to add the object to that.
Since it appears you are using Entity Framework, you'll need to inform Entity Framework that your model has been updated. Inbetween the HttpGet and the HttpPost methods, the model becomes dissassociated from the database context. I think the following code work to re-attach it to the context so you can save the changes:
context.Entry(myEntity).State = EntityState.Modified;
context.MyEntities.Attach(myModel);
context.SaveChanges();
Edit: Forgot to add the re-attach method part, assuming this is an existing record in the DB that your updating.
This I find is a far more reliable means to adding items to the database:
// Note: db is your dbContext, model is your view model
var entity = db.Entry(model); // Required to attach the entity model to the database
// you don't need the "var entity = " but its useful
// when you want to access it for logging
db.Entry(model).State = EntityState.Added; // Required to set the entity as added -
// modified does not work correctly
db.SaveChanges(); // Finally save