I have an entity which is not connected to my dbcontext. I want to change that. However there is already another instance of the same entity attached to dbcontext. If I just add my new entity, I get an error, that an entity with the same primary key is already attached.
I tried multiple different variants of removing the old entity from dbcontext without any success. How can I replace the old instance with the new one?
Note: I don't want to copy the values, I want to attach this very instance of my new entity to dbcontext.
var entity = new MyEntity { Id = 1 };
var logicalDuplicate = dbcontext.Set<MyEntity >().Local
.FirstOrDefault(e => e.Equals(entity));
if (logicalDuplicate != null)
{
// remove logicalDuplicate from dbcontext
}
dbcontext.MyEntity.Attach(entity);
For clarification: I have overridden Equals to check for Id instead of reference.
Try this:
if (logicalDuplicate != null)
{
dbcontext.Entry(logicalDuplicate).State = EntityState.Detached;
dbcontext.MyEntity.Attach(entity);
dbcontext.Entry(entity).State = EntityState.Modified;
}
else
{
dbcontext.MyEntity.Add(entity);
}
How to get related entries
I investigated that and want to share with my results.
I used reflection as short way to get entity properties names. But it's possible to get it without reflection as mentioned #Florian Haider. You can use
answer and this.
// Found loaded related entries that can be detached later.
private HashSet<DbEntityEntry> relatedEntries;
private DbContext context;
private List<string> GetPropertiesNames(object classObject)
{
// TODO Use cache for that.
// From question https://stackoverflow.com/questions/5851274/how-to-get-all-names-of-properties-in-an-entity
var properties = classObject.GetType().GetProperties(BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.Instance);
return properties.Select(t => t.Name).ToList();
}
private void GetRelatedEntriesStart(DbEntityEntry startEntry)
{
relatedEntries = new HashSet<DbEntityEntry>();
// To not process start entry twice.
relatedEntries.Add(startEntry);
GetRelatedEntries(startEntry);
}
private void GetRelatedEntries(DbEntityEntry entry)
{
IEnumerable<string> propertyNames = GetPropertiesNames(entry.Entity);
foreach (string propertyName in propertyNames)
{
DbMemberEntry dbMemberEntry = entry.Member(propertyName);
DbReferenceEntry dbReferenceEntry = dbMemberEntry as DbReferenceEntry;
if (dbReferenceEntry != null)
{
if (!dbReferenceEntry.IsLoaded)
{
continue;
}
DbEntityEntry refEntry = context.Entry(dbReferenceEntry.CurrentValue);
CheckReferenceEntry(refEntry);
}
else
{
DbCollectionEntry dbCollectionEntry = dbMemberEntry as DbCollectionEntry;
if (dbCollectionEntry != null && dbCollectionEntry.IsLoaded)
{
foreach (object entity in (ICollection)dbCollectionEntry.CurrentValue)
{
DbEntityEntry refEntry = context.Entry(entity);
CheckReferenceEntry(refEntry);
}
}
}
}
}
private void CheckReferenceEntry(DbEntityEntry refEntry)
{
// Add refEntry.State check here for your need.
if (!relatedEntries.Contains(refEntry))
{
relatedEntries.Add(refEntry);
GetRelatedEntries(refEntry);
}
}
Edit This finds the original product, removes it, and adds the new one:
static void UpdateDatabase()
{
Context context = new Context();
Product product1 = context.Products.Find(1);
context.Products.Remove(product1);
Product product2 = new Product(){ProductId = 1, Name = "Product2"};
context.Products.Add(product2);
context.SaveChanges();
}
Best way to salve this problem is
db is my database Object
updateprice is my database entity object
ep is my old same database entity object
db.Entry(updateprice).CurrentValues.SetValues(ep);
Related
I've got method AddOrUpdateFruitMetaData which should add or update record in FruitMetaData table.
Unfortunately I'm keep getting an errors:
Violation of PRIMARY KEY constraint 'PK_FruitMetaData'. Cannot insert duplicate key in object 'dbo.FruitMetaData'. The duplicate key value is (0, COLOR). The statement has been terminated.
How this is even possible even when I'm trying to find existing record before?
Also using AddOrUpdate() from System.Data.Entity.Migrations instead of Add() is not helping me.
Call stack looks that:
In my main class in Parallel.ForEach with multiple for loop looking like that:
Parallel.ForEach(args)
{
for(something)
{
var fruit = new Fruit();
fruit.FruitId = uniqueId;
UpdateOrCreateFruitMetaData(fruit, "Color", "Blue")
}
}
I'm calling method:
private void UpdateOrCreateFruitMetaData(Fruit fruit, string metaType, string value)
{
var fruitMetaData = new FruitMetaData();
fruitMetaData.FruitId = fruit.FruitId;
fruitMetaData.MetaType = metaType;
fruitMetaData.Value = value;
using (var db = Context.DB)
{
db.AddOrUpdateFruitMetaData(fruitMetaData);
}
}
That method is using Context.DB which is object containing new DBEntity() and also Dispose(). So my Entity Context is disposed every time.
Then I'm calling next method AddOrUpdateFruitMetaData():
AddOrUpdateFruitMetaData(FruitMetaData fruitMetaData)
{
lock (thisLock)
{
try
{
var fmd = db.FruitMetaData.Where(x => x.FruitId == fruitMetaData.FruitId)
.Where(x => x.MetaType == fruitMetaData.MetaType)
.FirstOrDefault();
if (fmd == null)
db.FruitMetaData.Add(fruitMetaData);
else
fmd.Value = fruitMetaData.Value;
db.SaveChanges();
}
catch (Exception ex)
{
Log.Error($"AddOrUpdateFruitMetaData. FruitId:{fruitMetaData.FruitId} MetaType:{fruitMetaData.MetaType}", ex);
}
}
}
[EDIT] To explain using (var db = Context.DB) :
My Context class contains DbData DB Property which looks like that :
public DbData DB
{
get { return new DbData(); }
}
And DbData class looks like that:
public class DbData : IDisposable
{
private DBEntity db;
public DbData()
{
db = new FruitDBEntity();
}
// This class contains that problematic method
AddOrUpdateFruitMetaData(FruitMetaData fruitMetaData)(...)
}
So each time I'm getting Context.DB property I am actually creating new Entity Context.
I have stumbled upon a problem with Entity Framework this morning.
I have following code mapping a modified entity and saving it into database.
public Group Save(Group x)
{
using (var db = new HostContext())
{
db.Projects.Attach(x.Project);
if (x.ID != 0)
{
db.AttachableObjects.Attach(x);
var manager = ((IObjectContextAdapter)db).ObjectContext.ObjectStateManager;
manager.ChangeObjectState(x, EntityState.Modified);
}
else
{
db.AttachableObjects.Add(x);
}
db.SaveChanges();
return x;
}
}
I call Save method with existing group as a parameter. Group contains one user I want to add as a member.
The method finishes successfully, however the relationship is not persisted in database.
Any help is very appreciated.
EDIT: These are my classes
class User : AttachableObject
{
...
private List<Group> memberof;
[DataMember]
[InverseProperty("Members")]
public List<Group> MemberOf
{
get { return memberof; }
set { memberof = value; }
}
...
}
class Group : AttachableObject
{
...
private List<User> members;
[DataMember]
[InverseProperty("MemberOf")]
public List<User> Members
{
get { return members; }
set { members = value; }
}
...
}
EDIT2: This is where the Save method is called
public Group AcceptInvite(int id)
{
var mapper = new InviteMapper();
var userMapper = new UserMapper();
var groupMapper = new GroupMapper();
var invite = mapper.Find(id);
if (invite != null)
{
var group = groupMapper.Find(invite.GroupID);
var user = userMapper.Find(invite.InviteeID);
group.Members.Add(user);
mapper.Delete(invite.ID);
return groupMapper.Save(group);
}
return null;
}
EDIT3: My mappers
public class GroupMapper
{
public Group Find(int id)
{
using (var db = new HostContext())
{
return db.AttachableObjects
.Include("Project")
.OfType<Group>().FirstOrDefault(x => x.ID == id);
}
}
}
The rest of the mappers is the same, only using their own tables.
You are not changing the relationship info of Project, you are only setting x to modified, relationship info must be changed explicitly.
So x.Project must have some property that points back to Group, you need to set it so the change is recorded.
I am guessing that x is resurrected via some deserialization process?
I want to write a function, which receives any Entity object and finds the current value by automatically determined primary key and updates it.
Could you please point me any direction.
public void Update(object entity)
{
using (var _db = new MyEntities())
{
var table = _db.Set(entity.GetType());
//Here we should somehow find the entity object which primary key coincides with the one of entity
var entityObj = table.FindObjectByPrimaryKey(entity);
entityObj.CopyDataFrom(entity);
_db.SaveChanges()
}
}
Is this possible?
you can find primary keys of an entity like this
ObjectContext objectContext = ((IObjectContextAdapter)_db).ObjectContext;
ObjectSet<YourEntity> set = objectContext.CreateObjectSet<YourEntity>();
IEnumerable<string> keyNames = set.EntitySet.ElementType
.KeyMembers
.Select(k => k.Name);
and then use reflection to access their values.
Found the answer.
public void Update(object entity)
{
using (var _db = new MyEntities())
{
_db.Entry(entity).State = System.Data.Entity.EntityState.Modified;
_db.SaveChanges();
}
}
Please advise, I'm using generic repository with UOW, and I got this error when I'm testing my InsertOrUpdate method.(I'm new in both c# and EF)
Result Message:
Test method UnitTestProject1.ManifestUOW.ManifestUOWTest threw exception:
System.InvalidOperationException: Attaching an entity of type 'DomainClasses.ManifestDetail'
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.
My generic repository
public T FindById(object id)
{
return _set.Find(id);
}
public void Add(T entity)
{
DbEntityEntry entry = this._ctx.Entry(entity);
if (entry.State != EntityState.Detached)
{
entry.State = EntityState.Added;
}
else
{
this._set.Add(entity);
}
}
public void Update(T entity)
{
DbEntityEntry entry = this._ctx.Entry(entity);
if (entry.State != EntityState.Detached)
{
this._set.Attach(entity);
}
entry.State = EntityState.Modified;
}
public void InsertOrUpdate(T entity, object id)
{
var _Record = FindById(id);
if (_Record != null)
{
Update(entity);
}
else
{
Add(entity);
}
}
My test implementation
[TestMethod]
public void ManifestUOWTest()
{
ApplicationUOW appUOW = new ApplicationUOW();
ManifestDetail manD=new ManifestDetail();
for (var i = 20; i <= 22; i++)
{
manD = new ManifestDetail();
manD.ID = "kkke" + i;
manD.ManifestID = "kkke";
manD.ModifiedDate = DateTime.Now;
manD.PriorityID = 1;
manD.JobNo = "8888777";
manD.PartNo = "ppppp";
manD.OpSeq = "9000";
manD.QTY = 9;
manD.Comment = "";
manD.LitNO = "Lit no";
appUOW.ManifestDetails.InsertOrUpdate(manD, manD.ID);
}
var man = new Manifest();
man.ID = "kkke";
man.CreatedDate = DateTime.Now;
man.ManifestStateID = 2;
man.MFBldgID = 1;
man.MFDestBldgID = 2;
man.UserID = "X6344";
appUOW.Manifests.InsertOrUpdate(man, man.ID);
appUOW.SaveChanges();
}
My UOW
namespace DataLayer
{
public class ApplicationUOW:IDisposable
{
private AuditorStationDB _context = new AuditorStationDB();
private IRepository<Manifest> _manifests = null;
public IRepository<Manifest> Manifests
{
get
{
if (this._manifests == null)
{
this._manifests = new GenericRepository<Manifest>(this._context);
}
return this._manifests;
}
}
private IRepository<ManifestDetail> _manifestDetails = null;
public IRepository<ManifestDetail> ManifestDetails
{
get
{
if (this._manifestDetails == null)
{
this._manifestDetails = new GenericRepository<ManifestDetail>(this._context);
}
return this._manifestDetails;
}
}
public void SaveChanges()
{
this._context.SaveChanges();
}
public void Dispose()
{
if (this._context != null)
{
this._context.Dispose();
}
}
}
}
The problem you are having is by your InsertOrUpdate() method calling the Update() method.
You first asked EF to locate your entity by Id, so at this point, in EF it is tracking that entity.
Then you pass in the T entity into Update(), in which your Update() method ask it to Attach() it to the EF.
Now the EF already tracked a same entity at step 1, you are trying to ask it to Attach() same entity(both
entity has same primary key) at step 2, that is why it failed.
So to solve your problem, there is possible 2 ways (not tested):
don't use FindById() to determine InsertOrUpdate() - maybe if id = 0 will mean need insert else update.
before calling Update(), detach your entity FindById() - this._ctx.Entry(entity).State = EntityState.Detached
Although in this article (http://msdn.microsoft.com/en-us/data/jj592676.aspx), it is not talking about Generic repository, but the idea of how to implement InsertOrUpdate method in EF can be found at bottom.
So basically I'm trying to do this sequence of events in entity framework.
Create a new account
Get an existing account
Move all the data from the old account to the new account (transactions, users, etc)
Delete the old account
I'm doing this all in 'one go', inside a single ObjectContext.
It fails when I call SaveChanges on the context. I get an foreign key constraint error.
I checked this in SQL profiler and it turns out that entity framework isn't sending any of the updates, just the selected and then a delete.
I kinda understand WHY it is working like that but there must be some way to force it do work properly, without having to call SaveChanges() twice or something.
Hopefully.
My merge function basically looks like this
public void Merge(Account newAccount, Account oldAccount)
{
// ...
foreach (var user in oldAccount.Users.ToList())
{
oldAccount.Users.Remove(user);
newAccount.Users.Add(user);
}
// ...
_unitOfWork.Accounts.Delete(oldAccount);
}
The objects are POCO objects created by the E.F.4 POCO Entity Generator. To avoid pasting the entire class here's just one of the association properties with it's 'fixup' function.
public virtual ICollection<User> Users
{
get
{
if (_users == null)
{
var newCollection = new FixupCollection<User>();
newCollection.CollectionChanged += FixupUsers;
_users = newCollection;
}
return _users;
}
set
{
if (!ReferenceEquals(_users, value))
{
var previousValue = _users as FixupCollection<User>;
if (previousValue != null)
{
previousValue.CollectionChanged -= FixupUsers;
}
_users = value;
var newValue = value as FixupCollection<User>;
if (newValue != null)
{
newValue.CollectionChanged += FixupUsers;
}
}
}
}
private void FixupUsers(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (User item in e.NewItems)
{
item.Account = this;
}
}
if (e.OldItems != null)
{
foreach (User item in e.OldItems)
{
if (ReferenceEquals(item.Account, this))
{
item.Account = null;
}
}
}
}
You use object as reference to add and remove inside the for loop , the best solution to get object by key instate of using an object when add.
oldAccount.Users.Remove(user);
newAccount.Users.Add(users.FirstOrDefault(t=>t.ID = user.Id));
Okay figured out the solution. #CodeGorilla's comment gave me a hint.
Essentially I just need to call
_context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave);
before trying to delete the account. This forces entity framework to do all the updates in the database. After that there is no problem with deleting it.