Finding out actual changes/diff done in database - c#

Is it possible in Entity Framework to figure out the actual changes/diff which entity framework is going to make in the database?
Consider an example, let say that some rows are already present in the database and we try to add them again. Since the rows are already present, the actual changes/diff made in the database is null. Similarly, if I try to 10 rows, out of which only 3 got updated, then I want only those 3.
I was trying using DbContext.ChangeTracker to achieve the same but it looks like that it returns all the rows which we are trying to add/update/delete irrespective of whether some of them are already there in the database. Can someone confirm this behavior as well?

I used the following code in my base repository to get a dictionary of modified property names and the old DB values. The new values can be get easily by the TModel object itself.
private Dictionary<string, object> GetModifiedProperties(TModel model)
{
var entry = Context.Entry(model);
// entry is detached.
// set entry to database entry and its CurrentValues to model values
if (entry.State == EntityState.Detached)
{
object key = model.GetType().GetProperty("Id").GetValue(model);
if (key == null)
{
throw new InvalidOperationException("The Entity you desire to update does not contain an Id value.");
}
var dbModel = Context.Set<TModel>().Find(key);
var dbEntry = Context.Entry(dbModel);
dbEntry.CurrentValues.SetValues(model);
entry = dbEntry;
//entry.State = EntityState.Modified;
}
var modifiedProps = new Dictionary<string, object>();
foreach (var propertyName in entry.CurrentValues.PropertyNames)
{
// copy only changed values to dict
var prop = entry.Property(propertyName);
if (prop.IsModified)
{
modifiedProps.Add(propertyName, prop.OriginalValue);
}
}
return modifiedProps;
}
Sadly I found no elegant way to get the Key property. But "Id" worked for me. Only the changed properties should appear in the dictionary. Not exactly what you want but something to work with.
Edit: I use the Unit of Work pattern for my DAL. Every repository derives from my base repository, where this code comes from. The update method triggers the GetModifiedProperties() method.
You can than write an update method like that:
UnitOfWork.CutomerRepository.Update(Customer updated, out Dictionary<string, object> changedProps);

Related

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.

Entity Framework 6 update existing record with another record's values

I use EF database-first model in my app. It's WPF MVVM app, so i use long-living DbContext, which is created when app starts and disposed when it finishes.
There are two tables - clients and settings. settings stores all client's settings with client_id as a foreign key and settings_id as primary key.
In this settings table I have some 'default' record with settings_id=1and client_id=1. I want my app to restore 'default' settings for a client by pressing a button.
In my vewmodel i have an ObservableCollection of type Client, which is my db entity model class, and a property SelectedClient of type Client, bound to currently selected client (in some ListBox). Also i have entity class Settings, which has some fields representing different settings from a settings table. I want all these settings from 'default' record to replace currently selected client's settings.
So what am i doing:
public void OnResetClientSettingsCommandExecute()
{
var defaultSettings = Global.DbContext.Settings.FirstOrDefault(c => c.client_id == 1);
if (defaultSettings == null) return;
var tmp = defaultSettings;
tmp.client_id = SelectedClient.client_id; // doing this to change the only field which needs to remain untouched
var selectedClientSettings = Global.DbContext.Settings.FirstOrDefault(c => c.client_id == SelectedClient.client_id);
selectedClientSettings = tmp;
Global.DbContext.SaveChanges();
}
This code doesn't work at all. The only thing i get here - is changing client_id for my 'default' record in settings to SelectedClients client_id. I don't know why it happens, i thought if i would use tmp it'll be ok, but no.
I know there are some practices of using Attach() methods or changing entity's State to Modified - i tried all of them and no one worked for me, i suppose because i use long-living DbContext approach.
Honestly, i am very confused of updating records in my app in general - i just can't do it, DbContext.SaveChanges() method does not save changes to database, but rolls them back for some reason. So i have to use raw SQL-queries, which is a bit of stone age.
Please someone help me to figure out what i am doing wrong. Thanks.
You could create a class with method like this
public static void CopyValues<T>(T source, T destination)
{
var props = typeof(T).GetProperties();
foreach(var prop in props)
{
var value = prop.GetValue(source);
prop.SetValue(destination, value);
}
}
Then assign your keys to temporary variables, copy the rest of the properties and reassign your keys back to their original values.
int id = selectedClientSettings.client_id;
ObjectCopier.CopyValues<Client>(defaultSettings, selectedClientSettings);
selectedClientSettings.client_id = id;
The right way to do it. But it's exhausting !
public void OnResetClientSettingsCommandExecute()
{
var defaultSettings = Global.DbContext.Settings.FirstOrDefault(c => c.client_id == 1);
if (defaultSettings == null) return;
var selectedClientSettings = Global.DbContext.Settings.FirstOrDefault(c => c.client_id == SelectedClient.client_id);
selectedClientSettings.serviceName = defaultSettings.serviceName;
selectedClientSettings.write_delay = defaultSettings.write_delay;
// etc...
Global.DbContext.SaveChanges();
}
You should consider using AutoMapper, it could be easier to write.

Dynamic LINQ - Entity Framework 6 - Update Records for Dynamic Select

C# rookie. Below is my code, been trying for hours now to get this to update some fields in my DB and tried many different implementations without luck.
// Select all fields to update
using (var db = new Entities())
{
// dbFields are trusted values
var query = db.tblRecords
.Where("id == " + f.id)
.Select("new(" + string.Join(",", dbFields.Keys) + ")");
foreach (var item in query)
{
foreach (PropertyInfo property in query.ElementType.GetProperties())
{
if (dbFields.ContainsKey(property.Name))
{
// Set the value to view in debugger - should be dynamic cast eventually
var value = Convert.ToInt16(dbFields[property.Name]);
property.SetValue(item, value);
// Something like this throws error 'Object does not match target type'
// property.SetValue(query, item);
}
}
}
db.SaveChanges();
}
The above code when run does not result in any changes to the DB. Obviously this code needs a bit of cleanup but i'm trying to get the basic functionality working. I believe what I might need to do is to somehow reapply 'item' back into 'query' but I've had no luck getting that to work no matter what implementation I try i'm always receiving 'Object does not match target type'.
This semi similar issue reaffirms that but isn't very clear to me since i'm using a Dynamic LINQ query and cannot just reference the property names directly. https://stackoverflow.com/a/25898203/3333134
Entity Framework will perform updates for you on entities, not on custom results. Your tblRecords holds many entities, and this is what you want to manipulate if you want Entity Framework to help. Remove your projection (the call to Select) and the query will return the objects directly (with too many columns, yes, but we'll cover that later).
The dynamic update is performed the same way any other dynamic assignment in C# would be, since you got a normal object to work with. Entity Framework will track the changes you make and, upon calling SaveChanges, will generate and execute the corresponding SQL queries.
However, if you want to optimize and stop selecting and creating all the values in memory in the first place, even those that aren't needed, you could also perform the update from memory. If you create an object of the right type by yourself and assign the right ID, you can then use the Attach() method to add it to the current context. From that point on, any changes will be recorded by Entity Framework, and when you call SaveChanges, everything should be sent to the database :
// Select all fields to update
using (var db = new Entities())
{
// Assuming the entity contained in tblRecords is named "ObjRecord"
// Also assuming that the entity has a key named "id"
var objToUpdate = new ObjRecord { id = f.id };
// Any changes made to the object so far won't be considered by EF
// Attach the object to the context
db.tblRecords.Attach(objToUpdate);
// EF now tracks the object, any new changes will be applied
foreach (PropertyInfo property in typeof(ObjRecord).GetProperties())
{
if (dbFields.ContainsKey(property.Name))
{
// Set the value to view in debugger - should be dynamic cast eventually
var value = Convert.ToInt16(dbFields[property.Name]);
property.SetValue(objToUpdate, value);
}
}
// Will only perform an UPDATE query, no SELECT at all
db.SaveChanges();
}
When you do a SELECT NEW ... it selects only specific fields and won't track updates for you. I think if you change your query to be this it will work:
var query = db.tblRecords.Where(x=>x.id == id);

EF Repository with UoW Update

I believe this is asked somewhere else but I can't find straight solution.
My Api is passing object model and on the server side every value of that object which is not passed is considered null (makes sense).
Is there way I can tell EF6 not to update entity with null values from passed object in manner I don't have to write each property and check if it's null.
Pseudo code
API
Update(int id, TaskEntity obj)
{
unitOfWork.Tasks.Update(id, userTask);
...
unitOfWork.Save()
}
Repo update
Update(int id, T entity)
{
var existingRecord = Get(id); //Gets entity from db based on passed id
if (existingRecord != null)
{
var attachedEntry = Context.Entry(existingRecord);
attachedEntry.CurrentValues.SetValues(entity);
}
}
My problem is that any data with null values will actually rewrite existing db record value with nulls.
Please point me to a solution or article where this is solved. Should I go reflections, maybe automapper could handle this (it's not its purpose i believe), or some kind of helper method should be written, as my objects can contain sub object.
Thank you in advance.
You can do something like this
Update(int id, T entity,string[] excludedFields)
{
var existingRecord = Get(id); //Gets entity from db based on passed id
if (existingRecord != null)
{
var attachedEntry = Context.Entry(existingRecord);
attachedEntry.CurrentValues.SetValues(entity);
for(var field in excludedFields)
{
attachedEntry.Property(field).IsModified = false;
}
}
}
some scenaries requires you to update part of the object and sometimes other parts, the best way in my opinion is to pass the fields to exclude from the update
hope it will help you
Personally not a big fan of hitting database and doing a get operation before doing an update. May be while doing the ajax call, you can send a list of properties which you should update (so that the scenario where updating to null values (erasing existing ones) will also be handled).
I'm doing small modifications to what #Hadi Hassan has done (without hitting database for getting the entity):
Update(T entity,string[] includedFields)
{
var existingRecord = Context.Attach(entity); // assuming primary key (id) will be there in this entity
var attachedEntry = Context.Entry(existingRecord);
for(var field in includedFields)
{
attachedEntry.Property(field).IsModified = true;
}
}
Note - attachedEntry.Property(field).IsModified will not work for related entities

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?

Categories