I have this problem where I have 2 entities connected by foreign key.
AEntity: id, idOfEntityB (foreign key, constraint), fields...
BEntity: id, fields...
I save both of them to the database with SaveChanges(), later when I try to get AEntity's idOfEntityB, I succeed but when I try to get BEntity according to the id I got from AEntity, I get nothing:
context.AEntities.Add(new AEntity {
BEntity = new BEntity { ... }
});
context.SaveChanges();
.
.
.
var id1 = context.AEntities.Select(x => x.idOfEntityB);
var bEntities = context.BEntities.Where(x => id1.Contains(x.id));
bEntities has nothing in it. but the fact I was able to have values in id1 is even more confusing since they have foreign key relations (with constraint) and furthermore, id could not be created if it was not saved to the DB.
Later, when I look in the DB I see both entities as should be.
It happens sometimes and I cant reproduce the problem, I cant give more then this as an example since there's a lot of code, I believe it has something to do with caching, and therefore would like to ask if something like that is possible or not and how.
is there a way entities are saved to the DB while the context (a different one used from the context that saved) does not hold all of them in completion?
This is likely the issue you are encountering if you are relying on seeing changes between state changes between different DbContext instances. When a DbContext has loaded entities, then another DbContext instance makes changes to those records or the records change behind the scenes in the database, that original DbContext will not refresh the entities from the database.
EF does support the ability to reload entities from the database, but when dealing with child collections it gets a bit more complicated to perform a full refresh. You effectively need to tell the DbContext to forget all of the child collections, stop tracking the parent, clear the parent's child collection, then re-attach and reload the child collection. I recently covered this in the answer for this question: Replacing a entity collection in Entity Framework Core causes DbContext to fetch the new values when not saved to db. How to reload the collection?
Ultimately a DbContext lifespan should be kept as short as possible.
Related
EF Core 6 and .NET 6.
Suppose all my entities have a LastUpdateAt property, which is a DateTime that gets updated every time an entity is added or modified.
I get an entity from the context and show it to the user (web page, WPF window, whatever). At some point, the user clicks a Save button.
Before I save, I want to check if the entity has been updated by someone else since I got my copy. However, I'm struggling to see how to do this.
If I query the context, it just gives me back the entity I already have (including any changes my user has made).
If I refresh the entity, it overwrites the one in my context, losing my user's changes.
How do I check if the database version has a newer time stamp than the one in my context?
Thanks
Moving the discussion here since I need to paste longer text. In this article it's said, during SaveChanges(), if the DATABASE version was modified in the mean time it will throw DbUpdateConcurrencyException. In that exception you have all 3 values and YOU can decide on how to resolve the conflict:
Resolving a concurrency conflict involves merging the pending changes from the current DbContext with the values in the database. What values get merged will vary based on the application and may be directed by user input.
There are three sets of values available to help resolve a concurrency conflict:
Current values are the values that the application was attempting to write to the database.
Original values are the values that were originally retrieved from the database, before any edits were made.
Database values are the values currently stored in the database.
If you are loading an entity, keeping a DbContext instance open, updating that entity, then saving to the same DbContext instance then by default you are relying on EF to manage concurrency. This follows a "last in wins". You can let EF manage the concurrency by adding a [ConcurrencyCheck] on the LastUpdateAt property or using a Row Version via [Timestamp]. This will cause EF to fail updating if the underlying data has been updated. From there you have to decide how you want to handle it.
If you want to perform the concurrency check yourself then there are a couple of options.
Structure your code to shorten the lifespan of the DbContext using either detached entities or projected View Models. This will generally have flow-on benefits to your code performance as the original longer-lived DbContext can easily find ways to cause bloat, or accumulate "poisoned" entities if alive too long. Automapper is a great tool to assist here where you can use ProjectTo to get the view models, then Map(source, destination) to copy the values across afterward. In this way you load the data including the last modified at value, make your changes, then when saving, you load the data, validate the modified at etc. then copy the values across and save.
Scope a DbContext instance to check the data before saving.
.
private DateTime getFooLastUpdateAt(int fooId)
{
using(var context = new AppDbContext())
{
var lastUpdateAt = context.Foos
.Where(x => x.FooId == fooId)
.Select(x => x.LastUpdateAt)
.Single();
return lastUpdateAt;
}
}
This could use an injected DbContext factory or such to create the DbContext instance..
I'm working on a form using EF Core in Blazor Server. I had a number of issues with entity tracking so I set all of my queries to AsNoTracking and designed my service to create a new instance of dbcontext for each query. I think this is appropriate as none of the returned values will be edited - only the form data that users enter and the id references to the queried fields, such as employee numbers, will be stored. For inserting data, I use this:
using var context = Factory.CreateDbContext();
context.SetupForm.Attach(model);
context.Entry(model).State = EntityState.Added;
await context.SaveChangesAsync();
I am attaching the data rather than adding it and then setting the form object state to added. This ensures EF Core doesn't attempt to insert the existing employee objects when it inserts the form data.
The trouble starts in a section of the form that can have as many items as the user wants. The select a couple of employees and type in relevant data. When they submit the form, they may have selected the same employee in multiple items. As these employees were selected from separate contexts, they are two separate instances with the same ID. Of course, EF Core doesn't like this and throws errors like this one:
The instance of entity type 'Principal' cannot be tracked because another instance with the key value '{EmployeeID: 1234}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
I understand why this error is occurring but I need to be able to attach multiple entities in this way. How can I work around this issue?
One thing I could do is assign the foreign keys manually but that would be rigid and require updates whenever the model changes.
just try this
using var context = Factory.CreateDbContext();
context.Set<Principal>().Add(model);
//or maybe context.Principals.Add(model);
await context.SaveChangesAsync();
This seems to do the trick! What it does is mark any entity that lacks a key as added. Otherwise, the entity is ignored entirely.
using var context = Factory.CreateDbContext();
context.ChangeTracker.TrackGraph(model, node =>
{
if (!node.Entry.IsKeySet)
{
node.Entry.State = EntityState.Added;
}
});
await context.SaveChangesAsync();
None of the items that have a key will need to be inserted. Treating them as untracked then solves any issues with duplicates and only inserts the rows that need it.
More information: https://learn.microsoft.com/en-us/ef/core/change-tracking/identity-resolution#resolve-duplicates
I have seen other questions about this same error, but I am unable to correct the error with those suggestions in my code; I think that this is a different problem and not a duplicate.
I have an app that makes a series of rules, of which the user can set properties in the GUI. There is a table of Rules in a connected database, with the primary key on the Rule.Id. When the user saves changes to a rule, the existing rule gets "IsActive=0" to hide it, then a new database record is made with the properties from the GUI written to the database. It looks to the user as though they have edited the rule, but the database actually sees a new rule reflecting the new properties (this allows for a history to be kept), connected to the old rule by another reference field.
In the C# code for the app, the View Model for each rule contains an EF Rule object property. When the user clicks "save" I use the parameters set in the view to build the ruleViewModel.Rule for each ruleViewModel they want to save, with porperties matching the GUI. The MainViewModel contains the DbContext object called dbo, so I use the ruleViewModel.Rule to write to the mainViewModel.dbo.Entry which I save to the Entity Framework. Here are the three basic steps performed for each saveable Rule View Model:
// get the rule from the GUI and use it to make sure we are updating the right rule in EF (which is connected to the mainViewModel)
var dboItem = ruleViewModel.MainViewModel.dbo.Rules.Single(r => r.Id == ruleViewModel.Rule.Id);
// set the values in the EF item to be those we got from the GUI
ruleViewModel.MainViewModel.dbo.Entry(dboItem).CurrentValues.SetValues(ruleViewModel.Rule);
// Save the differences
ruleViewModel.MainViewModel.dbo.SaveChanges();
If the user only saves a single rule, it all works fine, but if they subsequently try to save another, or if they save more than one at once, they get the following error, which is return by the ..SetValues(..) line:
Message = "The property 'Id' is part of the object's key information and cannot be modified. "
I see from other questions on this subject that there is a feature of EF that stops you from writing the same object twice to the database with a different Id, so this error often happens within a loop. I have tried using some of the suggestions, like adding
viewModel.MainViewModel.dbo.Rules.Add(dboItem);
and
viewModel.MainViewModel.dbo.Entry(dboItem).Property(x => x.Id).IsModified = false;
before the SaveChanges() command, but that has not helped with the problem (not to mention changing the function of the code). I see that some other suggestions say that the Entry should be created within the loop, but in this case, the entries are all existing rules in the database - it seems to me (perhaps erroneously) that I cannot create them inside the save loop, since they are the objects over which the loop is built - for each entity I find, I want to save changes.
I'm really confused about what to do and tying myself increasingly in knots trying to fix the error. It's been several days now and my sanity and self-esteem is beginning to wane! Any pointers to get me working in the right direction to stop the error appearing and allow me to set the database values would be really welcome as I feel like I have hit a complete dead end! The first time around the loop, everything works perfectly.
Aside from the questionable location of the DbContext and view models containing entities, this looks like it would work as expected. I'm assuming from the MVVM tag that this is a Windows application rather than a web app. The only issue is that this assumes that the Rule entity in your ruleViewModel is detached from the DbContext. If the DbContext is still tracking that entity reference then getting the entity from the DbContext again would pass you back the same reference.
It would probably be worth testing this once in a debug session. If you add the following:
var dboItem = ruleViewModel.MainViewModel.dbo.Rules.Single(r => r.Id == ruleViewModel.Rule.Id);
bool isReferenceSame = Object.ReferenceEquals(dboItem, ruleViewModel.Rule);
Do you get an isReferenceSame value of True or False? If True, the DbContext in your main view model is still tracking the Rule entity and the whole get dboItem and SetValues isn't necessary. If False, then the ruleViewModel is detached.
If the entities are attached and being tracked then edits to the view model entities would be persisted when you call a SaveChanges on the DbContext. (No load & SetValues needed) This should apply to single or multiple entity edits.
If the entities are detached then normally the approach for updating an entity across DbContext instances would look more like:
var context = mainViewModel.dbo;
foreach( var ruleViewModel in updatedRuleViewModels)
{
// This should associate the Entity in the ruleViewModel with the DbContext and set it's tracking state to Modified.
context.Entry(ruleViewModel.Rule).State = EntityState.Modified;
}
context.SaveChanges();
There are a couple of potential issues with this approach that you should consider avoiding if possible. A DbContext should be kept relatively short lived, so seeing a reference to a DbContext within a ViewModel is a bit of a red flag. Overall I don't recommend putting entity references inside view models or passing them around outside of the scope of the DbContext they were created in. EF certainly supports it, but it requires a bit more care and attention to assess whether entities are tracked or not, and in situations like web applications, opens the domain to invalid tampering. (Trusting the entity coming in where any change is attached or copied across overwriting the data state)
Some previous code I had written used the Find() method to retrieve single entities by their primary key:
return myContext.Products.Find(id)
This worked great because I had this code tucked into a generic class, and each entity had a different field name as its primary key.
But I had to replace the code because I noticed that it was returning cached data, and I need it to return data from the database each call. Microsoft's documentation confirmed this is the behavior of Find().
So I changed my code to use SingleOrDefault or FirstOrDefault. I haven't found anything in documentation that states these methods return cached data.
Now I am executing these steps:
Save an entity via EF.
Execute an UPDATE statement in SSMS to update the recently saved
record's Description field.
Retrieve the entity into a new entity variable using SingleOrDefault
or FirstOrDefault.
The entities being returned still have the old value in the Description field.
I have run a SQL trace, and verified that the data is being queried during step 3. This baffles me - if EF is making a round trip to the database, why is it returning cached data?
I've searched online, and most answers apply to the Find() method. Furthermore, they suggest some solutions that are merely workarounds (dispose the DbContext and instantiate a new one) or solutions that won't work for me (use the AsNoTracking() method).
How can I retrieve my entities from the database and bypass the EF cache?
The behaviour you're seeing is described in Microsoft's How Queries Work article under point 3:
For each item in the result set
a. If this is a tracking query, EF checks if the data represents an entity already in the change tracker for the context instance
If so, the existing entity is returned
It's described a little better in this blog post:
It turns out that Entity Framework uses the Identity Map pattern. This means that once an entity with a given key is loaded in the context’s cache, it is never loaded again for as long as that context exists. So when we hit the database a second time to get the customers, it retrieved the updated 851 record from the database, but because customer 851 was already loaded in the context, it ignored the newer record from the database (more details).
All of this is saying that if you make a query, it checks the primary key first to see if it already has it in the cache. If so, it uses what's in the cache.
How do you avoid it? The first is to make sure you're not keeping your DbContext object alive too long. DbContext objects are only designed to be used for one unit of work. Bad things happen if you keep it around too long, like excessive memory consumption.
Do you need to retrieve data to display to the user? Create a DbContext to get the data and discard that DbContext.
Do you need to update a record? Create a new DbContext, update the record and discard that DbContext.
This is why, when you use EF Core with dependency injection in ASP.NET Core, it is created with a scoped lifetime, so any DbContext object only lives for the life of one HTTP request.
In the rare case you really do need to get fresh data for a record you already have an object for, you can use EntityEntry.Reload()/EntityEntry.ReloadAsync like this:
myContext.Entry(myProduct).Reload();
That doesn't help you if you only know the ID though.
If you really really need to reload an entity that you only have the ID for, you could do something weird like this:
private Product GetProductById(int id) {
//check if it's in the cache already
var cachedEntity = myContext.ChangeTracker.Entries<Product>()
.FirstOrDefault(p => p.Entity.Id == id);
if (cachedEntity == null) {
//not in cache - get it from the database
return myContext.Products.Find(id);
} else {
//we already have it - reload it
cachedEntity.Reload();
return cachedEntity.Entity;
}
}
But again, this should only be used in limited cases, when you've already addressed any cases of long-living DbContext object because unwanted caching isn't the only consequence.
Ok, I have the same problem and finally found the answer,
You doing everything right, that's just how EF works.
You can use .AsNoTracking() for your purposes:
return myContext.Products.AsNoTracking().Find(id)
make sure you addedusing Microsoft.EntityFrameworkCore; at the top.
It works like a magic
So here's the scenario:
DDD states that you use a repository to get the aggregate root, then use that to add/remove to any collections it has.
Adding is simple, you simple call .Add(Item item) on the Collection you wish to add to. A new row is added to the database when you save. However, deleting is different - calling .Remove(Item item) doesn't remove the item from the database, it simply removes the foreign key. So while, yes, it is technically no longer part of the collection anymore, it's still in the database.
Reading around, the only solution is to delete it using the data context. But according to DDD the domain object shouldn't be aware of the data context so therefore deleting will have to be done outside of the domain.
What is the right way to go about this? Or Is leaving the database full of orphans acceptable (perhaps running a routine to clear them out)?
I've solved this problem in the application I'm currently working on by using domain events; a DDD concept Eric Evans said should have been in his book.
While domain objects aren't allowed to know about the object context, an IDomainEventHandler is - I've therefore got a DomainObjectDeletionHandler which deletes 'removed' objects from the object context before control returns to my application layer and the changes are saved.
For more information, I've written a blog about my implementation of domain events and how I approached hooking everything together.
Hope that helps :)
Edit
For example, if you have an Order class which has an OrderItems collection of type OrderItem:
public class Order
{
// Other stuff
public void RemoveOrderItem(int orderItemId)
{
var orderItemToRemove = OrderItems.First(oi => oi.Id == orderItemId)
OrderItems.Remove(orderItemToRemove);
DomainEvents.Raise(new OrderItemRemoved(orderItemToRemove));
}
}
When removing a child entity from a collection, EF will leave it as orphan, removing just the foreign key.
If you don't want to explicitly remove it using the DbContext, you can use what it is called "Identifying Relationship" (http://msdn.microsoft.com/en-us/library/ee373856.aspx at the bottom).
The trick is to set a composite primary key on the child including the parent's primary key.
Once you do that, when removing the entity from the parent's collection, it will be removed from the table as well.
I do not know if this is by design, but if a detail object has a composite key containing its master object's key columns, it will be automatically deleted if you remove it from the master object's collection. If you have an Order object with an OrderID key and ICollection OrderLines navigation property, give OrderLine a composite key containing OrderID and OrderLineID.
But since I do not know if I can rely on that, the solution I've used myself is to let EF handle it the way it does, and fix up 'detached' (not in EF terms) detail objects on the call to SaveChanges(), enumerating over all modified entities and changing the state to deleted as appropriate.
I solved this scenario by configuring the reference column as required and the delete behavior as Cascade
Example:
modelBuilder.Entity<AggregateRoot>()
.HasMany(x => x.Items)
.WithOne()
.IsRequired()
.OnDelete(DeleteBehavior.Cascade);
In this case, EF Core (6.x) no longer set the reference column to NULL, but deleted the record just by removing the Item from the Items collection of the aggregate root.
The decisive configuration here was the delete behavior Cascade.
Why not use two repositories?
var parent = ParentRepo.Get(parentId);
parent.Children.Remove(childId); // remove it from the property Collection
ChildRepo.Delete(childId); // delete it from the database
ParentRepo.Commit(); // calls underlying context.SaveChanges()
Assuming you're sharing contexts via IOC/DI, calling commit with one repo will commit for both, otherwise just call ChildRepo.Commit as well.