Adding entity to DbContext not possible - c#

I'm trying to copy an entity from one DbContext to another.
So I'm loading and detaching the entity before calling the save methods of the other DbContext.
EDIT:
First of all I'm loading the entity with the ID of the UserDT, then I'm detaching it from the old DbContext.
Finally I'l set SET IDENTITY_INSERT tUsers ON, save the entity and then turn SET IDENTITY_INSERT tUsers OFF.
var userEntity = DataContext.GetById<User>(id);
DataContext.EnsureDetached(userEntity);
offlineContext.ToggleIdentityInsert(true, typeof(User));
offlineContext.SetSaved(userEntity);
offlineContext.SaveChanges();
offlineContext.ToggleIdentityInsert(false, typeof(User));
Before DbContext.SaveChanges() I call this method (please note that this method is in a class inheriting from DbContext):
public T SetSaved<T>(T obj) where T : class, IEntity
{
var isNew = base.Set<T>().All(t => t.Id != obj.Id);
T ret = default(T);
var entry = Entry(obj);
if (isNew)
{
entry.State = EntityState.Added;
ret = base.Set<T>().Add(obj);
}
else
{
entry.State = EntityState.Modified;
}
return ret;
}
All entities I've used so far worked with this method.
Our User entity causes a NullReferenceException when calling this method, because the the properties CurrentValues and OriginalValues throw an InvalidOperationException saying that the entity is not attached to the current DbContext.
With this problem I can access the State and then add the obj to the DbContex.
EDIT:
I've forgot to say that the above code works on the second try. In my scenario I'm pressing the login button and the logged in user should be copied (offline DB). The first time I'll get the error, the second time I press the login button everything works fine.
Do you know what I could have forgotten?

Related

ChangeTracker.StateChanged of DbContext gets triggered when calling .Entry()

I implemented similar solution on how we can modify created and updated date upon saving data through EF Core as what is suggested here Populate Created and LastModified automagically in EF Core.
void OnEntityStateChanged(object sender, EntityStateChangedEventArgs e)
{
if (e.NewState == EntityState.Modified && e.Entry.Entity is IHasCreationLastModified entity)
entity.LastModified = DateTime.Now;
}
At first I thought this will be triggered only when SaveChanges() is called. But apparently it is also called on Entry()
// Get entity
var student = _dbContext.Students.Find(studentId);
// Modify student object
student.Name = "New student name";
// Called Entry(), trigger ChangeTracker.StateChanged
var entry = _dbContext.Entry(student);
// Doesn't trigger ChangeTracker.StateChanged
_dbContext.SaveChanges();
I found that ChangeTracker.StateChanged is triggered when _dbContext.Entry(student) is called. Then it doesn't get triggered again when _dbContext.SaveChanges() is called. And it also passes the condition above if (e.NewState == EntityState.Modified && e.Entry.Entity is IHasCreationLastModified entity).
My assumption why it is not triggered again when SaveChanges() is called, because there is no new update to the entity after Entity() is called.
This results in the LastModified property being assigned when .Entry(student) is called, instead of when .SaveChanges() is called.
Is there a way to only update LastModified property once when SaveChanges is called on the scenario above?
I suggest that you could override you SaveChanges method in your dbContext. You could refer to below code that I usually use.
public class ForumContext : DbContext
{
public ForumContext(DbContextOptions<ForumContext> options) : base(options)
{
}
//other settings
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
foreach (var entry in ChangeTracker.Entries())
{
switch (entry.State)
{
case EntityState.Added:
((BaseEntity)entry.Entity).AddedDate = DateTime.Now;
((BaseEntity)entry.Entity).LastModified = DateTime.Now;
break;
case EntityState.Modified:
((BaseEntity)entry.Entity).LastModified = DateTime.Now;
break;
case EntityState.Deleted:
entry.State = EntityState.Modified;
entry.CurrentValues["IsDeleted"] = true;
break;
}
}
return base.SaveChanges(acceptAllChangesOnSuccess);
}
I thought you might like to know why you were getting the the events you saw in your question.
When you execute the line student.Name = "New student name";, then, by default, nothing happens because EF Core hasn't called the ChangeTracker.DetectChanges method yet so it doesn't know anything has changed.
But the call to var entry = _dbContext.Entry(student); then runs a version of the ChangeTracker.DetectChanges - see the code below taken from the EF Core code.
public virtual EntityEntry<TEntity> Entry<TEntity>([NotNull] TEntity entity) where TEntity : class
{
Check.NotNull<TEntity>(entity, nameof (entity));
this.CheckDisposed();
EntityEntry<TEntity> entityEntry = this.EntryWithoutDetectChanges<TEntity>(entity);
//My comment - this runs a version of the DetectChanges method.
this.TryDetectChanges((EntityEntry) entityEntry);
return entityEntry;
}
EF Core's Entry method does this because you might ask for the State of the entity and therefore it has to call DetectChanges to make sure its up to date.
Now, it turns out that if you do the following
student.Name = "New student name";
_dbContext.SaveChanges();
Then (in EF Core 5 preview, but I think it is the same in EF Core 3.1) you get two events.
OldState.EntityState == Unchanged, newState.EntityState == Modified - that is triggered by the call to DetectChanges.
OldState.EntityState == Modified, newState.EntityState == Unchanged - that is triggered by SaveChanges when it set the state to say the database matches the entity class.
If you do the following
student.Name = "New student name";
var entry = _dbContext.Entry(student);
_dbContext.SaveChanges();
Then you would get the same events. The DetectChanges would be called twice (once by Entry and once by SaveChanges), but there is no change in the State on the second call the DetectChanges
You can see this in my unit tests in the repo I am writing to support my book Entity Framework Core in Action. I'm writing the section on these events and found your question and though I would answer it.
I hope it helps you understand what is going on, but I should say that the other answers suggesting overriding SaveChanges is a better solution than using these events.

EF, Update doesnt work, it says Entities may have been modified or deleted since entities were loaded.

I am trying to create a layered MVC project but I am having an UPDATE problem in EF. I am getting the following error.
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded.
I have DAL and BusinessLayer. In DAL, I have the following code for UPDATE
public void Update(params T[] entities)
{
using (var context = new BorselDBEntities())
{
foreach (T entity in entities)
{
context.Entry(entity).State = EntityState.Modified;
}
context.SaveChanges();
}
}
and this is how I call the DAL from BusinessLayer
public void UpdateProduct(params Product[] products)
{
_productRepository.Update(products);
}
Why am I getting the error above and what could I do to fix it?
One common reason is that context.Entry(entity) fails to get the entity which you want to update.
When you're debugging, see if context.Entry(entity) returns the entity; easily done by putting it on a separate line and setting a breakpoint afer:
public void Update(params T[] entities)
{
using (var context = new BorselDBEntities())
{
foreach (T entity in entities)
{
var myEntity = context.Entry(entity);
myEntity.State = EntityState.Modified;
}
context.SaveChanges();
}
}
If it's not, you'll need to work back through your code and work out why it's not able to pick it up. Often this will be because the identity/primary key column is not set on 'entity'.
E.g. in an MVC application, if you have an Edit/update form, remember to have a
#Html.HiddenFor(model => model.Id)
It is necessary to call the Attach method on the DbSet for the entity you are updating before you can change it's State. The local DbContext needs to contain the Entity or it will not know what changes to track.
https://msdn.microsoft.com/en-us/library/gg696261(v=vs.103).aspx

Entity Framework 6: update entity already existing in ChangeTracker

I use repository with Update method. In this method I need to check some conditions, finding updating entity in DB before actual update. I use one context for both - Find and Update operations inside one transaction. But when I try to update entity I get an exception:
"Attaching an entity of type 'MyNamespace.MyEntityType' failed because another entity of the same type already has the same primary key value"
public void Update(SomeType entity)
{
using (var context = new MyEntities())
{
using (var transaction = context.Database.BeginTransaction(IsolationLevel.RepeatableRead))
{
try
{
// check conditions
var root = context.MyEntitySet
.Where(e => e.Parent == null); // entity.Parent == null
if(root ...)
; // return
// HERE I got an error described above
context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
}
}
I need to load entity with same Id from DB first, check some conditions and if ok, update entity. But after "Get" entity attaches to ChangeTracker with "Detached" state and when I try to change state to "Modified" I got an error.
I've already tried:
if (IsAttached(entity, out attachedEntity))
context.Entry(attachedEntity).CurrentValues.SetValues(entity);
but my original entity object stays unchanged with DB generated values. State = EntityState.Modified works as I need, but how can I remove preloaded entity from ChangeTracker?

Adding and deleting many-to-many using DbContext API

I am using Entity Framework and DbContext API do build my application but I am having trouble working with objects with many-to-many relations. A simplified save-method could look like this
public void MyObj_Save(MyObj myobj)
{
DbContext.Entry(myobj).State = EntityState.Added;
DbContext.SaveChanges();
}
This code works fine, but if MyObj contains a many-to-many relation this is not saved. I know from using the old POCO API, that I needed to attach the related objects to the context but I cannot find a way to do this correctly with the DbContext API - a simplified example below
public void MyObj_Save(MyObj myobj, List<OtherObj> otherObjList)
{
foreach (OtherObj otherObj in otherObjList)
{
DbContext.OtherObj.Attach(otherObj);
myobj.OtherObj.Add(otherObj);
}
DbContext.Entry(myobj).State = EntityState.Added;
DbContext.SaveChanges();
}
I get no error, but the relations are not saved. What to do?
I quote your (important!) comment:
The objects I send to the method are attached and EntityState is
Unchanged. The configuration of my DbContext is, that I have disabled
AutoDetectChangesEnabled...
So, your code would look like this:
DbContext.Configuration.AutoDetectChangesEnabled = false;
DbContext.Entry(myobj).State = EntityState.Unchanged;
foreach (OtherObj otherObj in otherObjList)
DbContext.Entry(otherObj).State = EntityState.Unchanged;
// entering MyObj_Save method here
foreach (OtherObj otherObj in otherObjList)
{
//DbContext.OtherObj.Attach(otherObj); // does not have an effect
myobj.OtherObj.Add(otherObj);
}
DbContext.Entry(myobj).State = EntityState.Added;
DbContext.SaveChanges();
And this indeed doesn't work because EF doesn't notice that you have changed the relationship between myobj and the list of OtherObj in the line myobj.OtherObj.Add(otherObj); because you have disabled automatic change detection. So, no entries will be written into the join table. Only myobj itself will be saved.
You cannot set any state on an entity to put the state manager into a state that the relationship is saved because it is not an entity state which is important here but a relationship state. These are separate entries in the object state manager which are created and maintained by change detection.
I see three solution:
Set DbContext.Configuration.AutoDetectChangesEnabled = true;
Call DetectChanges manually:
//...
DbContext.Entry(myobj).State = EntityState.Added;
DbContext.ChangeTracker.DetectChanges();
DbContext.SaveChanges();
Detach the new myobj from the context before you set it into Added state (this feels very hacky to me):
// entering MyObj_Save method here
DbContext.Entry(myobj).State = EntityState.Detached;
foreach (OtherObj otherObj in otherObjList)
//...
Maybe it is possible - by getting to the ObjectContext through the IObjectContextAdapter - to modify the relationship entries in the object state manager manually but I don't know how.
In my opinion, this procedure to manipulate entity (and relationship) states manually is not the way you are supposed to work with EF. AutoDetectChangesEnabled has been introduced to make working with EF easier and safer and the only recommended situation to disable it is a high performance requirement (for example for bulk inserts). If you disable automatic change detection without need you are running into problems like this which are difficult to detect and it requires advanced knowledge of EF's inner workings to fix those bugs.
public void MyObj_Save(MyObj myobj, List<OtherObj> otherObjList)
{
DbContext.Entry(myobj).State = EntityState.Added;
foreach (OtherObj otherObj in otherObjList)
{
(((System.Data.Entity.Infrastructure.IObjectContextAdapter)DbContext)
.ObjectContext)
.ObjectStateManager
.ChangeRelationshipState(myobj, otherObj,
q => q.OtherObjs, EntityState.Added);
}
DbContext.SaveChanges();
}
Again, it is a simplified and not a real life example!

How to save combined (new+modified) detached entities in Entity Framework?

What is the proper and fast way to save combined new and modified detached POCO entities?
I was thinking about these methods:
private void Method_2(IList<Entity> entities) //detached entities
{
//This method is using SELECT to check if entity exist
using (var context = new ModelContainer())
{
foreach (Entity entity in entities)
{
var foundEntity = context.CreateObjectSet<Entity>().SingleOrDefault(t => t.Id == entity.Id);
context.Detach(foundEntity); //Remove it from ObjectStateManager
if (foundEntity != null)//It is modified entity
{
context.AttachTo("EntitySet", entity); //Attach our entity
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified); //We know it exists
}
else//It is new entity
{
context.CreateObjectSet<Entity>().AddObject(entity);
}
}
context.SaveChanges();
}
}
private void Method_1(IList<Entity> entities) //detached entities
{
//This method doesn't select anything from DB, but i have ta call Savechanges after each object
using (var context = new ModelContainer())
{
foreach (Entity entity in entities)
{
try
{
context.AttachTo("EntitySet", entity);
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
context.SaveChanges();
}
catch (OptimisticConcurrencyException)
{
context.ObjectStateManager.ChangeObjectState(entity, EntityState.Added);
context.SaveChanges();
}
}
}
}
When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.
Well i agree with this statement if you found yourself in situation when you need to use EF code like this in EF definitely something is wrong with you decision. I have chosen wrong tool for this job.
When you are working in detached environment you have to know which entity was added and which is modified - it is your responsibility to keep this information and provide it to ObjectContext.
The very easy way is:
foreach (var entity in entities)
{
if (entity.Id == 0) // 0 = default value: means new entity
{
// Add object
}
else
{
// Attach object and set state to modified
}
}
The example requires that you have some db auto-generated primary key (Id).
Your Method 2 is possible with some modifications. It is not needed to detach entity when you load it. Instead use ApplyCurrentValues. The approach with loading entity first is very usefull when you decide to work with object graphs instead of single entity. But in the case of object graph you have to do synchronization manually. ApplyCurrentValues works only for scalar (non navigation) properties. You can try to futher optimize your method to load needed enitites in single roundtrip to database instead of loading entities one by one.
Your Method 1 is terrible solution. Using exceptions raised on database server to control program flow is bad approach.
I agree with #Ladislav - Method_1 is a bad approach. Let the database raise exceptions which are caught by EF - don't try and swallow these exceptions yourself.
Your on the right track with Method 1.
Here is how i do it - as i also have a detached context (POCO's, no change tracking, ASP.NET MVC).
BLL Interface: (note i have TPT in my model, hence generics. "Post" is abstract)
void Add(Post post);
void Update<TPost>(TPost post) where TPost : Post, new();
The new() constraint is crucial - you'll see why shortly.
I won't show how i do "Add", because it's simple as you think - AddObject(entity);
The "Update" is the tricky part:
public class GenericRepository<T> : IRepository<T> where T : class
{
public void Update<T2>(T2 entity) where T2: class, new()
{
var stub = new T2(); // create stub, now you see why we need new() constraint
object entityKey = null;
// ..snip code to get entity key via attribute on all domain entities
// once we have key, set on stub.
// check if entity is already attached..
ObjectStateEntry entry;
bool attach;
if (CurrentContext.ObjectStateManager.TryGetObjectStateEntry(CurrentContext.CreateEntityKey(CurrentContext.GetEntityName<T>(), stub), out entry))
{
// Re-attach if necessary.
attach = entry.State == EntityState.Detached;
}
else
{
// Attach for first time.
attach = true;
}
if (attach)
CurrentEntitySet.Attach(stub as T);
// Update Model. (override stub values attached to graph)
CurrentContext.ApplyCurrentValues(CurrentContext.GetEntityName<T>(), entity);
}
}
And that works for me.
As for the entity key, i have used attributes on my domain classes. An alternative (which i'm about to move to), is have all my domain entities implement an interface, which specifies that all domain entities must have a property called "EntityKey". Then i'll use that interface on my constraints. Basically, i needed a dynamic way to create stub entities in a generic repository.
I don't personally like the idea of "checking the ID, if its > 0 then it's an update". Because i'm working with ASP.NET MVC, if i (or another developer) forgets to bind the ID to the View, it won't be passed through, so even though it may be an update, because the ID == 0 it will be added.
I like to be explicit about the operations. This way, i can perform Add/Update seperate validation logic.
Perhaps take a look at Self Tracking POCO entities. IMHO they are perfect for any scenario that requires the entity to be separated from the context. It takes care of all the plumbing code for you.

Categories