What is the difference between Attach and Update? - c#

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/

Related

Why is this unused line breaking Entity Framework?

I am updating some existing code of a former colleague and have a strange issue where an unused line is causing an error with Entity Framework. If I comment out the code tagged with //This Line!, everything works.
foreach (Place item in ListOfPlaces)
{
//This line!
List<Place> PlacesList = context.Places.Where(x => x.PlaceNumberID == item.PlaceNumberID).ToList();
long PlaceId = context.Places
.Where(x => x.PlaceNumberID == item.PlaceNumberID)
.Select(x => x.PlaceId)
.FirstOrDefault();
if (PlaceId != 0)
{
item.ID = PlaceId;
context.Places.Attach(item);
context.Entry(item).State = System.Data.Entity.EntityState.Modified;
}
}
If I include that line, I get the error shown here on the Attach(item) line:
Attaching an entity of type 'Place' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
I know how to fix this from a code point of view (remove the line!), but I can't work out why its breaking the application if somebody could kindly explain please.
I can't work out why its breaking the application
Looks to me like the line causes the download of some Place with ID N - see the ToList on the end? It will trigger the query to run and download data. EF creates objects from every row it receives because that's the default behavior(it can be disabled with eg AsNoTracking)
Later you try to create another object with the same primary key value and attach it to the context, but the context already knows about some object with ID 123 (for example) because the first line caused it to have been downloaded/tracked so you get an error when you try and associate another - if EF allowed both into its tracking memory it wouldn't know which one was the true authority of record that should be saved back to the db
Your interim query doesn't cause the problem, I believe, because it doesn't trigger the download of an entire entity, seeing as it just retrieves an ID
If you're trying to implement insert-if-not-exists style behavior, you should attempt to download an entity with ID x using some XOrDefault or Find, and if it results in null/default then create and add a new entity (you don't need to attach). In essence, ditch the first line, just do the ID check and if the returned ID is default, do a context.Places.Add(new Place{...}).
If you're looking for upsert, it's probably easiest to download the whole entity and then inspect if it was default or not; if it is, then make a new one otherwise edit the downloaded one.
If you're trying for "update without download" then skip the querying part entirely and attach an entity you declare as modified.
If you're after some hybrid upsets without download, I think you'll struggle, because you have to at least quiz the db as to whether it knows of an entity before you decide what to do.. or you run a raw MERGE
That entire loop makes no sense. You repeat the same twice. And as soon as you select one of the items, EF marks it as a tracked. And you can't update using another item, before the first one will be untracked or you can use the tracked item.
Try this code
foreach (Place item in ListOfPlaces)
{
var placesList = context.Places.Where(x => x.PlaceNumberID == item.PlaceNumberID).ToList();
if(placesList!=null && placesList.Count ==1)
{
var existedPlace = placesList.FirstOrDefault();
context.Entry(existedPlace).CurrentValues.SetValues(item);
}
// and maybe this
else context.Places.Add(item)
}
context.SaveChanges();
UPDATE
Thanks to #CaiusJard for a hint, in this case it is more efficient to use SingleOrDefault instead of ToList
.....
var existedPlace = context.Places.Where(x => x.PlaceNumberID == item.PlaceNumberID).SingleOrDefault();
if(existedPlace!=null)
{
context.Entry(existedPlace).CurrentValues.SetValues(item);
}
.....

Update record if it exists

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.

EF dbcontext.Entry in ASP.NET app

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.

Does Entity Framework (5) perform UPDATE on entities that have not been edited actually

Recently I've talked to a Java programmer who told me that Hibernate is smart enough to detect if an entity has any changes made to it and even if you call an UPDATE for entity if it has not been modified then no SQL UPDATE command will be executed.
I am not working with large volumes of data but still I'm curious if the same applies for Entity Framework 5. In my current project I use Repositories so the Update method looks like this:
public virtual void Update(TEntity entity)
{
dbSet.Attach(entity);
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
}
this combined with UnitOfWork let's say I've just submitted to the controller a List of type News where News is an entity. So if in my Action I do something like this :
public ActionResult Edit(List<News> model)
{
foreach (News item in model)
{
unitOfWork.NewsRepository.Update(item);
}
}
what will actually happen if I have items that have not been modified? Do I still make a database query to update the record with the same data or the entity framework is smart enough to just skip it and proceed with the next item?
In order to get the change set which will project to actual queries to database you can either query underlying context or the entity itself ( if it is self tracking entity). (See my answer to this question).
From your example it is not clear if you are using POCO or self-tracking entities, but in both those cases you are missing step of figuring out what was changed in the object when you attach it.
In order to do this, EF should either query db for original object and compare with the one attached, or if it is self-tracking entity, EF will query inner entity changeset to do the update.
UPDATE: In order to sum up what #hvd and I said:
Since we are talking about POCO entities here is what happens:
Create new context and load entity.
Changes are made to the object - context still hold the changeset
Dispose context, so changeset is lost
New context is created and entity is attached to context with EntityState set to Modified, so context belives all fields are modified
There are several things you can do
switch to self-tracking entities
serialize-deserialize context so changeset will persist
requery database for original object
keep original object somewhere and update it instead of just attaching changed and setting EntityState to Modified, ie do changes within context according to changed entity.
There is this course on Pluralsight ( and there are two newer versions of it with different client side technology stack) which covers this topics and shows how to do change tracking in case you have to change entities out of context.
Entity Framework tracks changes made to objects while they are attached to a context, and will issue UPDATE statements if you've made changes to the properties, and call SaveChanges, without needing to set State to EntityState.Modified manually.
In your code, you're making the changes when the objects aren't attached to a context. The only way Entity Framework can let this work is by forcing an UPDATE statement that (re-)sets all fields in the database, even those that remain unchanged.
So, yes, in general, the same does apply to Entity Framework, just not how you're using it.

Entity Framework 4 merge data to tracked entity

I'm struggling with Entity Framework code first and merging.
I have an MVC controller with a generic repository. A view model gets posted up and I convert that into the type that EF knows about
var converted = AutoMapper.Mapper.Map<RoutineViewModel, Routine>(result);
_routineRepository.Update(converted);
In the repository I have:
/*
Routines.Attach(item);
ChangeTracker.Entries<Routine>().Single(x => x.Entity.Id == item.Id).State = EntityState.Modified;*/
var match = Routines.Single(x => x.Id == item.Id);
var entity = Entry(match);
entity.CurrentValues.SetValues(item);
I commented out the first bit because it was throwing an error about already tracking the entity even though a check like this:
if (ChangeTracker.Entries<Routine>().Count(x => x.Entity.Id == item.Id) != 0)
returned false
The problem I'm having is that the Routine object has an ICollection property of Steps. When I set the values of the tracked entity to match that of the poco the ICollection changes aren't propagated down. Looking around this site there looks to be a few nasty looking recursive calls. Is this really how it works or am I missing something?
Is there any easy way to say, here is the source object (untracked), copy everything about it into the tracked object?
Just to be clear I don't think that getting the object first and updating the properties on that should be done outside of the repository. That seems to not only force you to pass your data models across domain boundaries but seems like instead of an equivalent SQL like statement being (update x,y where id = 1), to (insert into temp table where id = 1, for reach row in temp table, update x..... now for each row in table update table x = tempx where id = 1)
Edit --
So the problem is with the setValues not being a recursive call. The routine object has 2 simple properties (id and name) and one complex (ICollection ). If the item coming in has the name changed and some steps changed, setValues picks up the name change but doesn't apply to the children. Is there some other way to do this? It seems a little creaky to me that I have to hand roll this functionality
From what i can tell you are creating your entity, populating properties and then attaching it to the DB. This is kinda the wrong way round with EF.
If you want to attach an object which is already in the DB but isnt being tracked, you can use attach but only changes made after the attach call are recorded to be committed to the DB. If you want to use attach make sure you make your changes after calling that method.
In addition EF only allows you to attach an object which is not currently in the object graph. So if you try to attach the same object twice (or one with the same key) you will be given an error such as the one you are seeing.

Categories

Resources