Entity Framework The ObjectContext might be in an inconsistent state - c#

I'd like to understand why "The ObjectContext might be in an inconsistent state." exception occurs.
I know that there are multiple threads related to similar issue but none seems to answer my question. (Closest to my problem is: The changes to the database were committed successfully...The ObjectContext might be in an inconsistent state )
I've got Database First model containing tables:
Training (PK Id)
User (PK Id)
UserTraining(PK (UserId, TrainingId), FK(UserId), FK(TrainingId))
Documentation(PK Id, FK TrainingId)
UserDocumentation (PK (UserId, TrainingId, DocumentationId),
FK(UserId, TrainingId), FK(DocumentationId))
All above tables are properly mapped in code(all keys are present etc.).
What I'm doing is - Create Training with Documentation, then Create UserTraining with UserDocumentation, like below:
var type = this.trainingRepository.GetTrainingType(command.Training.TypeId);
var training = new Training(
type,
command.Training.Name,
command.Training.Documentation,
...);
if (type.Equals(TrainingType.Mandatory))
{
var users = this.userRepository.GetAllUsers();
foreach(var user in users)
{
user.AssignTraining(training);//under the hood is created new instance of UserTraining with relation to training and UserDocumentation related to UserTraining and Documentation from training
}
}
trainingRepository.Add(training);
trainingRepository.SaveChanges();
There are 2 repositories but each uses the same instance of DBContext(registered as PerRequest).
Now if I move adding and saving changes above if (type.Equals(TrainingType.Mandatory)) and add one move SaveChanges at the end of method everything passes without any issues.
Unfortunately in case above it throws exception mentioned in title.
Now the question - Why does adding in the middle and then updating work and my solution doesn't? Can it be related to poor database schema or rather EF limitations?
AssignTraining method:
public void AssignTraining(Training training)
{
this.State.UserTrainings.Add(new UserTraining(training));
}
UserTraining constructor:
public UserTraining(Training training) : base(new
DataAccess.Database.UserTraining())
{
this.State.Training = training;
if(training.Documentation != null)
{
this.State.UserDocumentations.Add(new UserDocumentation(training.Documentation));
}
}

Related

Assigning entity instance instead of entity id creates new record

I have these two tables:
public class FiscalYear
{
... other fields
public int FiscalYears_Id { get; set; }
}
public class SkipHeader
{
... other fields
public int FiscalYears_Id { get; set; }
public virtual FiscalYear FiscalYear { get; set; }
}
Attempting to create a new SkipHeader like so:
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYear = Session.FiscalYear,
}
Will cause the database to create a new FiscalYear record instead of using the Session.FiscalYear which is simply a static property that gets assigned to at program start. However, if I assign the FiscalYears_Id instead:
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYears_Id = Session.FiscalYear.FiscalYears_Id,
}
The program uses the existing record as expected.
This bug eluded me and my colleague for months! Now that I found a solution, I would like to know WHY this is the case?
This bug eluded me and my colleague for months! Now that I found a
solution, I would like to know WHY this is the case?
This occurs because the DbContext doesn't know about your FiscalYear object instance, such as whether it represents a new record or an existing one.
Take the following example:
var fiscalYear = new FiscalYear { Id = 4, Name = "2019/20" };
var skipHeader = new SkipHeader { FiscalYear = fiscalYear };
context.SkipHeaders.Add(skipHeader);
context.SaveChanges();
fiscalYear in this instance is an object instance that has been given an ID and Name. When we associate it to a new SkipHeader and add the SkipHeader to the DbContext, EF will see this fiscalYear. Since it isn't an object tracked by the context, it treats it as a new entity like the SkipHeader.
Depending on how your entities are configured for dealing with the PK will determine what happens.
If your PK (Id) is set up as an Identity column (DB will populate) then the FiscalYear will be inserted and assigned the next available Id value. After the SaveChanges() call, fiscalYear.Id would be "6" or "22" or whatever the next new ID assigned to it would be. (Not "4")
If your PK is not an Identity column (App will populate) and a FiscalYear row already exists in the DB for ID 4, then EF will throw a duplicate key Exception on SaveChanges().
Where people get confused is that they assume that since the FiscalYear was at one point (Say during a web request) loaded from a DbContext, it is still somehow acting as a tracked entity when passed into another method outside of the scope of that DbContext. (During another update web request) It's not. When a web request for instance accepts a FinancialYear as a parameter from the client, it is deserializing a FinancialYear. As far as EF is concerned, that is absolutely no different than the new FinancialYear { } example above. The DbContext is not aware of that entity.
Take the following example:
FiscalYear fiscalYear = null;
using (var context = new AppDbContext())
{
fiscalYear = context.FiscalYears.Single(x => x.Id == 4);
}
using (var context = new AppDbContext())
{
var skipHeader = new SkipHeader { FiscalYear = fiscalYear };
context.SkipHeaders.Add(skipHeader);
context.SaveChanges();
}
This provides a basic outline of a Fiscal Year that was loaded by one instance of a DbContext, but then referenced by another instance of a DbContext. When SaveChanges is called, you will get a behaviour like you are getting now. This is what essentially happens in web requests, as when an entity is returned, the entity definition is merely a contract and the Entity is serialized to send to the client. When it comes back into another request, a new untracked object is deserialized.
As a general rule, Entities should not be passed outside the scope of the DbContext they were read from. EF does support this via detaching and re-attaching entities, but this is honestly more trouble than it is typically worth because you cannot 100% rely on just attaching an entity using DbContext.Attach() as often there can be conditional cases where another entity instance is already being tracked by a context and the Attach will fail. In these cases you'd need to replace references with the already tracked entity. (Messy conditional logic to catch possible scenarios) References are everything when dealing with EF. Two different object references with the same key & values are treated as separate and different objects by EF. Rather than passing references around, it's usually a lot simpler, and better to pass just the FK. This has the benefit of being a smaller payload for web requests.
One option you've found out is to update via the FK:
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYears_Id = Session.FiscalYear.FiscalYears_Id,
}
This works, however when you have entities that are exposing both FK (FiscalYears_Id) and navigation property (FiscalYear) you can potentially find mismatch scenarios when updating records. This is something to be careful with as an application evolves.
For instance, take an example where you are editing an existing SkipHeader with a FiscalYears_Id of "4". This will have an associated FiscalYear reference available with a PK of "4".
Take the following code:
var skipHeader = context.SkipHeaders.Include(x => x.FiscalYear).Single(x => x.Id == skipHeaderId);
skipHeader.FiscalYears_Id = newFiscalYearId; // update FK from "4" to "6"
var fiscalYearId = skipHeader.FiscalYear.Id; // still returns "6"
context.SaveChanges();
We set the FK value on the skip header, however that does not update the reference for FiscalYear until after we call SaveChanges. This can be an important detail when dealing with FKs alongside navigation properties. Now normally we wouldn't bother going to the Navigation Property to get the ID again, but any code we call that is expecting the new FiscalYear reference to be updated will have a different behavior depending on whether SaveChanges had been called before or after the code in question. If before, all FiscalYear details will be for the old fiscal year even though we changed the FK reference.
This can also lead to odd Lazy Loading errors as well such as:
var skipHeader = context.SkipHeaders.Single(x => x.Id == skipHeaderId);
skipHeader.FiscalYears_Id = newFiscalYearId; // update FK from "4" to "6"
var fiscalYearId = skipHeader.FiscalYear.Id; // NullReferenceException!
context.SaveChanges();
Normally, provided you have lazy loading enabled loading a SkipHeader without eager loading the FiscalYear (.Include(x => x.FiscalYear))and then querying a property from the FiscalYear would lazy load this relative. However, if you change the SkipHeader's FiscalYear_ID FK and then try to access a property off the FiscalYear before calling SaveChanges(), you will get a NullReferenceException on the FiscalYear. EF will NOT lazy load either the old or new FiscalYear entity. Bugs in behaviour like that commonly creep in as applications get developed and code starts calling common functions that assume they are dealing with complete entities.
The alternative to setting updated values for known rows by FK is to load the entity to associate, and associate it by reference:
using (var context = new AppDbContext())
{
var fiscalYear = context.FiscalYears.Single(x => x.Id == fiscalYearId);
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYear = fiscalYear;
}
context.SaveChanges();
}
This example just uses a locally scoped DbContext. If your method has an injected context then use that instead. The context will return any cached, known instance of the Fiscal Year or retrieve it from the DB. If the FiscalYear ID is invalid then that operation will throw an exception specific to the Fiscal Year not being found due to the Single() call rather than a more vague FK violation on SaveChanges(). (Not an issue when there is only one FK relationship, but in entities that have dozens of relationships...)
The advantage of this approach is that the FiscalYear will be in the scope of the DbContext so any methods/code using it will have a valid reference. The entities can define the navigation properties without exposing the extra FK values,using .Map(x => x.MapKey()) [EF6] or Shadow Properties [EFCore] instead to avoid 2 sources of truth for FK values.
This hopefully will provide some insight into what EF is doing and why it resulted in the behaviour you've seen and/or any errors or buggy behaviour you might have also come across.
Assuming you have pretty standard setup with DbContext being scoped (per request) dependency - the reason is that the new instance of your DbContext does not track the Session.FiscalYear instance - so it creates new. Another way to solve this is using DbContext.Attach:
context.Attach(Session.FiscalYear);
var skipHeader = new SkipHeader()
{
... other fields get assigned to
FiscalYears_Id = Session.FiscalYear.FiscalYears_Id,
}
// save skipHeader
More about change tracker in EF.

Insert Master-Detail with LINQ

I want to insert a master-detail with the following structure:
Every Sale has an Id, date and a Client, Employee and SaleDetail.
Every Sales Detail has a pieces number and price and of course a reference to its master and which product is.
I tried the following code but I cannot get it to work:
private void GenerarNota()
{
EntityCollection<SalesDetail> details = new EntityCollection<SalesDetail>();
foreach (ListItem item in _productList)
{
SalesDetail detail = new SalesDetail();
detail.Product = db.Product.FirstOrDefault(p => p.Id == item.Id);
detail.Pieces = item.Pieces;
detail.Price = item.Price;
details.Add(detail);
}
Sale sale = new Sale
{
Client = (Client )txtCliente.Item,
Employee = (Employee )txtEmp.Item,
SalesDetail = details
};
db.AddToSale(sale);
db.SaveChanges();
}
The exception I got:
The object could not be added to the EntityCollection or
EntityReference. An object that is attached to an ObjectContext cannot
be added to an EntityCollection or EntityReference that is not
associated with a source object.
Am I doing something wrong? I read about attaching and dettaching objects but after I tried that I got a FK constraint violation.
Can you please tell me what I'm doing wrong or if it's another way to do it? I'm very new at LINQ, I could perfectly do it in pure SQL but I wanted to learn about it.
detail.Product is still associated with db. Prices and Pieces may be associated with it as well. The system is very strict in ensuring that every object can only be associated with one object context (your db) or completely detached (your EntityCollection<>).
Either your details EntityCollection needs to be associated with db (no idea how to do this) or all your objects must be properly detached before they're tacked into details.
Honestly, I'd just reorder things around - create Sale first and add it, then start inserting things into Sale.SalesDetail.

How to update entity?

I had a question more detailed earlier which I had no answer, I will have the same question with a simpler way:
I have an EF database with foreign key to another table.
I would like to UPDATE an ENTITY. But I need to this like this and I'll write the codes below:
Go to database and retrieve the Member by id, return EF Member object
Do some changes on the object OUTSIDE the EF Context
Send the MODIFED EF Member into a Save method
In BL layer save method uses the context and save changes.
1)
MemberManager currentMemberManager = new MemberManager();
Member NewMember = currentMemberManager.GetById(2);
2)
NewMember.FirstName = "NewFirstName";
NewMember.LanguageId = 1;
3)
currentMemberManager.Save(NewMember);
4)
public void Save2(Member newMember)
{
using (var Context = new NoxonEntities())
{
Member existingMember = Context.Member.First(c => c.Id == newMember.Id);
existingMember.FirstName = newMember.FirstName;
existingMember.Language = Context.Language.First(c => c.Id == newMember.LanguageId);
Context.SaveChanges();//In here I get the error below
}
}
The changes to the database were committed successfully, but an error
occurred while updating the object context. The ObjectContext might be
in an inconsistent state. Inner exception message: A referential
integrity constraint violation occurred: The property values that
define the referential constraints are not consistent between
principal and dependent objects in the relationship.
Note: You may suggest to SEND a different class (Ex: public class
MyMember) that has all the necessary properties and totally separated
from EF. But this requires much work to get all EF object converting
into my separate classes. Am I right?
I am hoping there is a way to Detach the entity just long enough for me to modify it and save the values into database. (Also, I tried the Detach method which updates no rows at all)
I've been trying to solve this for hours now.
Please, help me to understand it better, I really need a solution. Thank you so much to anyone how has some ideas.
Could you do something simple like detaching the entity, then attaching it to the context when you're ready to save?
MemberManager currentMemberManager = new MemberManager();
Member NewMember = currentMemberManager.GetById(2);
The get:
public Member GetById(int id)
{
var member = YourContext.Members.FirstOrDefault(m => m.id == id);
YourContext.Detach(member);
return member;
}
The save:
public void Save2(Member newMember)
{
using (var Context = new NoxonEntities())
{
Context.Attach(newMember);
Context.ObjectStateManager.ChangeObjectState(newMember, EntityState.Modified);
Context.SaveChanges();
}
}

Is there a way to find all Entities that have had their relationships deleted?

I am trying to not have my Business Logic know the inner workings of my Data Layer and vica versa.
But Entity Framework is making that hard. I can insert into a collection (in my Business Layer) without a reference to the ObjectContext:
order.Containers.Add(new Container { ContainerId = containerId, Order = order });
And that saves fine when it comes time to do a SaveChanges() in the Data Layer.
But to delete an item from a collection I need a reference to the ObjectContext. (I am case #1 in this guide to deleting EF Entities.) If I just do this:
delContainers.ForEach(container => order.Containers.Remove(container));
Then when I call SaveChanges() I get an exception telling me that I need to delete the object as well as the reference.
So, my options as I see it are:
To pass a delegate to my Business Logic that will call the Entity Framework ObjectContext Delete method.
Or (I am hoping) find a way to get all entities that have had their reference deleted and actually delete them. (Right before calling SaveChanges() in my data layer.)
Does anyone know a way to do that?
UPDATE:
I tried this:
// Add an event when Save Changes is called
this.ObjectContext.SavingChanges += OnSavingChanges;
...
void OnSavingChanges(object sender, EventArgs e)
{
var objectStateEntries = ObjectContext.ObjectStateManager
.GetObjectStateEntries(EntityState.Deleted);
foreach (var objectStateEntry in objectStateEntries)
{
if (objectStateEntry.IsRelationship)
{
// Find some way to delete the related entity
}
}
}
But none even though I deleted a relationship, the set of deleted items is empty.
(I tried viewing all the items too and my relationship is not in there. Clearly there is something fundamental that I don't get about ObjectStateManager.)
The correct solution for EF is point 3. from the linked article. It means propagating FK to principal entity into PK for dependent entity. This will form something called identifying relation which automatically deletes dependent entity when it is removed from the parent entity.
If you don't want to change your model and still want to achieve that in persistence ignorant way you probably can but it will work only for independent associations. Some initial implementation which works at least for my simple tested solution:
public partial class YourObjectContext
{
public override int SaveChanges(SaveOptions options)
{
foreach (ObjectStateEntry relationEntry in ObjectStateManager
.GetObjectStateEntries(EntityState.Deleted)
.Where(e => e.IsRelationship))
{
var entry = GetEntityEntryFromRelation(relationEntry, 0);
// Find representation of the relation
IRelatedEnd relatedEnd = entry.RelationshipManager
.GetAllRelatedEnds()
.First(r => r.RelationshipSet == relationEntry.EntitySet);
RelationshipType relationshipType = relatedEnd.RelationshipSet.ElementType;
if (!SkipDeletion(relationshipType))
{
// Now we know that model is inconsistent and entity on many side must be deleted
if (!(relatedEnd is EntityReference)) // related end is many side
{
entry = GetEntityEntryFromRelation(relationEntry, 1);
}
if (entry.State != EntityState.Deleted)
{
context.DeleteObject(entry.Entity);
}
}
}
return base.SaveChanges();
}
private ObjectStateEntry GetEntityEntryFromRelation(ObjectStateEntry relationEntry, int index)
{
var firstKey = (EntityKey) relationEntry.OriginalValues[index];
ObjectStateEntry entry = ObjectStateManager.GetObjectStateEntry(firstKey);
return entry;
}
private bool SkipDeletion(RelationshipType relationshipType)
{
return
// Many-to-many
relationshipType.RelationshipEndMembers.All(
r => r.RelationshipMultiplicity == RelationshipMultiplicity.Many) ||
// ZeroOrOne-to-many
relationshipType.RelationshipEndMembers.Any(
r => r.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne);
}
}
To make it work your entities must be enabled for dynamic change tracking (all properties must be virtual and entity must be proxied) or you must manually call DetectChanges.
In case of foreign key associations the situation will be probably much worse because you will not find any deleted relation in the state manager. You will have to track changes to collections or keys manually and compare them to find discrepancies (I'm not sure how to do it in generic way) Foreign key association IMHO requires the identifying relation. Using FK properties already means that you included additional persistence dependency into your model.
One way is to write a change handler in your data layer:
private void ContainersChanged(object sender,
CollectionChangeEventArgs e)
{
// Check for a related reference being removed.
if (e.Action == CollectionChangeAction.Remove)
{
Context.DeleteObject(e.Element);
}
}
There are many places you can wire this up -- in your object's constructor or repository get or SavingChanges or wherever:
entity.Containers.AssociationChanged += new CollectionChangeEventHandler(ContainersChanged);
Now you can remove the association from elsewhere and it will "cascade" to the entity.

OptimisticConcurrencyException Does Not Work in Entity Framework In Certain Situations

UPDATE (2010-12-21): Completely rewrote this question based on tests that I've been doing. Also, this used to be a POCO specific question, but it turns out that my question isn't necessarily POCO specific.
I'm using Entity Framework and I've got a timestamp column in my database table that should be used to track changes for optimistic concurrency. I've set the concurrency mode for this property in the Entity Designer to "Fixed" and I'm getting inconsistent results. Here are a couple of simplified scenarios that demonstrate that concurrency checking works in one scenario but not in another.
Successfully throws OptimisticConcurrencyException:
If I attach a disconnected entity, then SaveChanges will throw an OptimisticConcurrencyException if there is a timestamp conflict:
[HttpPost]
public ActionResult Index(Person person) {
_context.People.Attach(person);
var state = _context.ObjectStateManager.GetObjectStateEntry(person);
state.ChangeState(System.Data.EntityState.Modified);
_context.SaveChanges();
return RedirectToAction("Index");
}
Does not throw OptimisticConcurrencyException:
On the other hand, if I retrieve a new copy of my entity from the database and I do a partial update on some fields, and then call SaveChanges(), then even though there is a timestamp conflict, I don't get an OptimisticConcurrencyException:
[HttpPost]
public ActionResult Index(Person person) {
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
currentPerson.Name = person.Name;
// currentPerson.VerColm == [0,0,0,0,0,0,15,167]
// person.VerColm == [0,0,0,0,0,0,15,166]
currentPerson.VerColm = person.VerColm;
// in POCO, currentPerson.VerColm == [0,0,0,0,0,0,15,166]
// in non-POCO, currentPerson.VerColm doesn't change and is still [0,0,0,0,0,0,15,167]
_context.SaveChanges();
return RedirectToAction("Index");
}
Based on SQL Profiler, it looks like Entity Framework is ignoring the new VerColm (which is the timestamp property) and instead using the originally loaded VerColm. Because of this, it will never throw an OptimisticConcurrencyException.
UPDATE: Adding additional info per Jan's request:
Note that I also added comments to the above code to coincide with what I see in my controller action while working through this example.
This is the value of the VerColm in my DataBase prior to the update: 0x0000000000000FA7
Here is what SQL Profiler shows when doing the update:
exec sp_executesql N'update [dbo].[People]
set [Name] = #0
where (([Id] = #1) and ([VerColm] = #2))
select [VerColm]
from [dbo].[People]
where ##ROWCOUNT > 0 and [Id] = #1',N'#0 nvarchar(50),#1 int,#2 binary(8)',#0=N'hello',#1=1,#2=0x0000000000000FA7
Note that #2 should have been 0x0000000000000FA6, but it's 0x0000000000000FA7
Here is the VerColm in my DataBase after the update: 0x0000000000000FA8
Does anyone know how I can work around this problem? I'd like Entity Framework to throw an exception when I update an existing entity and there's a timestamp conflict.
Thanks
Explanation
The reason why you aren't getting the expected OptimisticConcurrencyException on your second code example is due to the manner EF checks concurrency:
When you retrieve entities by querying your db, EF remembers the value of all with ConcurrencyMode.Fixed marked properties by the time of querying as the original, unmodified values.
Then you change some properties (including the Fixed marked ones) and call SaveChanges() on your DataContext.
EF checks for concurrent updates by comparing the current values of all Fixed marked db columns with the original, unmodified values of the Fixed marked properties.
The key point here is that EF treats the update of you timestamp property as a normal data property update. The behavior you see is by design.
Solution/Workaround
To workaround you have the following options:
Use your first approach: Don't requery the db for your entity but Attach the recreated entity to your context.
Fake your timestamp value to be the current db value, so that the EF concurrency check uses your supplied value like shown below (see also this answer on a similar question):
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
currentPerson.VerColm = person.VerColm; // set timestamp value
var ose = _context.ObjectStateManager.GetObjectStateEntry(currentPerson);
ose.AcceptChanges(); // pretend object is unchanged
currentPerson.Name = person.Name; // assign other data properties
_context.SaveChanges();
You can check for concurrency yourself by comparing your timestamp value to the requeried timestamp value:
var currentPerson = _context.People.Where(x => x.Id == person.Id).First();
if (currentPerson.VerColm != person.VerColm)
{
throw new OptimisticConcurrencyException();
}
currentPerson.Name = person.Name; // assign other data properties
_context.SaveChanges();
Here is another approach that is a bit more generic and fits in the data layer:
// if any timestamps have changed, throw concurrency exception
var changed = this.ChangeTracker.Entries<>()
.Any(x => !x.CurrentValues.GetValue<byte[]>("Timestamp").SequenceEqual(
x.OriginalValues.GetValue<byte[]>("Timestamp")));
if (changed) throw new OptimisticConcurrencyException();
this.SaveChanges();
It just checks to see if the TimeStamp has changed and throws concurrency exception.
If it's EF Code first, then use code similar to below code. This will change the original TimeStamp loaded from db to the one from UI and will ensure OptimisticConcurrencyEception occurs.
db.Entry(request).OriginalValues["Timestamp"] = TimeStamp;
I have modified #JarrettV solution to work with Entity Framework Core. Right now it is iterating through all modified entries in context and looking for any mismatch in property marked as concurrency token. Works for TimeStamp (RowVersion) as well:
private void ThrowIfInvalidConcurrencyToken()
{
foreach (var entry in _context.ChangeTracker.Entries())
{
if (entry.State == EntityState.Unchanged) continue;
foreach (var entryProperty in entry.Properties)
{
if (!entryProperty.IsModified || !entryProperty.Metadata.IsConcurrencyToken) continue;
if (entryProperty.OriginalValue != entryProperty.CurrentValue)
{
throw new DbUpdateConcurrencyException(
$"Entity {entry.Metadata.Name} has been modified by another process",
new List<IUpdateEntry>()
{
entry.GetInfrastructure()
});
}
}
}
}
And we need only to invoke this method before we save changes in EF context:
public async Task SaveChangesAsync(CancellationToken cancellationToken)
{
ThrowIfInvalidConcurrencyToken();
await _context.SaveChangesAsync(cancellationToken);
}

Categories

Resources