EF keeps trying to persist invalid object - c#

I'm having a rather strange issue with Entity Framework 4.3 in my MVC application. I'm using a Unit of Work wrapper around DbContext, and in my MVC application I use Unity to pass this UOW to my repositories, and repositories to controllers. I've registered the UOW type with the HierarchicalLifetimeManager.
When I try to persist an entity to the database that raises an error, e.g. the database throws a UNIQUE constraint violation, the entity is kept inside EF's ObjectStateManager. So when I go back in my application to fix the error and save the new entity (without errors), EF first tries to add the old and invalid object again, thus failing with the same error.
What am I missing here? I believe that the invalid object should be completely forgotten by EF and that this would be done automatically. But it's clearly not the case.
To add objects to DbContext in order to persis them, the following command gets called (where base is the DbContext):
base.Set<TEntity>().Add(objectToPersist);
And to commit the changes to the database, I call:
base.SaveChanges();
Which throws the error.

I believe that the invalid object should be completely forgotten by EF
and that this would be done automatically. But it's clearly not the
case.
Right, that's not the case and I've never heard that entities would be detached from the context automatically when an exception occured.
There are basically two options to deal with the problem. I show a simple model with your example of a unique key constraint violation:
public class Customer
{
// so we need to supply unique keys manually
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyContext>());
using (var ctx = new MyContext())
{
var customer = new Customer { Id = 1, Name = "X" };
ctx.Customers.Add(customer);
ctx.SaveChanges();
}
// Now customer 1 is in database
using (var ctx = new MyContext())
{
var customer = new Customer { Id = 1, Name = "Y" };
ctx.Customers.Add(customer);
try
{
ctx.SaveChanges();
// will throw an exception because customer 1 is already in DB
}
catch (DbUpdateException e)
{
// customer is still attached to context and we only
// need to correct the key of this object
customer.Id = 2;
ctx.SaveChanges();
// no exception
}
}
}
}
The above is the prefered solution: Correct the object which is attached to the context.
If you need - for whatever reason - to create a new object you must detach the old object from the context. That object is still in state Added and EF will try to save the object again when you call SaveChanges leading to the same exception as before.
Detaching the old object would look like this:
try
{
ctx.SaveChanges();
// will throw an exception because customer 1 is already in DB
}
catch (DbUpdateException e)
{
ctx.Entry(customer).State = EntityState.Detached;
// customer is now detached from context and
// won't be saved anymore with the next SaveChanges
// create new object adn attach this to the context
var customer2 = new Customer { Id = 2, Name = "Y" };
ctx.Customers.Add(customer2);
ctx.SaveChanges();
// no exception
}
This procedure can be tricky if relationships are involved. For example if customer has a relationship to a list of orders, detaching the customer object will delete references between customer and its orders if the orders are attached to the context as well. You have to reestablish the relationships with the new customer2.
Therefore I'd prefer to modify the attached object to put it into correct state. Or let the application crash because such constraint violations usually indicate bugs in the code or - in a multiuser environment - should be handled with proper optimistic concurrency checks.

looks like you'll have to tell EF that you changed your mind about the invalid object:
base.Set().Remove(objectToPersist);

If you want to reset the changes, you could set the ObjectContext to null and re-instantiate it.

Related

.NET - Attaching an entity of type failed because another entity of the same type already has the same primary key value

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

NHibernate by code - Save entity with unsaved(transient) child entity

I have the following entity:
public class Item
{
public virtual long ID { get; set; }
public virtual Version Version { get; set;}
More properties...
}
In the entity mapping I have:
ManyToOne(p => p.Version, m =>
{
m.Column("VERSION_ID");
}
The entity Version is also mapped by code and it's ID is an auto generated sequence.
When I save an Item, I create a new Version, assign it to the Version property and save it. I want to save the Version entity only after the Item is successfully saved. Now it throws a TransientObjectExceptionwhen I do this. Is it possible to solve this?
You cannot save an entity that references a transient object through a mapped property (Item->Version) unless when mapping the property you specify Cascade.Persist or Cascade.All.
Another thing is that since you should be running that code in a transaction the order of inserts should not matter. In case an exception is thrown (or anything else bad happens) after you save Version but before you save the Item, the transaction should be rolled back and nobody is going to see the new version.
The snippet below shows how you can begin/commit a transaction with nHibernate. Notice that the transaction will be rolled back if it does not get commited before it is disposed.
using(var session = sessionFactory.OpenSession())
using(var transaction = session.BeginTransaction())
{
// Do your thing here...
transaction.Commit();
}

Reattaching a detached entity throws an exception

I'm using Entity Framework 6 (using generated models and DbContext) for my project. The architecture I'm using in my program is that the data access layer is abstracted from the application. This means my entities will mostly stay in detached state.
It seems that reattaching any previously connected entities seems impossible (or I'm not doing it properly).
I've tested a case in isolation and was able to narrow the problem down. Here are the steps.
Get the entity from the context and dispose the context. This detaches the entity.
public async Task<ArchiveEntry> GetEntry(string api, string id)
{
// Omitted some code for simplicity
ArchiveEntry entry;
using (var db = new TwinTailDb())
{
entry = await db.ArchiveEntries.Where(a => a.Id == id).SingleOrDefaultAsync();
}
return entry;
}
Save the entity regardless if it was changed or not.
public async Task Save()
{
// Omitted some code for simplicity
using (var db = new TwinTailDb())
{
db.ArchiveEntries.AddOrUpdate(a => a.Id, this);
await db.SaveChangesAsync();
}
}
Finally, reattach the detached entity.
public async Task RevertChanges()
{
using (var db = new TwinTailDb())
{
if (db.Entry(this).State == EntityState.Detached && Id != 0)
{
db.ArchiveEntries.Attach(this);
//await db.Entry(this).ReloadAsync(); // Commented since this is irrelevant
}
}
}
Then I run this function to use the code above.
public async Task Test()
{
ArchiveEntry entry = await ArchiveService.GetEntry(null, "7");
await entry.Save();
await entry.RevertChanges();
}
Then it throws this error:
Attaching an entity of type 'TwinTail.Entities.ArchiveEntry' 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.
Here's an important point. If I skip step 2, it doesn't throw an exception.
My speculation is that the entity was modified and saved in a different context. If step 2 was skipped, the entity remains unchanged so reattaching it doesn't pose a problem (just guessing). However, this entity is already in a detached state so this should be irrelevant.
Another point, the ChangeTracker doesn't contain anything during these steps. Also, if I perform any context operation on the detached entity, it throws an exception saying that it should be attached first. I've also noticed that the internal _entitywrapper still has a reference to the old context.
So, finally, here's the question. How do I properly reattach an entity and why does this exception happen.
I've asked something similar in a different question (How to revert changes on detached entities) but felt that I need to post a new one since this is more general.
The architecture I'm using in my program is that the data access layer is abstracted from the application.
It looks like you are implementing these methods on the ArchiveEntry class itself. That is not an abstracted data access layer, and passing entities around to many short-lived contexts like this will get you into trouble.
Instead of giving the entity classes their own methods for managing persistence concerns, you should put that code into a class separate from the entity, and make sure that once an entity gets attached to a context, you keep using that same context until it gets disposed. Once the context is disposed, if you want to do another operation on the entity, you should retrieve it from a new context before trying to do any (attached) things with it.
I was using AsNoTracking() so, the exception was odd to me. The code below fixed the issue in my code. I think I could get ride of the code in the try block and only use the catch block, but I never tested the concept.
Entity Base class with PrimaryKey
public class EntityBase<K>
{
[NotMapped]
public virtual K PrimaryKey {get;}
}
Update function
public static void Update<T,K>(DbContext context, T t) where T:EntityBase<K>
{
DbSet<T> set = context.Set<T>();
if(set==null) return;
if(context.Entry<T>(t).State == EntityState.Detached)
{
try
{
T attached = set.Attached(t);
context.Entry<T>(attached).State = EntityState.Modified;
}catch(Exception ex)
{
T found = set.Find(t.PrimaryKey);
context.Entry(found).CurrentValues.SetValues(t);
}
}
context.SaveChanges();
}
Extension Function
public static void Update<T,K>(this DbContext context, T t) where T:EntityBase<K> => Update<T,K>(context, t);

Change id of foreign key object while add new object in EF

I use EF as ORM, but I have problem with simple add object with foreign keys. I execute this code below to add new article to DB.
dataLayer = new CmsDataLayer();
var newArticle = new Article();
newArticle.Author = AuthorService.CurrentAuthor; //id =8
dataLayer.Articles.Insert(newArticle);
There is CmsDataLayer.ICmsRepository is repository pattern (simple CRUD operation)
class CmsDataLayer
{
public ICmsRepository<Author> Authors = new MsSqlServerCmsRepository<Author>();
}
In method insert I do this
class MsSqlServerCmsRepository
{
private DbSet<T> dataSet;
public MsSqlServerCmsRepository()
{
dataSet = dataContext.Set<T>();
}
public void Insert(T entity)
{
this.dataSet.Add(entity);
this.dataContext.SaveChanges(); //<--
}
After this operation newArticle.Author gets new value of Author ID, before SaveChanges() entity it has Author with id=8, after Author has id=14.
I don't understand why EF change AuthorId after save operation, author with id=8 exists w DB?
I don't know which data context the current author is loaded from in the author service, but I think you need to get it from the same context or attach it. Otherwise it will see it as a new object and insert it again. Alternatively, rather than setting the navigation property, you can leave it sert to null and set the key field .AuthorId instead.
Is the author loaded from the same DB context (or attached to it)?
If not set author ID instead of navigation property.
Also save changes isn't supposed to be in the add method, it is supposed called before closing the DB context.
Try to apply this modification:
public void InsertArticle(Article entity)
{
entity.Author = this.dataContext.Authors.Find(entity.AuthorEntityIdPropertyName);
this.dataSet.Add(entity);
this.dataContext.SaveChanges(); //<--
}
Looking your code I think the AuthorService.CurrentAuthor is not tracked by the MsSqlServerCmsRepository's dataContext. So that it will insert it wiht the next incremented id.

Lots of problems (foreign key or no rows were updated errors) when trying to save complex objects in EF code first

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.

Categories