I have been using Add() and ran into a problem where by a parent entity was being duplicated in the database when Adding a child. Using Attach() solved this but I would like to know why rather than blindly stumbling around.
Well, when you use Attach you tell the context that the entity is already in the database, SaveChanges will have no effect over attached entities. Add, on the other hand, changes the state of the entity in the context (if it's already there) to Added, meaning it will always insert the entity in the database when you call SaveChanges.
That's the difference.
in case of ef-core
Attach is good for cases when you are adding a new entity to the database with navigational properties. Attach only marks newly created items as changed.
Let's say you are adding a new Employee to an Industry. If the industry already exists in the database it must have an ID. and the Employee you are adding is not inserted to the database yet so it does not have an ID yet (I am talking about row IDs here).
So what attach does is since the Industry already has an ID. Attach marks that as Unchanged. And your Employee who doesn't have an ID yet attach marks it as Added.
You can read more about this topic here: https://www.learnentityframeworkcore.com/dbcontext/modifying-data#attach
Related
I have this problem where I have 2 entities connected by foreign key.
AEntity: id, idOfEntityB (foreign key, constraint), fields...
BEntity: id, fields...
I save both of them to the database with SaveChanges(), later when I try to get AEntity's idOfEntityB, I succeed but when I try to get BEntity according to the id I got from AEntity, I get nothing:
context.AEntities.Add(new AEntity {
BEntity = new BEntity { ... }
});
context.SaveChanges();
.
.
.
var id1 = context.AEntities.Select(x => x.idOfEntityB);
var bEntities = context.BEntities.Where(x => id1.Contains(x.id));
bEntities has nothing in it. but the fact I was able to have values in id1 is even more confusing since they have foreign key relations (with constraint) and furthermore, id could not be created if it was not saved to the DB.
Later, when I look in the DB I see both entities as should be.
It happens sometimes and I cant reproduce the problem, I cant give more then this as an example since there's a lot of code, I believe it has something to do with caching, and therefore would like to ask if something like that is possible or not and how.
is there a way entities are saved to the DB while the context (a different one used from the context that saved) does not hold all of them in completion?
This is likely the issue you are encountering if you are relying on seeing changes between state changes between different DbContext instances. When a DbContext has loaded entities, then another DbContext instance makes changes to those records or the records change behind the scenes in the database, that original DbContext will not refresh the entities from the database.
EF does support the ability to reload entities from the database, but when dealing with child collections it gets a bit more complicated to perform a full refresh. You effectively need to tell the DbContext to forget all of the child collections, stop tracking the parent, clear the parent's child collection, then re-attach and reload the child collection. I recently covered this in the answer for this question: Replacing a entity collection in Entity Framework Core causes DbContext to fetch the new values when not saved to db. How to reload the collection?
Ultimately a DbContext lifespan should be kept as short as possible.
This question already has answers here:
Entity Framework: Re-finding objects recently added to context
(7 answers)
Closed 4 years ago.
I have a MVC Action that imports data from a CSV file using EF. When I read a new record from CSV, I need to check if that record already exists including records that I have already imported before Saving, so I can update it instead of creating a new record.
The simple solution is to just SaveChanges() after every dbContext.Entities.Add(newEntity) call but I suspect that is not very efficient. It also means that I cannot rollback (easily) to prior to beginning the import. I.e. if any part of the import fails, I can avoid calling SaveChanges()
My question is: Can I search "Added" entities, then update them, before I call dbContext.SaveChanges() once, at the end of my import routine?
Yes, you can check if each imported entity already exists in the database. Use the same context, and try to find them, one by one, in the database.
If the entity doesn't exists in the database leave it as is (tha is, in the Added state)
If the entity already exists in the database, modify the entity loaded from the DB: it was unchanged, because you had just read it from the DB, but, when you modify it, it will be in the Modified status, so that, when you save changes, it will be updated. But there is still a very important step: the imported entity which you have found in the database must be detached from the context so that it's ignored when you call SaveChanges().
If you don't understand what is to detach an entity, please, read this intereeting article: Entity Framework Add Remove Attach and Detach.
This solve your problem for saving all changes in one single operation.
You can search the "local" dataset before it is committed (i.e. before a call to SaveChanges() is made). Please note that I am using EF5.
As an example, the following search of the database returns null.
Entity myEntity = new Entity() { Name = "search text" };
db.Entities.Add(myEntity);
existingEntity = db.Entities.(e => e.Name == myEntity.Name).SingleOrDefault();
But the following subsequent call will find the Added entity before changes are saved allowing me to make further changes.
existingEntity = db.Set<Entity>().Local
.Entities.(e => e.Name == myEntity.Name).SingleOrDefault();
I'm having a small problem with ASP.NET MVC and Entity Framework 4. I have an entity called "UF" and another one called "Pais", and they have this relation:
UF [* ... 0..1] Pais
I can access the Pais object directly from UF using a navigation property:
UF.Pais.Whatever = "foobar";
Currently I have a View which inserts a new item into the database, and it has an editor for "Pais.Codigo" ("Codigo" is the primary key for Pais). So when I insert a new UF, the framework creates an instance of class UF with a reference to an instance of class Pais. Then this is done:
if (ModelState.IsValid)
{
db.UFs.AddObject(uf);
db.SaveChanges();
return View();
}
The problem is that the EF is inserting a new Pais into the database, so it basically ignores the existing one.
For example, if let's say my object UF has a Pais with an ID of 1. The current value of uf.Pais.Codigo is 1. Other attributes, like the description, is currently null. When I execute the SaveChanges, both "uf" and "uf.Pais" are with the state of Added. The correct state for "uf.Pais" should be Unchanged, since it already exists on the database.
My question is: there's some way of changing the default relationship EntityState for Unchanged? The following code solves the problem, but adding it to each function with adds a new entry to the database (and for each FK) is overkill!
db.ObjectStateManager.ChangeObjectState(uf.Pais, EntityState.Unchanged);
That's it. I'm not sure if I was clear enough. Feel free to ask more information if needed. And sorry for any english mistakes!
Thanks,
Ricardo
PS: "Pais" stands for Country and "UF" for State.
My question is: there's some way of changing the default relationship
EntityState for Unchanged?
Yes by calling Attach instead of Unchanged.
The following code solves the problem, but adding it to each function
with adds a new entry to the database (and for each FK) is overkill!
No it is not overkill, it is a solution because either Attach or AddObject will always make the operation for all entities and associations in entity graph. That means that calling AddObject will make everything what context is not aware of yet as Added and Attach will make everything what context is not aware of as Unchanged (so you will in turn have to set each modified or inserted entity to its correct state). That is how EF works if you are using detached entities.
Another solution for the problem is making the connection after the UF is added:
// Here UF.Pais is null
db.UFs.AddObject(uf);
// Create dummy Pais
var pais = new Pais { Id = "Codigo" };
// Make context aware of Pais
db.Pais.Attach(pais);
// Now make the relation
uf.Pais = pais;
db.SaveChanges();
If you are working with detached entities you are always responsible for setting the correct state for each entity (and independent association). So you will either use attached entities to let EF make the magic for you (as shown in the example) or you will use the approach you dislike. In more complex scenarios you can find out that best approach is to load entity graph again and merge incoming changes into the attached graph.
I have a newly entity framework 4 entity that references another entity, a simple user record. In my input data, I may have multiple instances of user records that are the same person. That person may not exist in the database; if not, I need to add them by creating a new user entity and adding them.
So when I create the main entity that references the user, I do a quick lookup for that user in the database. If that person exists, great, I got the reference to set. If not, I create a new user record and set the reference in the main entity to the newly created user entity. As I process more records, the same user may pop up, so when I do the query in the "create new user" function, I was assuming that if a newly created but not yet saved entity existed, the query would find that record, but apparently not (ok maybe dumb assumption)... so end result is that a duplicate record is created for that user, linked up to the new main entity, and when it is all saved, I get a "SQL Error 2601: Cannot insert duplicate key row in object 'dbo.user_table' with unique index 'IX_user_table'." error.
So I'm a bit baffled how to resolve this situation. The problem is that I can't call SaveChanges() on the context right after adding a new user entity because that causes the entire entity graph to be saved, and the main entity that is referencing the user records is not yet complete, it is not ready to save, so I can't save the whole mess every time I add a new user. I need to construct all the entities then save it all in one shot.
Do I need to do two queries every time I look up a user, one on the database and one something like:
var unsavedVisitors = from c in
dataContext.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Added)
where c.Entity is my_table
select c.Entity;
I really haven't fiddled with the internals of the entity state manager so I'm not sure what to do here - how can I do a query to find an existing yet unsaved user entity that I could then just link up to the new main entities I'm creating as I loop through my input data? It looks like I can just cast c.Entity above to my_table type and wala, I've got my reference. I wish there was just some kind of "include unsaved entities in my query" trick or something, to avoid walking all the unsaved entities with so many queries - is there?
No, there is no "include unsaved entities in my query" trick. It's the right way to check for entries in the ObjectStateManager. Alternatively you could manage the new added users in some custom dictionary but that's basically the same like leveraging the ObjectStateManager. Last option would be to clean up somehow your collection of users from duplicate objects in the first place.
I use LINQ-to-SQL to load data from a database that has two tables in a one-to-many relationship (one Recipe has many Ingredients).
I load a Recipe and LINQ retrieves Ingredient objects into an EntitySet that is binded into a ListBox.
If I want to delete some Ingredients off a Recipe, I get a "An attempt was made to remove a relationship between a Recipe and a Ingredient. However, one of the relationship's foreign keys (Ingredient.RecipeID) cannot be set to null.
I SOLVED this problem using the well known solution by adding 'DeleteOnNull="true"' to the DBML file. But adding this setting only removes the problem when we are deleting Ingredient objects that were retrieved from the DB.
The problem is with the Ingredient objects that were created in code (added to a Recipe) and added to the EntitySet collection of Ingredients and then deleted BEFORE SubmitUpdates is called. Then, the same exception happens again. This usually happens on a new, unsaved recipe when user is adding ingredients to it, makes a mistake and erases an ingredient off a recipe. I added the DeleteOnNull to both 'Association Name="Recipe_Ingredient"' lines in DBML.
How am I supposed to remove such objects? The only solution I see at the moment is that I would load the ingredients into a collection not under the DataContext and then when saving, delete all ingredients off a recipe and add then again from that cache..
try
{
// Needed for existing records, but will fail for new records
yourLINQDataContext.Ingredients.DeleteOnSubmit(ingredient);
}
catch (Exception)
{
// Swallow
}
yourRecipeObject.Ingredients.Remove(ingredient);
It seems that you're looking for something that I was looking for myself just a few days back when I asked "How do I design backing data types for a databound WPF dialog with Ok/Cancel buttons?".
The answer is an intriguing post from Paul Stovell describing a sample IEditable adapter for Linq to Sql. This will let you create your desired "Apply/Cancel" semantics in a generalized manner without completely dissociating yourself from the underlying ORm-generated classes through a full custom-written layer.
It's a pretty slick trick, overall, that will essentially let you sidestep the problems you're fighting right now. :)
On a different note, I'm curious as to why your recipe to ingredient relationship is 1:n instead of m:n. Is it for simplicity's sake? I use garlic in a lot of recipes. :)
// Create new entities
Cart c = new Cart();
CartEntry ce = new CartEntry();
ce.Cart = c;
// Delete the entry
c.CartEntries.Remove(ce);
dc.Cartentries.Attach(ce);
dc.CartEntries.DeleteOnSubmit(ce);
// Insert the cart into database
dc.Carts.InsertOnSubmit(c);
dc.SubmitChanges();
Explaination of the issue: Both entities, c and ce, are not related to a data context - they are not being tracked. EntitySet.Remove() (first delete line) only removes the relation between c and ce. While c can exist without associated cart entries, ce can't exist without an assiciated cart because of a foreign key constraint. When submitting changes to the database, the disconnected ce is dealt with as well, causing a constraint violation and the exception.
In order to get rid of that untracked and disconnected cart entry you need to attach it to your data context (causing it to be tracked) and then mark it for delete on submit. The moment you submit your changes the cart entry will be deleted properly and not cause the exception.
For more details on that issue check this out:
http://msdn.microsoft.com/en-us/library/bb546187%28v=VS.100%29.aspx
you need to decouple the save code from the events in your GUI, it seems like you're a little to eager to save things to the db before the dust has settled and you're queuing and removing things from the db that never got there in the first place, it would be best if you could identify a point when the user will "commit" their changes, and at that moment, process the full condition of the GUI - this will save you a bunch of spaghetti code.
I would also be curious to know if your entities have autonumber IDs or if you're using some other ID mechanism. You're probably sending DELETEs to the database for the as-yet-uncommitted Ingredient records, if those include NULL IDs, I think the linq could get nasty.
Have you hooked up a textwriter to your DataContext.Log to see what sorts of SQL is generated just before you get your exeception?
Thank you for your answer, I will examine the posts and see what I can do. I must say I'm surprised to even see this problem occuring, it seems quite natural to me that one could add records to the LINQ-provided "cache" of data, then decide to erase some of them and then commit. Change tracking should be able to handle that. I just starting with LINQ so I might be doing a stupid mistake somewhere in the code (wouldn't be the first).
On the other note: You are quite correct that garlic can belong to many recipes (not my coctail recipes thought!). I actually model that with an Article object/table. But for a recipe, you need quantities. So in my model, you have a Recipe that has 1:n Ingredients, each of them having a Quantity, a 1:1 link to an Article (which has a Name, an AlcoholContent and some data to establish an interchangeability hierarchy) and a 1:1 link to an Unit (for the quantity to make sense).
So in a sense, Ingredient table makes a M:N relationship between Recipe and Article, and at the same time adding some additional information to each individual linked pair.
I had exactly the same problem. I had a parent / child hierarchy, and when adding and removing the child entity without saving to the database I received the "An attempt was made to remove a relationship" exception.
I discovered that this problem only arose when I set an object style property of the child to another linq-sql entity before saving. eg
1. This creates the error
RetailAccountCustomerCard racc = new RetailAccountCustomerCard();
Card addedCard = _idc.Cards.Where(c => c.CardId == card.CardId).ToList().First();
racc.Card = addedCard;
this.CurrentCustomer.RetailAccountCardsBindingList.Add(racc);
// Some code triggered by the user before saving to the db
CurrentCustomer.RetailAccountCardsBindingList.Remove(racc);
2. This doesn't create the error
RetailAccountCustomerCard racc = new RetailAccountCustomerCard();
racc.CardId = card.CardId; // note that I have set the Id property not the object
this.CurrentCustomer.RetailAccountCardsBindingList.Add(racc);
// Some code triggered by the user before saving to the db
CurrentCustomer.RetailAccountCardsBindingList.Remove(racc);
Strangely enough, the error that arises in 1. specifies the problem is to do with the relationship is on the RetailAccountCustomerId property of RetailAccountCustomerCard. IT HAS NOTHING to do with the Card object I added. It seems that simply setting any object property of the new entity triggers the problem.
NB. Example 1 works fine in terms of saving, it only causes a problem if the the new entity is deleted before saving.
I am running into a similar issue, as a workaround, I need to call DataContext.GetChanges(), then everything seems to have caught on again :)
Another problem you could have it that you are binding to columns and not entity properties, and hence the referential collections are not updated (already stated by someone else, but enforcing the fact).