This error has been asked about A LOT. But none of the cases I think apply to my particular case, or at least not quite.
I am creating a new entity with 2 navigation properties that are collections. Both the entity and the navigation properties are new entities that do not exist in the database. My problem is that whenever I try to attach the entity to the context, if either of the collections has more than 1 element I get to aforementioned exception.
I am getting this error on the Attach() instruction in the following code:
using (var context = new NSModel())
{
context.Notifications.Attach(e);
context.ObjectStateManager.ChangeObjectState(e,
StateHelpers.GetEquivalentEntityState(e.State));
foreach (NavigationProperty1 np in e.NavigationProperty1s)
context.ObjectStateManager.ChangeObjectState(np,
StateHelpers.GetEquivalentEntityState(np.State));
foreach (NavigationProperty2 np in e.NavigationProperty2s)
context.ObjectStateManager.ChangeObjectState(np,
StateHelpers.GetEquivalentEntityState(np.State));
context.SaveChanges();
return e;
}
The code is for a web site so the entities are stateless and the context is created and disposed of with every call...
Any ideas?
Both the entity and the navigation properties are new entities that do
not exist in the database.
Then the question is: Why do you use Attach? By using Attach you tell EF that the entites are in the database and EF will consider the values of the primary key properties as the PK column values in the database. Because those properties must be unique EF will complain as soon as you have two entites with the same key values. It's well possible that you have this situation when you have autogenerated identities as key properties and don't set the values when you create the entities.
Simple example:
public class Parent
{
public Parent
{
Children = new List<Child>();
}
public int Id { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child
{
public int Id { get; set; }
}
This code ...
using (var ctx = new MyContext())
{
var parent = new Parent();
var child1 = new Child(); // <- Id is 0
var child2 = new Child(); // <- Id is 0 as well
parent.Children.Add(child1);
parent.Children.Add(child2);
ctx.Parents.Attach(parent); // <- exception here
//...
}
... will throw your exception because EF tries to attach two different child instances with the same key 0. When the second child child2 will get attached it says: "An object (namely child1) with the same key (namely 0) already exists in the ObjectStateManager".
If you want to add the whole object graph as new entities to the database you could just call context.Notifications.AddObject(e); instead of Attach.
Related
I have a simple design of 3 classes in C# entity framework:
class Foo{ int id, string name}
class ParentFoo{ int id, Foo foo}
class GrandparentFoo{int id, ParentFoo parentFoo}
When I try to save records in GrandparentFoo using DataContext.SaveChanges(), the ids of ParentFoo and Foo are incremented which is not expected.
function AddGrandparent(int g_id, ParentFoo pf){
using (var dc = dcHelper_.CreateDataContext())
{
result = dc.GrandparentFoos.Add(new GrandparentFoo()
{
id = g_id,
parentFoo = pf
});
dc.SaveChanges(); }}
Can anyone please help me out with this issue?
ParentFoo etc. are not associated to your context so that context will treat them as new entities. Avoid passing entities around outside of the context they are loaded to avoid issues like this. Instead, if you load the Parent/child data needed with IDs into POCO view models then retrieve references as needed, you can avoid problems with unassociated entities.
Using your code:
void AddGrandparent(int id, ParentFoo pf)
{
using (var dc = dcHelper_.CreateDataContext())
{
var parent = dc.ParentFoos.Find(pf.Id);
dc.GrandparentFoos.Add(new GrandparentFoo
{
Id = id,
ParentFoo = parent
});
dc.SaveChanges();
}
}
If GrandParentFoo has identity set, you can do away with setting IDs, EF will ignore it when added to the context. If parent contains a reference to grandparent, then you may need to set that reference in ParentFoo as well.
var grandParent = dc.GrandparentFoos.Add(new GrandparentFoo
{
Id = id,
ParentFoo = parent
});
parent.GrandparentFoo = grandParent;
Edit:
If GrandParent has a collection of Parent and you want to create a Grandparent and associate it to one existing Parent:
First, be sure that the Parents collection in GrandParent is initialized to a new List/HashSet
public virtual ICollection<ParentFoo> ParentFoos {get; set;} = new List<Parent>();
then
using (var dc = dcHelper_.CreateDataContext())
{
var parent = dc.ParentFoos.Find(pf.Id);
var grandParent = dc.GrandparentFoos.Add(new GrandparentFoo
{
Id = id
});
grandParent.ParentFoos.Add(parent);
parent.GrandparentFoo = grandParent;
}
The children of ParentFoo don't matter given the parent and it's associated children already exist in the data.
I know there are several questions posed about this very same thing but none of which seems to help me. I'm trying to do a .RemoveRange() and every question I've been seeing has to do with edits and adds.
Here's the relevant bits of the method in which the exception is getting thrown:
public bool UpdateFileboundApplications(IList<IFileboundApplicationDm> fileboundApplications)
{
// get all mappings in the DB that match the incoming fileboundApplications
var incomingFbAppsAlreadyExistingInDb =
fileboundApplications.Where(app => app.Id == Db.inf_DMS_FBApplicationProjectMapping.SingleOrDefault(a => a.ApplicationId == app.Id)?.ApplicationId
&& app.FileboundProject != null).ToList();
// in the case that application/project mappings include filebound applications with no project mapping,
// pass the collection to a method which will handle removal of these records.
var fbAppMappingsWithoutNulls = RemoveNullFileboundApplicationMappings(incomingFbAppsAlreadyExistingInDb, fileboundApplications);
var fbAppMappingsAppIdsAndProjectIds = fbAppMappingsWithoutNulls.Select(x => new { appId = x.Id, projectId = x.FileboundProject.Id}).ToList();
var dbRecords = Db.inf_DMS_FBApplicationProjectMapping.Select(y => new { appId = y.ApplicationId, projectId = y.ProjectID}).ToList();
var fbApplicationDifferences =
dbRecords.FindDifferences(fbAppMappingsAppIdsAndProjectIds,
s => new Tuple<int, int>(s.appId, s.projectId),
d => new Tuple<int, int>(d.appId, d.projectId));
if (fbApplicationDifferences.ExistOnlyInSource.Any())
{
// items to remove from the table, as these apps are now assigned to a different project.
var allAppsToRemove = fbApplicationDifferences.ExistOnlyInSource.Select(x => new inf_DMS_FBApplicationProjectMapping
{
ApplicationId = x.appId,
ProjectID = x.projectId,
MapId = Db.inf_DMS_FBApplicationProjectMapping.Single(m => m.ApplicationId == x.appId).MapId
}).ToList();
Db.inf_DMS_FBApplicationProjectMapping.RemoveRange(allAppsToRemove);
}
Db.SaveChanges();
return true;
}
FWIW, I'll include the code for the RemoveNullFileboundApplicationMappings as well:
private IEnumerable<IFileboundApplicationDm> RemoveNullFileboundApplicationMappings(IEnumerable<IFileboundApplicationDm> incomingFbAppsAlreadyExistingInDb,
IEnumerable<IFileboundApplicationDm> fileboundApplications)
{
// hold a collection of incoming fileboundApplication IDs for apps that have no associated fileboundProject
var appIdsWithNoFbProject = fileboundApplications.Except(incomingFbAppsAlreadyExistingInDb)
.Select(app => app.Id);
// get records in the table that now need to be removed
var dbRecordsWithMatchingIds = Db.inf_DMS_FBApplicationProjectMapping.Where(mapping => appIdsWithNoFbProject.Contains(mapping.ApplicationId));
if (dbRecordsWithMatchingIds.Any())
{
// remove records for apps that no will no longer have an associated Filebound project
Db.inf_DMS_FBApplicationProjectMapping.RemoveRange(dbRecordsWithMatchingIds);
Db.SaveChanges();
}
return fileboundApplications.Where(app => app.FileboundProject != null);
}
Finally, here's the inf_DMS_FBApplicationProjectMapping class:
public partial class inf_DMS_FBApplicationProjectMapping
{
public int MapId { get; set; } // <-- this is the PK
public int ApplicationId { get; set; }
public int ProjectID { get; set; }
public Nullable<int> Modified_By { get; set; }
public Nullable<System.DateTime> Modified_On { get; set; }
public virtual glb_Applications glb_Applications { get; set; }
}
}
Exception reads as follows:
{"Attaching an entity of type 'xxxx' 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 don't quite understand how I need to be using Db.inf_.....Add(), as I'm not intending to add records to the table; I need to be removing records.
I don't understand what this "attaching to context" is all about and what that really means.
I really appreciate any insight the community may have on this. It's been a struggle trying to find a way to solve this. Thanks!
I guess the problem is in the new that you use to compose the list you pass as parameter to RemoveRange. As the entities in that list have not been queried directly from your DbSet they have never been attached to your local context and so EF gets confused.
You need to understand the concept of entities attached to the context. Entity Framework keeps track of the changes done to entities you are working with, in order to be able to decide what to do when you do SaveChanges: insert, update, delete. EF is only able to do that if the entities are attached to the context. That means they have a property State with the value Added, Deleted, Modified, Unchanged, etc.
In simple scenarios this is transparent to you, because entities get automatically attached when you do DbSet.Add(entity), DbSet.Find(entityId), or when you get an entity instance as a result of a query, like DbSet.Where(...), DbSet.FirstOrDefault(...), etc. That is why you probably never had to worry about attached entities before in your EF code.
In more complex scenarios like your current one, the entities you are trying to delete have not been instantiated from one of those operations, so they have not been automatically attached to your context. You have to do it explicitly, if you instantiate them with new.
So you should do something like this before the SaveChanges:
foreach(var item in allAppsToRemove)
{
Db.Entry(item).State = EntityState.Deleted;
}
By using the method Entry the entities get attached to the context, and then you explicity set their state as Deleted, to have them deleted when SaveChanges is executed later.
Take a look at this page. Even if it deals mostly with Add and Update cases it contains information relevant to your problem with the Delete. Understanding the concept of entities attached to the local DbContext will help you a lot when programming with EF. There are some cases like this one where you will have trouble if you don't know how attached entities work (you will eventually get to some 'orphaned children' errors also).
Note: in Entity Framework Core (EF7) there is an AttachRange method that can be used before RemoveRange.
With Diana's help, I was able to solve this issue.
The problem was that I was manually flipping the entity state AND calling .RemoveRange(). I only needed to be flipping the entity state. Here's the relevant bits that solved the issue:
...
...
...
if (fbApplicationDifferences.ExistOnlyInSource.Any())
{
// items to remove from the table, as these apps are now assigned to a different project.
var allAppsToRemove = fbApplicationDifferences.ExistOnlyInSource.Select(x => new inf_DMS_FBApplicationProjectMapping
{
ApplicationId = x.appId,
ProjectID = x.projectId,
MapId = Db.inf_DMS_FBApplicationProjectMapping.Single(m => m.ApplicationId == x.appId).MapId
}).ToList();
foreach (var app in allAppsToRemove)
{
var item = Db.inf_DMS_FBApplicationProjectMapping.Find(app.MapId);
Db.Entry(item).State = EntityState.Deleted;
}
//Db.inf_DMS_FBApplicationProjectMapping.RemoveRange(allAppsToRemove); <-- these items are already "flagged for deletion" with .State property change a few lines above.
}
Just change your code after SaveChanges methot change EntityState Detached
I have created a simple one to zero/one relationship inside of code first. The code below works in that I can have a Person instance and optionally have an Account and its modeled fine in the database.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Account Account { get; set; }
}
public class Account
{
public int Id { get; set; }
public string Location { get; set; }
public virtual Person Owner { get; set; }
}
//Mapping
modelBuilder.Entity<Person>().HasOptional(x => x.Account).WithRequired(x => x.Owner);
What I would like to do is to be able to delete the optional child from the parent. I would expect this to work.
using (Context ctx = new Context())
{
var personToDeleteFrom = ctx.Persons.Single(x => x.Id == <personid>);
personToDeleteFrom.Account = null;
ctx.SaveChanges();
}
However, the child object of the relationship is simply left in the database. Is there a way to make this work? If not, what is the best practice for handling this type of relationship?
You aren't actually removing the child data just by setting the navigation property equal to null. You need to actually delete the data to get it to go away.
Just change the setting of the null to a Remove on the Accounts collection instead.
using (Context ctx = new Context())
{
var personToDeleteFrom = ctx.Persons.Single(x => x.Id == <personid>);
ctx.Accounts.Remove(personToDeleteFrom.Account);
ctx.SaveChanges();
}
This will remove the Account.
This is due to the behavior of how Entity Framework handles 1:1 relationships. EF doesn't actually add foreign key fields in the database as they are unnecessary. Instead it just maintains the relationship that the primary key for an Account always equals the primary key for the associated Person.
You can see this behavior arise if you attempt to do the following.
using (Context ctx = new Context())
{
var person = ctx.Persons.Single(x => x.Id == <personid>);
person.Account = null;
ctx.SaveChanges();
person.Account = new Account();
ctx.SaveChanges();
}
This will throw a System.Date.Core.Entity.UpdateException as it attempts to add an entry to the Accounts table with a primary key set to <personid> when one already exists.
As such, nulling out the navigation property doesn't actually do anything. The relationship is maintained by keeping the primary keys of each entity in sync. To actually remove the Account you need to delete it from the table.
I have a pretty deep object hierarchy in my application, and I am having trouble saving the entities. Depending on the order I do things, I either one of two errors:
[OptimisticConcurrencyException: 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.]
or
[DbUpdateException: 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.]
Here is the classes I am working with:
public class SpecialEquipment : Entity
{
public Equipment Equipment { get; set; }
public virtual ICollection<AutoclaveValidation> Validations { get; set; }
}
public class Equipment : Entity
{
public string Model { get; set; }
public string SerialNumber { get; set; }
public Location Location { get; set; }
public EquipmentType EquipmentType { get; set; }
public ICollection<Identifier> Identifiers { get; set; }
}
public class Identifier : Entity
{
public IdentifierType Type { get; set; }
public string Value { get; set; }
}
public class Location : Entity
{
public Building Building { get; set; }
public string Room { get; set; }
}
What I was trying to do was populate one SpecialEquipment object based on form inputs and already existing objects in the database and then save the special equipment to push all changes through, it looks like this:
Building building = buildingService.GetExistingOrNew(viewModel.BuildingCode)); //checks to see if building exists already, if not, create it, save it, and re-query
Location location = locationService.GetExistingOrNew(viewModel.Room, building); //checks to see if location exists already, if not, create it, save it, and re-query
EquipmentType equipmentType = equipmentTypeService.GetOne(x => x.Name == EquipmentTypeConstants.Names.Special);
Equipment equipment = new Equipment{ EquipmentType = equipmentType, Location = location };
equipment.Identifiers = new Collection<Identifier>();
foreach (FormIdentifier formIdentifier in identifiers)
{
FormIdentifier fIdentifier = formIdentifier;
IdentifierType identifierType = identifierTypeService.GetOne(x => x.Id == fIdentifier.Key);
equipment.Identifiers.Add(new Identifier { Type = identifierType, Value = fIdentifier.Value });
}
EntityServiceFactory.GetService<EquipmentService>().Save(equipment);
SpecialEquipment specialEquipment = new SpecialEquipment();
specialEquipment.Equipment = equipment;
specialEquipmentService.Save(specialEquipment);
This code returns Store update, insert, or delete statement affected an unexpected number of rows (0). If I comment out the foreach identifiers OR put the foreach identifiers after the equipment save and then call equipment save after the loop the code works. If I comment out the foreach identifiers and the save equipment line, I get : The INSERT statement conflicted with the FOREIGN KEY constraint "SpeicalEquipment_Equipment". The conflict occurred in database "xxx", table "dbo.Equipments", column 'Id'.
So how can I make these errors not occur but still save my object? Is there a better way to do this? Also I don't like saving my equipment object, then associating/saving my identifiers and/or then my special equipment object because if there is an error occurring between those steps I will have orphaned data. Can someone help?
I should mention a few things that aren't inheritly clear from code, but were some answers I saw for similar questions:
My framework stores the context in the HttpContext, so all the service methods I am using in my API are using the same context in this block of code. So all objects are coming from/being stored in one context.
My Entity constructor populates ID anytime a new object is created, no entities have a blank primary key.
Edit: At the request of comments:
My .Save method calls Insert or Update depending on if the entity exists or not (in this example insert is called since the specialEquipment is new):
public void Insert(TClass entity)
{
if (Context.Entry(entity).State == EntityState.Detached)
{
Context.Set<TClass>().Attach(entity);
}
Context.Set<TClass>().Add(entity);
Context.SaveChanges();
}
public void Update(TClass entity)
{
DbEntityEntry<TClass> oldEntry = Context.Entry(entity);
if (oldEntry.State == EntityState.Detached)
{
Context.Set<TClass>().Attach(oldEntry.Entity);
}
oldEntry.CurrentValues.SetValues(entity);
//oldEntry.State = EntityState.Modified;
Context.SaveChanges();
}
GetExistingOrNew for Building and location both are identical in logic:
public Location GetExistingOrNew(string room, Building building)
{
Location location = GetOne(x => x.Building.Code == building.Code && x.Room == room);
if(location == null)
{
location = new Location {Building = building, Room = room};
Save(location);
location = GetOne(x => x.Building.Code == building.Code && x.Room == room);
}
return location;
}
Get one just passes that where predicate to the context in my repository with singleOrDefault. I am using a Service Layer/Repository Layer/Object Layer format for my framework.
Your Insert method does not seem to be correct:
public void Insert(TClass entity)
{
if (Context.Entry(entity).State == EntityState.Detached)
Context.Set<TClass>().Attach(entity);
Context.Set<TClass>().Add(entity);
Context.SaveChanges();
}
specialEquipment is a new entity and the related specialEquipment.Equipment as well (you are creating both with new)
Look what happens if you pass in the specialEquipment into the Insert method:
specialEquipment is detached because it is new
So, you attach it to the context
Attach attaches specialEquipment and the related specialEquipment.Equipment as well because both were detached from the context
Both are in state Unchanged now.
Now you add specialEquipment: This changes the state of specialEquipment to Added but not the state of specialEquipment.Equipment, it is still Unchanged.
Now you call SaveChanges: EF creates an INSERT for the added entity specialEquipment. But because specialEquipment.Equipment is in state Unchanged, it doesn't INSERT this entity, it just sets the foreign key in specialEquipment
But this FK value doesn't exist (because specialEquipment.Equipment is actually new as well)
Result: You get the FK constraint violation.
You are trying to fix the problem with calling Save for the equipment but you have the same problem with the new identifiers which will finally throw an exception.
I think your code should work if you add the specialEquipment (as the root of the object graph) at the end once to the context - without attaching it, so that the whole graph of new objects gets added, basically just:
context.Set<SpecialEquipment>().Add(specialEquipment);
context.SaveChanges();
(BTW: Your Update also doesn't look correct, you are just copying every property of entity to itself. The context won't detect any change and SaveChanges won't write any UPDATE statement to the database.)
My guess? It can't have an ID if you haven't saved it and that's the root of the problem (since it works if you save first).
Pop everything in a transaction, so if anything goes wrong all is rolled back. Then you don't have orphans.
See http://msdn.microsoft.com/en-us/library/bb738523.aspx for how to use transactions with EF.
In a situation where you have a parent class which has one child class, what is the best pattern for mapping the entities.
I have seen a lot of suggestions where the parent has a reference to both the child class, and the child class id. E.g.
public class Parent
{
public int Id
{
get;
set;
}
public int ChildId
{
get;
set;
}
public virtual Child Child
{
get;
set;
}
}
public class Child
{
public int Id
{
get;
set;
}
}
public class ParentMapping : EntityTypeConfiguration<Parent>
{
public ParentMapping()
{
HasKey(x => x.Id);
HasRequired(X => x.Child)
.WithMany()
.Map(x => x.ToTable("Parent")
.MapKey("ChildId"));
}
}
With this pattern, when saving the parent, if you want to swap out the child for a different but existing child, examples I have seen just update the ChildId and not the Child which feels wrong because the object is out of sync with itself.
The code looks neater without the ChildId but with this pattern I am having trouble saving the parent using an existing child because EF is trying to save a new child.
public class Parent
{
public int Id
{
get;
set;
}
public virtual Child Child
{
get;
set;
}
}
What is the best pattern, I would like to know if the ChildId is needed, then how is the Child property kept in sync and will it be lazy loaded from the database or not.
This is difference between foreign key and independent association. When using foreign key association you can really use just key without loading the related object. It makes the reference out of sync if you have it loaded - which is not always the case. If you want to keep the reference in sync you are almost back in the situation which you must solve with independent association.
If you expose foreign key you should use it because it makes a lot of things much more easier. If you use the independent association you should do something like:
var parent = GetUpdatedParentSomehow();
// Dummy object for the old child if the relation is not loaded
parent.Child = new Child { Id = oldChildId };
// Attach the parent
context.Parents.Attach(parent);
// Create dummy for new child (you can also load a child from DB)
var child = new Child { ID = newChildId };
// No attach the child to the context so the context
// doesn't track it as a new child
context.Childs.Attach(child);
// Set a new child
parent.Child = child;
// Set parent as modified
context.Entry(parent).State = EntityState.Modified;
context.SaveChanges();
There is very strange part where I'm creating dummy for the old child. I'm almost sure that if I don't do it before attaching the parent and setting the new child I will get some exception during saving changes (in case of independent association).