How to update using Entity Framwork by passing in object - c#

How do I update using Entity Framework? I'm passing in the object with the updated values, but I don't see an Update method.
public void UpdateRecipient(Domain.Entities.RecipientEntity recipient)
{
using (EfDbContext context = CreateEfDbContext(recipient.ApplicationId.ToString()))
{
context.Recipients. //?? I don't see an update method
context.SaveChanges();
}
}

Three steps:
Get the item to update from the context
Copy over the updated properties from the entity you pass your update method
Save the changes.
Roughly:
using (EfDbContext context = CreateEfDbContext(recipient.ApplicationId.ToString()))
{
var toUpdate = context.Recipients.SingleOrDefault(r => r.Id == recipient.Id);
if (toUpdate != null)
{
toUpdate.Field1 = recipient.Field1;
// Map over any other field data here.
context.SaveChanges();
}
else
{
// Handle this case however you see fit. Log an error, throw an error, etc...
}
}

There is another way of updating object without re-fetching it from the database again thus by saving cost of a trip to database. The object being attached must have a value for its primary key.
Attach the updated object to the context
Change it's state to 'modified'.
Call SaveChanges() method of the context
Like:
public void UpdateRecipient(Domain.Entities.RecipientEntity recipient)
{
using (EfDbContext context = CreateEfDbContext(recipient.ApplicationId.ToString()))
{
context.Attach(recipient);
context.ObjectStateManager.ChangeObjectState(recipient,EntityState.Modified);
context.SaveChanges();
}
}

If you're updating the record then you'd do something like this:
//Retrieve the entity to be updated
Entity row = context.Recipients.Single(a => a.Id == recipient.Id);
//Update a column
row.Name = recipient.Name;
//Save changes
context.SaveChanges();
If you want to update/add things at the same time then you'd do:
if(!context.Recipients.Any(a => Id == recipient.Id))
{
context.Recipients.Add(recipient);
}
else
{
Entity row = context.Recipients.Single(a => a.Id == recipient.Id);
row.Name = recipient.Name;
}
context.SaveChanges();

Related

EF: how to modify existing entry in DB in N-layered application

Im not EF specialist propably, but I have an issue with it:).
Im doing n-layered business application, so I have Service code and Repository code in my app. In my service code, it read existing User entity, I do update of some properties and it calls Repositiry' method Edit. And there error appears:
Attaching an entity of type
'MobileWallet.Common.Repository.MwbeUserData' 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 Edit method looks like this:
public override void Edit(MwbeUserData entityToUpdate)
{
LogChangeTrackerStateValues("UserUpdate starts");
if (Context.Entry(entityToUpdate).State == EntityState.Detached)
{
DbSet.Attach(entityToUpdate);
}
Context.Entry(entityToUpdate).State = EntityState.Modified;
//fix for User.Address problem
Context.Entry(entityToUpdate.Address).State = EntityState.Modified;
LogChangeTrackerStateValues("UserUpdate ends");
}
I also tried code like this:
public override void Edit(MwbeUserData entityToUpdate)
{
Context.Entry(entityToUpdate).State = EntityState.Modified;
//fix for User.Address problem
Context.Entry(entityToUpdate.Address).State = EntityState.Modified;
}
Record which it being udpated is kept in ChangeTracker
Context.ChangeTracker.Entries().ToList()[1]
but Context.Entry(entityToUpdate).State = Detach for this object.
QUESTION 1: How to solve this?
QUESTION 2: Any good tutorial to understand how to work with EF with business n-layered applications?
Thanks for answers.
UPDATE 1:
New findings:
In Repository Edit method:
Context.ChangeTracker.Entries().ToList()[1].CurrentValues["Firstname"] = "AAA"
Context.ChangeTracker.Entries().ToList()[1].OriginalValues["Firstname"]= "AAA"
but CurrentValue should be BBB, why is not updated? it was updated in Service code which calls Respority Code and updated entity is passed to Repository Edit method.
UPDATE 2:
More about my architecture: I have 3 layes Controler(WEB API), Service and Repository. So my Service method update looks like this:
public bool UpdateUser(MwbeUserUpdateIn userUpdateData)
{
MwbeReturnData<MwbeUserData> userData = repository.Get(userUpdateData.UserId);
// Determine if user exists
if (MwbeResponseCodes.NotFound == userData.Code)
{
return false;
}
MwbeUserData user = userData.Data;
// Check each field to be updated
if (!String.IsNullOrEmpty(userUpdateData.FirstName))
{
user.Firstname = userUpdateData.FirstName;
}
if (!String.IsNullOrEmpty(userUpdateData.MiddleName))
{
user.Middlename = userUpdateData.MiddleName;
}
if (!String.IsNullOrEmpty(userUpdateData.LastName))
{
user.Secondname = userUpdateData.LastName;
}
if (!String.IsNullOrEmpty(userUpdateData.MobileNumber))
{
user.Mobilenumber = userUpdateData.MobileNumber;
}
if (!String.IsNullOrEmpty(userUpdateData.Email))
{
user.Email = userUpdateData.Email;
}
if (null != userUpdateData.BirthDate)
{
user.BirthDate = (DateTime)userUpdateData.BirthDate;
}
// Update Addres fields
if (null != userUpdateData.Address)
{
if (!String.IsNullOrEmpty(userUpdateData.Address.City))
{
user.Address.City = userUpdateData.Address.City;
}
if (!String.IsNullOrEmpty(userUpdateData.Address.Country))
{
user.Address.Country = userUpdateData.Address.Country;
}
if (!String.IsNullOrEmpty(userUpdateData.Address.Street))
{
user.Address.Street = userUpdateData.Address.Street;
}
if (!String.IsNullOrEmpty(userUpdateData.Address.ZipCode))
{
user.Address.ZipCode = userUpdateData.Address.ZipCode;
}
}
// Save changes to DB
repository.Edit(ref user);
repository.SaveChanges();
return true;
}
Instead of calling Context.Entry(entityToUpdate) and setting it state to modified you could search for the entity you want to update and modify its members. Then set its state as modified, like this.
Also in your Edit function you should explicitly list which members of your MwbeUserData object your are updating. This is a security issue, it will prevent someone over posting extra members to your controller.
Here is an example of a edit function I have used.
public ActionResult Edit([Bind(Include = "Id,CompanyName,Abbreviation,CompanyTypeRef")] Company company)
{
if (!ModelState.IsValid) return View(company);//error invalid state
var c = Entities.Set<Company>().FirstOrDefault(x => x.Id == company.Id);
if(c == null) return View(company);// error could not find entity
Entities.Entry(c).CurrentValues.SetValues(company);
Entities.Entry(c).State = EntityState.Modified;
Entities.SaveChanges();
return RedirectToAction("Edit", "Company", new { id = company.Id });
}
Update:
I had a similar issue with entity changes not being recorded by changes tracker in EntityFramework.Extented and it was fix by updating the entities CurrentValues with this line.
Entities.Entry(c).CurrentValues.SetValues(company);

Check if entity is being tracked by Entity Framework context

I have a code block which checks whether an entity is being tracked by my context. If it is, I need to detach it. This works for a given T type.
public virtual async Task<bool> InsertOrUpdate(TE entity)
{
if (entity.Id == 0 || entity.Id == ModelState.New)
{
// attach new entity
_context.Entry(entity).State = EntityState.Added;
}
else
{
// Sometimes when you want to update a detached entity, before attempting to attach it (by setting the .State property),
// you first need to make sure the entity isn't already attached and being tracked. If this is the case, the existing entity
// needs to be detached, and the updated entity, attached.
var attachedEntity = _context.ChangeTracker.Entries<TE>().FirstOrDefault(e => e.Entity.Id == entity.Id);
if (attachedEntity != null)
{
// the entity you want to update is already attached, we need to detach it and attach the updated entity instead
_context.Entry<TE>(attachedEntity.Entity).State = EntityState.Detached;
}
_context.Entry<TE>(entity).State = EntityState.Modified; // Attach entity, and set State to Modified.
_context.Entry<TE>(entity).Property(o => o.CreatedUserId).IsModified = false;
_context.Entry<TE>(entity).Property(o => o.CreatedDate).IsModified = false;
}
return await _context.SaveChangesAsync() > 0;
}
I now need to change the method so that it fetches all objects of type IEntity within the given T entity parameter and then do the same logic for each object found, but I'm having trouble with setting the ChangeTracker.Entries as I need to set the T type to the current selected type within the foreach. I have no idea how to do this.
public virtual async Task<bool> InsertOrUpdate(TE entity)
{
//// Find all instances of IEntity within TE:
//// * IF entity is new we set State to EntityState.Added (INSERT)
//// * IF entity is existing, we set State to EntityState.Modified (UPDATE)
List<IEntity> found = FindAllInstances<IEntity>(entity);
foreach (IEntity ent in found)
{
if (entity.Id == 0 || entity.Id == ModelState.New)
{
// attach new entity
_context.Entry(entity).State = EntityState.Added;
}
else
{
// Sometimes when you want to update a detached entity, before attempting to attach it (by setting the .State property),
// you first need to make sure the entity isn't already attached and being tracked. If this is the case, the existing entity
// needs to be detached, and the updated entity, attached.
var attachedEntity = _context.ChangeTracker.Entries<TE>().FirstOrDefault(e => e.Entity.Id == entity.Id);
if (attachedEntity != null)
{
// the entity you want to update is already attached, we need to detach it and attach the updated entity instead
_context.Entry<TE>(attachedEntity.Entity).State = EntityState.Detached;
}
_context.Entry<TE>(entity).State = EntityState.Modified; // Attach entity, and set State to Modified.
_context.Entry<TE>(entity).Property(o => o.CreatedUserId).IsModified = false;
_context.Entry<TE>(entity).Property(o => o.CreatedDate).IsModified = false;
}
}
return await _context.SaveChangesAsync() > 0;
}
You could use the inner context wich is an ObjectContext.
var ctx = ((IObjectContextAdapter)_context).ObjectContext;
And then call ctx.Detach() on whatever entity you want. Fortunately this is not a generic method.
You can also get a reference to an ObjetStateManager from the ObjectContext and use it to do whatever state change you want.
More informations:
https://msdn.microsoft.com/en-us/library/system.data.objects.objectcontext.objectstatemanager(v=vs.110).aspx
https://msdn.microsoft.com/en-us/library/system.data.objects.objectstatemanager(v=vs.110).aspx

Passing Entity Framework object to helper method for update

I'm having some trouble using a helper method to perform an update to a set of model objects. The table uses a lookup table to hold 5 records per agent/user. If I want to save the record for the agent, I need to save that record onto the AgentTransmission table, and up to 5 other records on the RelationshipCodeLookup table.
Since I have to do this five times per agent, and we must do the process in the Create and Edit methods, I created a helper method to save the records. This works fine during the create process as we're simply doing a DbContext.Add(). However when I need to perform an update, I get the error message
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
I think this has to do with the fact I'm passing the model object to my helper method, and therefore the DbContext thinking that it has two separate objects to keep track of. I say this because the lines of code that are commented out work just fine and allow me to save the object. Passing the object to the helper method, however, gets the above error.
Does anyone know of a way around this (using a helper method to perform an update)?
Controller Action
//Save relationship codes in lookup table
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode1))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode1, agenttransmission.ID);
}
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode2))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode2, agenttransmission.ID);
}
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode3))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode3, agenttransmission.ID);
}
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode4))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode4, agenttransmission.ID);
}
if (AgentTransmissionValidator.ValidateRelationshipCode(agenttransmission.RelationshipCode5))
{
//db.Entry(agenttransmission.RelationshipCode1).State = EntityState.Modified;
//db.SaveChanges();
SaveRelationshipCodes(agenttransmission.RelationshipCode5, agenttransmission.ID);
}
Helper Method
public void SaveRelationshipCodes(RelationshipCodeLookup relCode, int id)
{
if (relCode.AgentId == 0) relCode.AgentId = id;
relCode.LastChangeDate = DateTime.Now;
relCode.LastChangeId = Security.GetUserName(User);
//Check to see if record exists and if not add it
if (db.RelationshipCodeLookup.Find(id, relCode.RelCodeOrdinal) != null)
{
db.Entry(relCode).State = EntityState.Detached;
}
else
{
if(relCode.RelCodeOrdinal == 0) relCode.RelCodeOrdinal = FindOrdinal(relCode);
db.RelationshipCodeLookup.Add(relCode);
}
db.SaveChanges();
}
EDIT
After scouring the web I attempted to save via this method
//Check to see if record exists and if not add it
if (db.RelationshipCodeLookup.Find(id, relCode.RelCodeOrdinal) != null)
{
db.Entry(relCode).CurrentValues.SetValues(relCode);
}
else
{
Member 'CurrentValues' cannot be called for the entity of type 'RelationshipCodeLookup because the entity does not exist in the context. To add an entity to the context call the Add or Attach method of DbSet<RelationshipCodeLookup>
However.... doing that only puts me back at the start with the following error on db.RelationshipCodeLookup.Attach(relCode);
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
Try this:
db.RelationshipCodeLookup.Attach(relCode);
db.Entry(relCode).State = EntityState.Modified;
For updates you want to attach the detached object then set it's state to modified.
The issue here seems to be that the Entity Framework cannot track two objects of the same kind at the same time. Because of that I find the solution to this problem more than a little weird. By calling .Find() on the DbContext and instantiating a second copy of the model object I was finally able to save. Seems to break all the rules the EF was laying out for me in the error messages, but hey it works.
public void SaveRelationshipCodes(int id, RelationshipCodeLookup relCode)
{
if (relCode.AgentId == 0) relCode.AgentId = id;
relCode.LastChangeDate = DateTime.Now;
relCode.LastChangeId = Security.GetUserName(User);
//Check to see if record exists and if not add it
if (db.RelationshipCodeLookup.Find(id, relCode.RelCodeOrdinal) != null)
{
//Need to call .Find to get .CurrentValues method call to work
RelationshipCodeLookup dbRelCode = db.RelationshipCodeLookup.Find(id, relCode.RelCodeOrdinal);
db.Entry(dbRelCode).CurrentValues.SetValues(relCode);
}
else
{
if(relCode.RelCodeOrdinal == 0) relCode.RelCodeOrdinal = FindOrdinal(relCode);
db.RelationshipCodeLookup.Add(relCode);
}
db.SaveChanges();
}

Entity Framework entity state is modified without any modification

I have the following issue on update of the entities. Given below is my WCF method. (Update is called by public Save method after determining if it is update or add)
protected bool UpdateSalesMaster(SalesMaster order)
{
using (var context = new MyContext())
{
SalesMaster original = context.SalesMasters.FirstOrDefault(o => o.OrderID == order.OrderID);
if (original != null)
{
context.Entry(original).CurrentValues.SetValues(order);
foreach (SalesDetail detail in order.SalesDetails)
{
if (detail.OrderDetailID == 0)
context.SalesDetails.Add(detail);
else
{
SalesDetails originalDetail = context.SalesDetails.FirstOrDefault(o => o.OrderDetailID == detail.OrderDetailID);
if (originalDetail != null)
context.Entry(originalDetail).CurrentValues.SetValues(detail);
}
}
context.SaveChanges();
return true;
}
else
{
throw new FaultException(string.Format("Invalid Order specified: {0}", order.OrderID));
}
}
}
When I just update the OrderDate in SalesMaster and don't change any in the detail, an update query is fired to the database for detail. I expected to see Update query only for SalesMaster.
Can someone let me know what am I doing wrong here? I don't want to fire update queries to the DB if nothing is changed.
I use the approach of getting the original value from the database to determine if any values are updated using context.Entry(originalDetail).CurrentValues.SetValues(detail);
I also override the SaveChanges to set the LastModified date by checking for IAuditable implementation of the entity. This is when I find that the state of the detail entity is identified as modified. But the only update that happens in the DB is LastModifiedBy which was updated in my save changes. I am not sure how it was set to state of Modified when nothing changed in detail.
public override int SaveChanges()
{
var changeSet = ChangeTracker.Entries<IAuditable>();
if (changeSet != null)
{
foreach (var entry in changeSet.Where(c => c.State != EntityState.Unchanged))
{
if (entry.State == EntityState.Added)
entry.Entity.DateCreated = DateTime.Now;
if (entry.State == EntityState.Modified)
{
entry.Property(a => a.CreatedByUser).IsModified = false;
entry.Property(a => a.DateCreated).IsModified = false;
}
entry.Entity.DateModified = DateTime.Now;
}
}
try
{
return base.SaveChanges();
}
catch (DbEntityValidationException ex)
{
throw ex;
}
}
My solution structure is:
Client - Windows forms UI
Entities - POCO as seperate library
WCF - All business logic, add, update, delete of objects.
Data - Entity Framework context with Fluent mapping.
Depending on how you set up you POCOs, EF will default to one of two ways to check changes on an entity.
If ALL your POCOs properties are virtual, EF will use proxy object that inherit your POCO type, with all the properties overridden to track changes.
I assume that in this circumstance, using SetValues on the whole object WILL trigger the dirty flag, that will cause EF to generate an Update query to the database.
If your are not using proxies, your IAuditable implementation would be the primary suspect as brumScouse suggested.

Entity Framework Error: An object with a null EntityKey value cannot be attached to an object context

In my application I have the following code...
public Boolean SaveUserInformation(UserInfoDTO UserInformation)
{
return dataManager.SaveUserInfo(new UserInfo()
{
UserInfoID = UserInformation.UserInfoID.HasValue ? UserInformation.UserInfoID.Value : 0,
UserID = UserInformation.UserID,
ProxyUsername = UserInformation.ProxyUsername,
Email = UserInformation.Email,
Status = UserInformation.Status
});
}
This code calls a method on a dataManager object that utilizes Entity Framework...
public Boolean SaveUserInfo(UserInfo userInfo)
{
try
{
//Validate data prior to database update
if (userInfo.UserID == null) { throw new Exception("UserInfoDomainModel object passed to PriorityOne.Data.DataManager.SaveUserInfo with UserID property set to NULL."); }
if (userInfo.ProxyUsername == null) { throw new Exception("UserInfoDomainModel object passed to PriorityOne.Data.DataManager.SaveUserInfo with ProxyUsername property set to NULL."); }
if (userInfo.Email == null) { throw new Exception("UserInfoDomainModel object passed to PriorityOne.Data.DataManager.SaveUserInfo with Email property set to NULL."); }
if (userInfo.UserInfoID == 0)
{
//Perform Insert
using (PriorityOneEntities entities = new PriorityOneEntities())
{
entities.UserInfoes.AddObject(userInfo);
entities.SaveChanges();
}
}
else
{
//Perform Update
using (PriorityOneEntities entities = new PriorityOneEntities())
{
entities.Attach(userInfo);
entities.SaveChanges();
}
}
return true;
}
catch (Exception ex)
{
//TODO: Log Error
return false;
}
}
The insert on this code works just fine. But when I try to perform an update I'm getting an error saying: "An object with a null EntityKey value cannot be attached to an object context."
It occurs on this line of code: entities.Attach(userInfo);
What I'm trying to accomplish is to avoid making a round trip to the database just to select the record that I will later make changes to and update, thus making two round trips to the database.
Any ideas what is going wrong, or how I could better accomplish this?
Thanks.
Seems like you're using EF 4.1+
You have to tell EF that you want your entity to be updated (Modified state):
//Perform Update
using (PriorityOneEntities entities = new PriorityOneEntities())
{
entities.Entry(userInfo).State = EntityState.Modified;
entities.SaveChanges();
}
P.S. You don't have to explicitly call Attach. It's done under the hood.
Update:
based on your comments, you're using EF 4.0. Here's what you have to do to attach your object as modified in EF 4.0:
ctx.AddObject("userInfoes", userInfo);
ctx.ObjectStateManager.ChangeObjectState(userInfo, EntityState.Modified);
ctx.SaveChanges();
You cannot use Attach method. From http://msdn.microsoft.com/en-us/library/bb896271.aspx:
If more than one entity of a particular type has the same key value, the Entity Framework will throw an exception. To avoid getting the exception, use the AddObject method to attach the detached objects and then change the state appropriately.
From MSDN
The object that is passed to the Attach method must have a valid
EntityKey value. If the object does not have a valid EntityKey value,
use the AttachTo method to specify the name of the entity set.
Hope this will help you.

Categories

Resources