Tracking Original Values of Entity in Multi-Layered/Tier Environment - c#

Referring - How to get original values of an entity in Entity Framework? - I tried to extract the original value(s) of an entity in EF. But the ObjectStateManager.GetOBjectStateEntry giving the modified value of the entity. What am I missing?
I am using EF 4.0 (POCO Entities) in a multi-layered environment.
public bool Update(IMessage objMessage)
{
object ob = objMessage.GetMaster();
appSancAdvice _entity = ob as appSancAdvice;
using (var context = new BISEntities())
{
context.appSancAdvices.Attach(_entity);
ObjectStateEntry objectState = context.ObjectStateManager.GetObjectStateEntry(_entity);
objectState.ChangeState(System.Data.EntityState.Modified);
// this is giving the modified value of _entity
var originalValues = context.ObjectStateManager.GetObjectStateEntry(_entity).OriginalValues["sancstatus_id"];
int _i = context.SaveChanges();
return (_i > 0) ? true : false;
}
}

The context does not know the original value because you attach the entity. If you want the original values, you must fetch the object from the database. It is not that EF does that automatically when you get OriginalValues from a newly attached object.

Related

Duplicating Entity Framework entities

I have this EF query:
var records = mydata.Where(a => some condition).ToList();
Records is a list of Record objects. Record is a database table that has a one to many relationship called "relation".
The EF object represents that member variable as Collection<Relation>.
After the where above, I see that "relation" member variable contains a collection of 18 entities.
I need to duplicate those Record objects, so that I am detaching them first, this way:
var detached = this.DetachEntities(records, db, "RecordId");
Where this.DetachEntities is defined this way:
private T DetachEntity<T>(T entity, Repositories.GestionActivosEntities db, string keyName) where T : class
{
db.Entry(entity).State = EntityState.Detached;
if (entity.GetType().GetProperty(keyName) != null)
{
entity.GetType().GetProperty(keyName).SetValue(entity, 0);
}
return entity;
}
private List<T> DetachEntities<T>(List<T> entities, Repositories.GestionActivosEntities db, string keyName) where T : class
{
foreach (var entity in entities)
{
this.DetachEntity(entity, db, keyName);
}
return entities;
}
After I detached the entities, I am doing:
db.Record.AddRange(detached);
The problem, is that, after I detached the list, the relation is not copied also, resulting that "relation" member variable contains no elements.
How can I keep the relation elements after detaching?
EDIT:
This shows the debugging session:
The breakpoint is placed in the line where the query is done. The cursor shows the line after the query was executed.
This screenshot shows the child records ValorCampo and Workflow
This screenshot shows the entity after detaching it. Note the child records are not present in the detached object
As I have told, the problem only is when detaching. Child records are not kept. Database rows remain untouched.
I had the same problem, unfortunately navigation properties are lost after detaching an Item or when entity state is changed to detached.
what you can do is clone the entity
one way to do this is : Context.Entry(your_entity).CurrentValues.ToObject();
however this will not clone the navigation properties either
if you fully want to clone an object among with navigation properties
the easiest way for me to achieve it was using automapper library for c#
Below is a sample usage:
var config = new MapperConfiguration(cfg => cfg.CreateMap<originalObject, T>());
var mapper = new Mapper(config);
// or
var mapper = config.CreateMapper();
T clonedObject = mapper.Map<T>(originalObject);
after you clone and detach the original object, you can add
db.Record.AddRange(clonedObject );
and below is a generic extension to do it
public static object Map<T>(this T source)
{
var fullName = source.GetType().FullName;
var sourceType = source.GetType();
var baseType = ObjectContext.GetObjectType(source.GetType());
var config = new MapperConfiguration(cfg =>
cfg.CreateMap(sourceType, baseType));
var mapper = config.CreateMapper();
var entity = mapper.Map(source, sourceType, baseType);
return entity;
}
where you can call it like
var clonedObject = originalObject.Map();
Hope this helps!

Entity Framework Core. How to update the related data properly?

This question is a common one, but I still can't understand how to update the related entity properly?
I have the following code:
public async Task<bool> UpdateStepAssignedToLevelAsync(Step step, Guid levelId, int priority = -1)
{
var item = await this._context.StepLevels
.Include(sl => sl.Step)
.FirstOrDefaultAsync(x => x.StepId == step.Id && x.LevelId == levelId);
if (item == null)
{
return false;
}
//this._context.Entry(item).State = EntityState.Detached;
if (priority > -1)
{
item.Priority = priority;
}
item.Step = step;
//this._context.StepLevels.Update(item);
var rows = await this._context.SaveChangesAsync();
return rows > 0;
}
When it runs I'm getting the following error:
InvalidOperationException: The instance of entity type 'Step' cannot be tracked because another instance with the key value '{Id: 35290c18-5b0a-46a5-8f59-8888cf548df5}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
As I understand the entity is being tracked since the select request at the method's start. Okay, but when I'm detaching the entity and calling the Update method (see commented lines), the Step entity is not being changed. But StepLevel does: the priority is changing. When I'm trying to call just Update the EF tries to insert the new Step instead of updating the existing.
So, could you please advice me, what is the best practicing here?
Thanks a lot in advance!
First, detaching an entity does not detach related entities, so detaching item does not detach the item.Step retrieved with .Include(sl => sl.Step).
Second, since you don't want to change the item.Step, but to update the existing Step entity (x.StepId == step.Id), and you know that the context is tracking (contains) the corresponding Step entity loaded from the database, you should use the procedure for updating a db entity from a detached entity via Entry(...).CurrentValues.SetValues method.
So remove
item.Step = step;
and use the following instead:
this._context.Entry(item.Step).CurrentValues.SetValues(step);
Last note. When you work with attached entities, there is no need to (should not) use Update method. The change tracker will automatically detect the changed property values if any of the entities being tracked.
The context is tracking the corresponding Step entity loaded from the DB for that reason it is throwing this error. You can update each Step property -
public bool UpdateStepAssignedToLevelAsync(Step step, int levelId)
{
using(var context = new StackOverFlowDbContext())
{
var item = context.StepLevels
.Include(sl => sl.Step)
.FirstOrDefault(x => x.Id == step.Id && x.Id == levelId);
if (item == null)
{
return false;
}
// Updating Name property
item.Step.Name = step.Name;
// Other properties can be upadated
var rows = context.SaveChanges();
return rows > 0;
}
}

The instance of entity type 'xTestType' cannot be tracked because another instance of this type with the same key is already being tracked?

I'm trying to delete multiple rows from a table. But it gives the following error after the first iteration. I can see primary key Id as 0 on all the xTestType object. that might be the issue. Why is it always giving Id 0.
foreach (var temp in oldxDetails.TestTypes)
{
if (deleteTestTypes.Contains(input.Id))
{
var xTestType = new xTestType
{
xId = xId,
TestTypeMasterId = temp.Id
};
await _xTestRepository.DeleteAsync(xTestType);
}
}
Exception:
The instance of entity type 'xTestType' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
When you fetch data from database, and iterate over it, like:
var dataObject = await dbContext.Table.where(x=>x.UserId == model.UserId).TolistAsync();
foreach(var item in dataObject)
{
}
do not create another object, pass the fetched object directly to Delete or use it to update, because DbContext is tracking the objects it has fetched, not the ones you create. for example:
//Wrong code
var dataObject = await dbContext.Table.where(x=>x.UserId == model.UserId).TolistAsync();
foreach(var item in dataObject)
{
var x=new DataObject()
{
x=item.Id
};
dbContext.Table.Remove(x);
}
you must pass the originally fetched instance to Remove() method, see:
var dataObject = await dbContext.Table.where(x=>x.UserId == model.UserId).TolistAsync();
foreach(var item in dataObject)
{
dbContext.Table.Remove(item);
}
The issue exists because the entity framework is tracking xTestType when you fetched all of them. There are two approaches to handle the situation.
Approach 1:
DbContext.Entry(xTestTypeOld).State = EntityState.Deleted; // where xTestTypeOldis record from which you are taking xId
Approach 2 :
DbContext.Entry(xTestTypeOld).State = EntityState.Detached;
DbContext.Entry(xTestType).State = EntityState.Deleted;
I would say the first approach is the best one.

Two references to the same domain/entity model

Problem
I want to save the attributes of a model that have changed when a user edits them. Here's what I want to do ...
Retrieve edited view model
Get domain model and map back updated value
Call the update method on repository
Get the "old" domain model and compare values of the fields
Store the changed values (in JSON) into a table
However I am having trouble with step number 4. It seems that the Entity Framework doesn't want to hit the database again to get the model with the old values. It just returns the same entity I have.
Attempted Solutions
I have tried using the Find() and the SingleOrDefault() methods, but they just return the model I currently have.
Example Code
private string ArchiveChanges(T updatedEntity)
{
//Here is the problem!
//oldEntity is the same as updatedEntity
T oldEntity = DbSet.SingleOrDefault(x => x.ID == updatedEntity.ID);
Dictionary<string, object> changed = new Dictionary<string, object>();
foreach (var propertyInfo in typeof(T).GetProperties())
{
var property = typeof(T).GetProperty(propertyInfo.Name);
//Get the old value and the new value from the models
var newValue = property.GetValue(updatedEntity, null);
var oldValue = property.GetValue(oldEntity, null);
//Check to see if the values are equal
if (!object.Equals(newValue, oldValue))
{
//Values have changed ... log it
changed.Add(propertyInfo.Name, newValue);
}
}
var ser = new System.Web.Script.Serialization.JavaScriptSerializer();
return ser.Serialize(changed);
}
public override void Update(T entityToUpdate)
{
//Do something with this
string json = ArchiveChanges(entityToUpdate);
entityToUpdate.AuditInfo.Updated = DateTime.Now;
entityToUpdate.AuditInfo.UpdatedBy = Thread.CurrentPrincipal.Identity.Name;
base.Update(entityToUpdate);
}
The issue is that Entity Framework cache's the objects it reads in the DbSet. So when you request the object the second time, it isn't going to the database because it already has loaded it.
However, the good news is that Entity automatically tracks the original values. See this question for information on how to get them: How to get original values of an entity in Entity Framework?

Adding foreign key relationship with Entity Framework

This problem is similar to my previously asked question. When I query data using the Entity Framework (EF) I always use the MergeOption.NoTracking option because I end up taking my EF generated objects and mapping them to view models which have lovely attributes decorated on the property to enforce validation and so on.
I am trying to add a foreign key relationship using the EF, but anytime I do I am getting the following exception:
The object being attached to the source object is not attached to the same ObjectContext as the source object
Here is my code:
public static void UpdateDownloadFileVersion(DownloadFile downloadFile, int[] selectedVersions) {
using (SupportEntity supportContext = new SupportEntity()) {
supportContext.DownloadFiles.Attach(downloadFile);
var productVersionIdsToAdd = (from v in selectedVersions
where (downloadFile.ProductVersions.Any(pv => pv.Id == v) == false)
select v).ToList();
foreach (var productVersionId in productVersionIdsToAdd) {
var productVersion = new ProductVersion() { Id = productVersionId };
downloadFile.ProductVersions.Attach(productVersion); //Exception happens here.
downloadFile.ProductVersions.Add(productVersion);
}
supportContext.SaveChanges();
}
}
This is where Stub Entities become very very useful...
var productVersion = new ProductVersion() { Id = productVersionId };
supportContext.AttachTo("ProductVersions", productVersion);
Here is a good article
In above case, when attached productVersion is assigned to product versions' entity, productversion entity gets attached to context, with EntityState=Added. Entire graph will be in or out of context.

Categories