TryUpdateModel is one of the methods in System.Web.Mvc,but I need to use it in windows application which does not have this library.
Any similar method in EF or other suggestions so that I can update an entity without matching fields one by one with the target model?
I tried many solutions but finally I wrote my own method via reflection which worked perfect for me as follows:
public void TryUpdateModel<T>(T existingModel, T newModel)
{
PropertyInfo[] properties = existingModel.GetType()
.GetProperties()
.Where(pi => !(pi.PropertyType.IsSubclassOf(typeof(T))))
.ToArray();
foreach (var item in properties)
{
var propName = item.Name;
if (item.PropertyType.IsSealed && item.PropertyType.IsSerializable && item.Name != existingModel.GetType().Name + "Id")
{
var newValue = db.Entry(newModel).Property(propName).CurrentValue;
db.Entry(existingModel).Property(propName).CurrentValue = newValue;
}
}
}
It gets 2 models (as they are generic classes but should be the same) in which first one is the model fetched from database and second one is what submitted from the UI form.
It updates property values from newModel to existingModel except navigation properties and primary key so that entity framework realizes that values are changed and have to be updated.
Related
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.
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);
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);
I know the title wasn't the greatest, but here is what I actually want to accomplish.
I have a details view that represents Entity1 and its associations. I am capturing the property names and values in a key/value pair. I am currently using reflection to set the Entity properties to the corresponding values for non associations. I doubt this is the most efficient way, but I have been unable to find a better way using Expression Trees. So now I need to set the associations of Entity1 to their corresponding entities based on the primary key of those entity associations, call them Entity2-4.
When iterating the Properties of Entity1, I don't know how to construct a dynamic query to Entity2-4 and set Entity1.association to the corresponding entity. Here is the code I have so far:
foreach (string k in e.Values.Keys)
{
if (e.Values[k] != null && !String.IsNullOrEmpty(e.Values[k].ToString()))
{
System.Type objectType = Entity1.GetType();
PropertyInfo[] p = objectType.GetProperties();
foreach (PropertyInfo pi in p)
{
// set Entity1.Property for non associations (works just fine)
if (pi.Name == k)
{
System.Type t = pi.PropertyType;
pi.SetProperty(e.Values[k].ToString(), Entity1);
break;
}
// when i see pi.Name contain Reference, I know I'm working on an association
else if (pi.Name.Contains("Reference"))
{
// k is in the form of Entity.Property
var name = pi.Name.Left("Reference");
var keys = k.Split('.');
var ent = keys[0];
var prop = keys[1];
if (name == ent)
{
// here I need to obtain an instance from the db
// ie generate my dynamic query to the Entity with the name
// contained within the var "ent"
// I tried using reflection and could instantiate the entity
// but it did me no good as I needed the entity from the db
var entityInstance = some dynamic query;
// here I need to set the association of Entity1 to entityInstance from above
// normally I would use reflection, but I'm not sure that would work
// since EntityReference is the actual property returned by reflection
Entity1.SetAssocation(prop, Entity2);
break;
}
}
}
}
}
EDIT
I basically need construct the entity and its association entities so that I can submit them to the data context. Entity 2 through 4 exist in the db, I need to query the db to obtain the instances to that I may associate them to the new Entity1 I am creating and going to submit.
My basic model:
Entity1
Entity1.ID
Entity1.Prop1
Entity1.Prop2
Entity1.Prop3
Entity1.Entity2
Entity1.Entity3
Entity1.Entity4
Entity2
Entity2.ID
Entity2.Name
Entity3
Entity3.ID
Entity3.Name
Entity4
Entity4.ID
Entity4.Name
We've had an in-depth discussion about how to get the metadata out of the DbContext. Here are a few links to get you started. And I'll add some specific comments.
How I can read EF DbContext metadata programmatically?
A short summary (but you should check in there for more):
using (var db = new MyDbContext())
{
var objectContext = ((IObjectContextAdapter)db).ObjectContext;
var container = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
var dependents = ((EntitySet)(set)).ForeignKeyDependents;
var principals = ((EntitySet)(set)).ForeignKeyPrincipals;
var navigationProperties = ((EntityType)(set.ElementType)).NavigationProperties;
// and e.g. for many-to-many (there is more for other types)
ManyToManyReferences = navigationProperties.Where(np =>
np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many &&
np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
.Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
.ToList();
}
#Goran Obradovic did a great job to wrap up what I started into a set of reusable queries (my due credits to him:).
I have worked out all other sorts of information that's in there. This is just for the DataSpace.CSpace (which is the most useful to you), but there is also DataSpace.SSpace etc. - which are more for creating SQL queries etc. I'll put most links at the bottom.
Specifics:
In your case following might be helpful:
(note: I'm not entirely sure what you're after, but I'm trying to guess here what's the direction that you're heading)
db.Set<Worker>().Find(1);
Is the generic method for accessing DbSet for a specific entity.
You could also construct it from a Type if you need it to be fully dynamic, e.g....
(I always wanted to do this:)
MethodInfo setMethod = typeof(DbContext).GetMethod("Set", new Type[]{});
MethodInfo genericSetMethod = setMethod.MakeGenericMethod(new Type[] { typeof(YourEntity) });
var set = genericSetMethod.Invoke(db, new object[] {});
Put your entity - or your Type instead of typeof(YourEntity).
You could then proceed and query that for e.g. Find(id) - for that entity - to get concrete values etc.
That's as dynamic as it gets - I'm not sure if that's what you want - but I'm just throwing things out here in case you need it.
That should get you started at least I hope.
links:
(all are posts of mine - some may be more or less relevant but might help)
How I can read EF DbContext metadata programmatically?
How check by unit test that properties mark as computed in ORM model?
Get Model schema to programmatically create database using a provider that doesn't support CreateDatabase
Programmatic data transformation in EF5 Code First migration
So, I wasn't able to perform this dynamically. But here is my working solution. Can anyone advise on how to perform this dynamically?
foreach (string k in e.Values.Keys)
{
if (e.Values[k] != null && !String.IsNullOrEmpty(e.Values[k].ToString()))
{
System.Type objectType = roster.GetType();
PropertyInfo[] p = objectType.GetProperties();
foreach (PropertyInfo pi in p)
{
if (pi.Name == k)
{
System.Type t = pi.PropertyType;
pi.SetProperty(e.Values[k].ToString(), roster);
break;
}
else if (pi.Name.Contains("Reference"))
{
var name = pi.Name.Left("Reference");
var keys = k.Split('.');
var entityName = keys[0];
var prop = keys[1];
if (name == entityName )
{
var val = e.Values[k].ToString();
switch (pi.Name)
{
case "Entity2Reference":
Entity1.Entity2Reference.EntityKey = new EntityKey("MyEntities." + entityName + "s", prop, val);
break;
case "Entity3Reference":
Entity1.Entity3Reference.EntityKey = new EntityKey("MyEntities." + entityName + "s", prop, val);
break;
case "Entity4Reference":
Entity1.Entity4Reference.EntityKey = new EntityKey("MyEntities." + entityName + "s", prop, Int64.Parse(val));
break;
}
}
}
}
}
}
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?