Struggling to implement a generic InsertOrUpdate() on DbContext - c#

I am struggling to implement a very basic "InsertOrUpdate()" method on a DbContext. I tried following the advice in this post.
private static bool SaveItem<TEntity>(Object objToSave, TEntity existing = null) where TEntity : class
{
try
{
/////////////////////////////////////////
// BLOCK A
if(existing != null)
db.Set<TEntity>().Attach(existing);
/////////////////////////////////////////
db.Entry(objToSave).State = existing!=null ? EntityState.Modified : EntityState.Added;
db.SaveChanges();
} catch(Exception e)
{
Logger.Exception(e);
return false;
}
return true;
}
An example call to is the following:
SaveItem(item, db.MyInstances.Where(dbItem => dbItem.ID == item.ID).FirstOrDefault());
Some definitions:
class MyInstancesDbContext: DbContext { ... }
private static MyInstancesDbContext db = new MyInstancesDbContext();
As I understand it, in that call the .Where() will cause an attachment of some sort. So I've tried both including the small block of code labeled "A" and removing it. Both of which give me the same kind of error:
System.InvalidOperationException: Attaching an entity of type '...MyInstance' failed because a
nother 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 en
tities 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 found this popular related answer to this error where the user suggests using AsNoTracking(), but that instead makes me feel like I don't fundamentally understand something or am trying to ignore some error.
I'd greatly appreciate any advice.

I think what you're missing is that the DbContext tracks entities, and it doesn't like tracking entities of the same type with the same primary key.
When you call this:
db.MyInstances.Where(dbItem => dbItem.ID == item.ID).FirstOrDefault()
you've loaded an entity of MyInstance with primary key == item.ID into the context if it exists in the database.
This line is completely unneeded because existing would already be attached -- but that probably doesn't cause the error.
if(existing != null)
db.Set<TEntity>().Attach(existing);
The problem is probably here:
db.Entry(objToSave).State =
existing != null ? EntityState.Modified : EntityState.Added;
If existing == null, you might be okay, because this line will attach objToSave, but if existing exists, you'll have a problem because you'll be trying to attach objToSave which has the same type and primary key as existing.
Instead, you could try using objToSave to set the values for the attached entity:
db.Entry(existing).CurrentValues.SetValues(objToSave);
So objToSave will not be attached if there is an existing record.
https://msdn.microsoft.com/en-us/data/jj592677.aspx

Related

EF throws exception after second update attempt

There is an update method throws exception but it works fine in first loging after second attempt throws exception with message;
**
Additional information: Attaching an entity of type
'Hsys.InfluenzaTaniBilgisi' 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.
**
Iknow its common and found many smilar issue but I couldnt make it work..
here is code piece;
public void CreateUpdateInfluenzaTani(InfluenzaTaniBilgisi taniBilgisi)
{
using (HsysDbContext con = new HsysDbContext())
{
if (con.InfluenzaTestTanilari.Any(x => x.ICD10TaniKodu == taniBilgisi.ICD10Kodu && x.IsDeleted != true))
{
var taniExist = con.InfluenzaTaniBilgisi.FirstOrDefault(x => x.MuayeneId == taniBilgisi.MuayeneId && x.ICD10K
odu == taniBilgisi.ICD10Kodu && x.IsDeleted != true);
if (taniExist == null)
{
taniBilgisi.ObjectState = Framework.Entities.ObjectState.Added;
Create(taniBilgisi);
}
else
{
taniExist.HastaYasi = taniBilgisi.HastaYasi;
taniExist.HekimTC = taniBilgisi.HekimTC;
taniExist.ObjectState = Framework.Entities.ObjectState.Modified;
Update(taniExist);// throws Exception!
}
}
}
}
taniExist.ObjectState = Framework.Entities.ObjectState.Modified;
This is your issue.
You cannot use the Framework.Entities.ObjectState.Modified; on an existing key, entity frame work won't allow that.
You need to first do this:
taniExist.ObjectState = Framework.Entities.ObjectState.Added;
and then:
taniExist.ObjectState = Framework.Entities.ObjectState.Modified;
Your code will look like this:
taniExist.HastaYasi = taniBilgisi.HastaYasi;
taniExist.HekimTC = taniBilgisi.HekimTC;
taniExist.ObjectState = Framework.Entities.ObjectState.Added;
Update(taniExist);
Framework.Entities.ObjectState.Modified;

EF add List of entites to an existing entity

I am having an issue trying to add a list of entities to an existing entity.
Example -
My object Trade has a List offered and a List selected
When a trade is first created it contains a list of offered items. When someones selects items from the offered they are to be saved in the Selected field.
in my save i did the following
internal void acceptTrade(Trade trade)
{
using(var context = new datacontext())
{
Context.Entry(Trade).State = EntityState.Modified;
Trade.Status = "Accepted";
Foreach(selected)
{
Context.entry(selected).State = EntityState.Added;
}
Context.SaveChanges();
}
}
However, with this i get the following error
Attaching an entity of type item 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
Ive tried to set each of the offered state to unchanged
internal void acceptTrade(Trade trade)
{
Context.Entry(Trade).State = EntityState.Modified;
Trade.Status = "Accepted";
Foreach(selected)
{
Context.entry(selected).State = EntityState.Added;
}
foreach(offered)
{
Context.Entry(offered).State = EntityState.Unchanged;
}
Context.SaveChanges();
}
and i still get the same error.
I even tried to use migrations by doing
db.set<Trade>().AddorUpdate(trade);
and no error occured but nothing was saved or modified.

Adding entity to DbContext not possible

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?

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.

Add a relationship entity in generic/abstract way

I'm trying to write a WCF method that will receive a detached EntityObject from the client and will be able to tell which properties and which relationships were changed for this entity comparing it with what's already in the context.
Of course that if this entity is a new entity or one of its relationships were added/deleted/modified it should also recognize that and act accordingly.
I'm already able to recognize if the entity's relationship is a new one but can't seem to be able to add it correctly. -With every approach I try I get a different exception.
Here is the method I use to update the detached object:
public static void AttachUpdated(this ObjectContext context, EntityObject objectDetached)
{
if (objectDetached.EntityState == EntityState.Detached)
{
object currentEntityInDb = null;
if (context.TryGetObjectByKey(objectDetached.EntityKey, out currentEntityInDb))
{
context.ApplyPropertyChanges(objectDetached.EntityKey.EntitySetName, objectDetached);
//Apply property changes to all referenced entities in context
context.ApplyReferencePropertyChanges((IEntityWithRelationships)objectDetached,
(IEntityWithRelationships)currentEntityInDb); //Custom extensor method
}
else
{
//The entity should be added
//?????
}
}
}
And this is a method I use to update the entity's relationships:
public static void ApplyReferencePropertyChanges(this ObjectContext context,
IEntityWithRelationships newEntity,
IEntityWithRelationships oldEntity)
{
foreach (var oldRelatedEnd in oldEntity.RelationshipManager.GetAllRelatedEnds())
{
var oldRef = oldRelatedEnd as EntityReference;
if (oldRef != null)
{
// this related end is a reference not a collection
var newRef = newEntity.RelationshipManager.GetRelatedEnd(oldRef.RelationshipName, oldRef.TargetRoleName) as EntityReference;
if (newRef.EntityKey != null)
{
oldRef.EntityKey = newRef.EntityKey;
}
else
{
//When oldRed is a 1:Many relationship
//newRef is an EntityReference<TEntity> object
EntityObject entity = newRef.GetType().GetProperty("Value").GetValue(newRef, null) as EntityObject;
oldRef.EntityKey = entity.EntityKey;
}
}
else
{
IRelatedEnd newRelatedEnd = newEntity.RelationshipManager.GetRelatedEnd(oldRelatedEnd.RelationshipName, oldRelatedEnd.TargetRoleName);
foreach (IEntityWithRelationships e in newRelatedEnd)
{
if (!oldRelatedEnd.Contains((e as IEntityWithKey).EntityKey))
{
//this is a new relation and it needs to be added.
//???????
}
else
{
//Find out if relation was modified - and update it if needed
//????????
}
}
IEnumerable entities = oldRelatedEnd as IEnumerable;
}
}
}
How should it be implemented?
Please help :(
Where is your ObjectContext coming from? (I am assuming that this is your Entity Framework database reference)
There may be two problems here:
Firstly, I do not think that ObjectContext is serializable, so if you are sending it to the client and then sending it back, you will get an error.
Secondly, If you are keeping ObjectConext on the server, the server objects are by default per call, not per session, therefore you will be trying to associate your Entity with a new ObjectContext.
In our projects we map Entity framework Objects to data transfer objects in order to send then over WCF. What you are trying to do may be easier (possible?) in the next version of Entity Framework.
I'm not sure what you want to achieve - if you want to save the changes in the server side you can use ADO .Net Data Services. Is that the case?
See Perseus:
Perseus is a small project designed to
explore ways for exchanging graphs of
Entity Framework entities over WCF web
services. The key piece of the project
is EntityBag which stores a graph
of entities along with change tracking
information. Here's hoping no one will
use this to store & transport
something as nasty as Medusa's head.
;-)

Categories