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);
}
.....
Related
I´m trying to update a list of objects, but when I run context.SaveChanges(), I get an error
Cannot insert explicit value for identity column in table 'TABLE' when IDENTITY_INSERT is set to OFF
I know when the Identity key is generated you cannot register a new entry with this KEY but I'm trying to update my registry not insert a new one and this exception appears. I don't know why this happens now.
My code:
foreach (int i in deletes)
{
var p = context.Item.FirstOrDefault(g => g.participant_id == i);
if (p != null)
{
p.group_id = 0;
context.Entry(p).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
context.SaveChanges();
}
}
group_id is not my identity column, it is just another column I added:
ALTER TABLE Items
ADD group_id INTEGER DEFAULT(0);
The error specifically is the result of a configuration mismatch somewhere in your entity definitions where a table is set to use an Identity PK but EF is not configured for an identity column and the DbContext is attempting to insert a row into that table. This is not necessarily the table you think the DbContext is attempting to update. Chances are somewhere prior to SaveChanges being called there has been an entity incorrectly created or associated and treated as a new entity.
Try this, provided your code is roughly identical to your example:
using (var tempContext = new YourAppDbContext())
{
foreach (int i in deletes)
{
var p = tempContext.Item.FirstOrDefault(g => g.participant_id == i);
if (p != null)
p.group_id = 0;
}
tempContext.SaveChanges();
}
Here we isolate this operation to a clean, temporary DbContext instance to ensure there are no polluting tracked changes. This is not a "fix" for the issue, just to identify whether the context was polluted. You can inspect context pending changes to see what it might be tracking. (ChangeTracker) Your issue may also stem from setting the entire entity to Modified state and a misconfigured related entity definition. (unlikely if the entity and associated were read from this DbContext instance) When updating an entity, don't set the EntityState to Modified. This sets EF up to effectively update all columns on that table. In the above example if we just update 1 column, EF will generate an Update statement for just that single column, and will only generate an update statement if that value actually changes.
If the above code works without an issue, then you have rogue tracked changes polluting your DbContext. This either means you have something else going on before this code is called that you need to fix, or your DbContext lifetime scope is far longer than it should be. (or both)
The problem with your example is that we cannot see the scope that your context is covering. Whenever you make any kind of change to a DbContext, those changes are tracked by default by that DbContext. If you have code that is working with detached entities anywhere that suddenly get re-associated with a DbContext via association with an Added entity and treated as new entities themselves, then calling SaveChanges() when you expect to update a single row all the sudden results in strange unrelated exceptions because the DbContext wants to persist queued up changes.
A typical culprit to look out for with issues like this would be first to check over all of your entity definitions for tables using Identity PKs and ensure those entities are configured with DatabaseGeneratedOption.Identity. This alone would prevent the exception, but you'd probably find that EF would be inserting new, duplicate rows somewhere with new PKs. The culprit behind that is usually detached entities being re-associated to an entity.
Given a Order entity associated to a Product. We want to create an order so since we already loaded the Products collection to select and associated to the new Order, we set that reference client side and send the Order to the server. We assume that since we loaded the Products and it's children from a DbContext on a GET, that when we call POST and send that parent back to the server, all should be good.
public void CreateOrder(Order order)
{
context.Orders.Add(order);
context.SaveChanges();
}
order had a Product reference with ProductId = 22 since we selected product #22 for the order. However, the DbContext instance when we call CreateOrder doesn't know Product ID 22 represents a known entity, it isn't tracking Product ID 22, so it gets treated as a new entity. If the Product entity ID isn't configured as an Identity column but is in the DB, you get the error you saw. If it is configured as an Identity column then you'd find the Order saves successfully, but is pointing at a new, duplicate Product with an ID of 56 or whatever the next available ProductId is.
Passing detached entities is a big cause of headaches like this. The safe way to do something like the CreateOrder would be:
public void CreateOrder(Order order)
{
// TODO: Obviously add null checks, validations, etc.
var product = context.Products.Single(x => x.ProductId == order.Product.ProductId);
order.Product = product; // replace with the product tracked by this DbContext.
context.Orders.Add(order);
context.SaveChanges();
}
All associations on a detached entity need to be checked.
Devs don't like the hit to the database so they can be tempted to merely attach the Product:
public void CreateOrder(Order order)
{
// TODO: Obviously add null checks, validations, etc.
context.Products.Attach(order.Product);
context.Orders.Add(order);
context.SaveChanges();
}
.. and in most cases that will appear to work. Until it doesn't. Attach will throw an exception the moment you attempt to attach an entity with an ID that the DbContext is already tracking. If any code prior had loaded or attached an instance of that Product, then the call will fail. This can result in random-like exceptions appearing at runtime. The safe way to attach the entity would be:
public void CreateOrder(Order order)
{
// TODO: Obviously add null checks, validations, etc.
var existingProduct = context.Product.Local.SingleOrDefault(x => x.ProductId == order.Product.ProductId);
if (existingProduct == null)
context.Products.Attach(order.Product);
else
order.Product = existingProduct;
context.Orders.Add(order);
context.SaveChanges();
}
This checks the local tracking cache to see if the DbContext is tracking that product. (Does not hit DB) If it isn't then we can safely attach it. If it is, we replace the product reference on the order with the tracked one.
This would have to be done for every reference. Any that are missed would result in potential errors or duplicate rows.
Try
var p = context.DbSet<Participant>().Where(g => g.participant_id == i).FirstOrDefault();
And did you check your Participant class in EF? It still can be a foreign key there.
I'm running into a problem with inserting OR updating roughly 950 entities.
var coins = JsonConvert.DeserializeObject<List<Currency>>(json);
var sw = new Stopwatch();
sw.Start();
using (var ctx = CryptoContext.Get)
{
var existingCoins = ctx.Coins.ToList();
foreach (var coin in coins)
{
var existing = existingCoins.FirstOrDefault(c => c.CMC_Id == coin.CMC_Id);
if (existing != null)
{
ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Modified;
} else
{
ctx.Entry<Currency>(coin).State = Microsoft.EntityFrameworkCore.EntityState.Added;
}
}
ctx.SaveChanges();
var el = sw.ElapsedMilliseconds;
}
The code runs in the background of my netcoreapp1.1, with SQLite, and retrieves a list of currencies. This is done every 5 minutes with FluentScheduler. Because they're not entirely large objects I do all comparisons in memory, and try to add or update each one. My entity has a database-given ID of Id, and the API I'm retrieving from guarantees that CMC_Id is unique.
The initial insertion works fine. I get an error on the second "Update". I believe what's happening is that I'm tracking multiple entities as modified that each have an Id of 0
I was trying to follow this: https://msdn.microsoft.com/en-us/library/jj592676(v=vs.113).aspx
And the error I get is: "The instance of entity type 'Currency' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context."
I am unsure how to proceed with updating each row.
Issue here multiple entities with same key are asked to be tracked.
When you set EntityEntry.State to something then EF Core will start tracking the entity in the specific state. Since in your code, you are querying the database to find out existing entity, EF Core will start tracking the entity with given key therefore it throws above exception while setting the EntityEntry.State because there is already entity with same key being tracked.
More precisely you are trying to AddOrUpdate. There are multiple ways to achieve the behavior. Which one is the best depends on if you are adding one entity without relation or a complex graph.
The simplest method would be to just check existence instead of tracking the entity from database. Options for that would be to use AsNoTracking in your query so that EF does not start tracking it. Even more optimized way would be to just get count from database. If you are querying on PK property then count will be either 0 (non-existent) or 1 (existing entity). If it does not exist then you call Add otherwise Update.
var updatedBlog = new Blog { Id = 1, Title = "Updated" };
var exist = db.Blogs.Count(b => b.Id == updatedBlog.Id) != 0;
if (exist)
{
db.Update(updatedBlog);
}
else
{
db.Add(updatedBlog);
}
db.SaveChanges();
Since Add or Update methods start tracking whole graph, if your graph is in one consistent state, (all entities are new or all are being modified) then it would work just fine.
If your graph is somewhat inconsistent that state of each node in graph can be different (e.g. Updating a blog but it has new posts). Then you should use EntityEntry.State on individual entity. This makes sure that state is applied to only given entity and no other related entity in graph. Though you need to do above kind of check for each node in the graph. Another alternative is to use Attach method to attach whole graph in Unchanged state and then set state for individual node.
If you are having auto-generated Key values then probably you will have PK value set only when it is update else it would be CLR default. For single entity without relations, you can make that check yourself instead of querying database like above code and make decision. For graphs, you can use
db.ChangeTracker.TrackGraph(updatedBlog, n => n.Entry.State = n.Entry.IsKeySet ? EntityState.Modified : EntityState.Added);
This will set state of each node based on PK value being set or not.
Hope this helps :)
In our Entity Framework 6 seed method, we're setting user preferences to default values. We had a lot of issues getting stuff to work correctly, so we started printing debugging statements to a file. Now, however, if we remove the debugging lines, we get an exception.
Here is the code:
// Get the preferences.
Preferences prefs = context.Preferences.FirstOrDefault(x => x.UserId == user.Id);
using (StreamWriter write = new StreamWriter(#"C:\myFile.txt"))
{
//foreach (PropertyInfo prop in prefs.GetType().GetProperties())
// write.WriteLine($"{prop.Name} = {prop.GetValue(prefs)}");
prefs.ColumnIds = defaultColumnIds;
prefs.Columns = defaultColumns;
prefs.CategoriesOnYAxis = true;
prefs.TabHorizontal = true;
prefs.OnlyAssignedToUser = true;
context.SaveChanges();
}
If we uncomment that For loop, then the seed method runs fine. With the for loop commented out, we get the following exception:
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See
the InnerException for details.
In this example, the User object is 1-1 to the Preferences object, with Preferences having a foreign key to the User table.
We can even take out the stream writer and loop through the properties to the console, and as long as that loop is there the seed method runs correctly. As soon as it's gone, we get the error.
I have a guess what might be happening here. I think this is your problem:
prefs.ColumnIds = defaultColumnIds;
prefs.Columns = defaultColumns;
I assume these are backed by the same field in your database? So you're setting both the Column entities as well as the ColumnId primary keys? You don't need to set both (though it should work).
I bet if you remove the assignment to prefs.Columns (and leave out the debug code), your code will start to work. The issue is defaultColumns. What's in there? Column entities - but are those entities attached to the current DbContext? (Your code doesn't show how they come into being)
When you fetch your prefs with context.Preferences.FirstOrDefault(x => x.UserId == user.Id);, you're asking EF for a Preference entity, but by default EF won't eager-load navigation properties (like those Column entities). Conversely, if you have a bunch of Column entities but they haven't been explicitly attached to (or fetched using) the current DbContext, EF will think those Columns are new, and that relationship might not be mapped in a way that EF can insert new Columns (which you don't want to happen, anyway).
When your debug code runs, prefs.GetType().GetProperties() is enumerating all the properties in the Preference, which I think EF is then lazy loading. When you remove the loop, it no longer enumerates then, so all the Preference's navigation properties didn't get pre-loaded.
There are a few ways you can go about this:
If you're absolutely certain all those defaultColumnIds are already in the database when you run the Seed method, then set only ColumnIds and call it a day. It will match the entities in the database when it executes your query, and as long as the expected foreign keys are there you're fine.
Fetch the defaultColumns from the context as soon as you open the context, or, if you defined them in code, attach them to the ChangeTracker before you call SaveChanges.
I have a class Customer. I am trying to clone a Customer object and modify it, then I want those modifications to be reflected in the context (database as well). I am using following code to do that.
Customer old = context.Customers.Where(c=>c.CustomerID ==1 ).SingleOrDefault();
Customer m = CustomExtensions.ShallowCopyEntity<Customer>(old);
m.Name = "Modified";
m.MobileNo = "9999999999";
context.Customers.Attach(m);
But its throwing following exception
Attaching an entity of type 'DataBindingSample.Customer'
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 tried changing EntityState to Modified but it didn't work.
Can anyone tell me how to achieve this?
My main goals are
I want to clone (I will use deep clone when necessary) an existing entity
Want to modify the cloned entity (as well as referenced entities - I will use deep clone in this case)
Finally I want to save changes to database
EDIT
As pointed out in this comment i am trying to attach object which aready exists in context. So i can detach it first and then atttach again as shown bellow if attach is compulsory.
Customer old = context.Customers.Where(c=>c.CustomerID ==1 ).SingleOrDefault();
Customer m = CustomExtensions.ShallowCopyEntity<Customer>(old);
m.Name = "Modified789789";
m.MobileNo = "9999999999";
((IObjectContextAdapter)context).ObjectContext.Detach(old);
context.Customers.Attach(m);
context.Entry(m).State = EntityState.Modified;
context.SaveChanges();
Otherwise i can follow 2 options mentioned in this answer.
There are 2 options that I can think of:
Copy the updated values back to the original entity loaded into your DbContext and then save changes.
Updated values of the original entity and then discard them if user canceled the update.
Options 1
Just copy the updated values back to the originally loaded entity. Automapper is your friend in tasks like this. This approach can later be extended to allow user to change a model of your entity and not the data layer object itself (e.g. to expose a limited number of fields that user can edit).
var entity = context.Customers.SingleOrDefault(c => c.CustomerID == 1);
var updatedEntity = CustomExtensions.ShallowCopyEntity<Customer>(old);
updatedEntity.Name = "Modified";
updatedEntity.MobileNo = "9999999999";
entity.Name = updatedEntity.Name;
entity.MobileNo = updatedEntity.MobileNo;
context.SaveChanges();
If you add Automapper nuget, then you mappings (copying) will become much easier:
Mapper.CreateMap<Customer, Customer>();
Mapper.Map(updatedEntity, entity);
And your code will look like:
// Configuring mapping. Needs to be done only once.
Mapper.CreateMap<Customer, Customer>();
var entity = context.Customers.SingleOrDefault(c => c.CustomerID == 1);
// Check if entity is null
var updatedEntity = CustomExtensions.ShallowCopyEntity<Customer>(old);
updatedEntity.Name = "Modified";
updatedEntity.MobileNo = "9999999999";
// Copy the updated values back
Mapper.Map(updatedEntity, entity);
context.SaveChanges();
Options 2
Make changes in the originally loaded entity and discard them if user changed her mind and canceled. See this post and this post on how to do it.
Discarding the whole DbContext might not be a good option in case you still need it (duh).
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.