Set Properties of linq object to those of another - c#

I have a complex object / entity pulled from the database which has 50-60 fields. I've generated a form for this by hand and it was a pain in the bum. I don't want to have to now write c# code to manually apply each property.
I thought I could do this
using (DataContext db = new DataContext())
{
var _contact = db.Contact.Where(x => x.ID == model.Contact.ID).FirstOrDefault();
_contact = model.Contact;
db.SaveChanges();
}
However, this does not update the object. I guess the reference to the original is lost when I set _contact = model.Contact
I've found a lot of similar threads on SO with this solution (or variations):
var notNullProps = typeof(Contact).GetProperties()
.Where(x => x.GetValue(model.Contact, null) != null);
foreach (var p in notNullProps)
{
p.SetValue(_contact, p.GetValue(model.ContactWithMetaData.Contact, null), null);
}
However this always throws the error Non-static method requires a target
I've even tried using AutoMapper, even though my objects are of the same class. It seemed to lose the reference to the object too. It's late and I may be doing something stupid. Can anyone help?
UPDATE:
My AutoMapper Code:
Mapper.CreateMap<Contact, Contact>();
Mapper.Map(model.ContactWithMetaData.Contact, _contact);
db.SaveChanges();
UPDATE 2:
Sorry folks. It turns out I was doing something stupid. I was populating my form with model.Contact but had omitted a hidden field for Contact.ID. As a result Contact.ID and _contact were both null to begin with. Now that I've seen my mistake, I've gone back and tested. Both methods above work, and wahwahwah's method also works.
Sorry for wasting everyone's time!

I think this might help:
public void SetProperties(object source, object target)
{
var contactType = target.GetType();
foreach (var prop in source.GetType().GetProperties())
{
var propGetter = prop.GetGetMethod();
var propSetter = contactType.GetProperty(prop.Name).GetSetMethod();
var valueToSet = propGetter.Invoke(source, null);
propSetter.Invoke(target, new[] { valueToSet });
}
}
Only works if the properties of both objects are exactly the same (case sensitive.) Not as robust as AutoMapper...

By doing _contact = model.Contact; you are replacing the entire reference of _contact with the reference to model.Contact. The original object referred to by _contact is unchanged and hence there is nothing to save.

Related

What is the best way to iterate through a C# object so only the modified fields are updated?

Please excuse my limited knowledge of Entity Framework/C#. I'm sure there is an easy way to do what I wish to do.
I want to iterate through an object that I received from the database in order to check which fields are different from my update DTO. I wish to do this so only the changed fields are updated in the objFromDb. I am aware that if I only change the fields in the objFromDb that are modified, EF will only update the changed fields. I'm not sure if this is necessary or if there is a better way to do it.
Here is my update code (works but not the way I want it to) with the individual properties as well as the commented code that I was trying to accomplish. I don't like hard-coding the individual properties as this will require maintenance in the event that the object is changed.
public T_IFS_EmployeeDTO Update(T_IFS_EmployeeDTO ojbDTO)
{
var objFromDb = _db.T_IFS_Employee.FirstOrDefault(u => u.EmployeeID == ojbDTO.EmployeeID);
if (objFromDb != null)
{
//foreach (var prop in objFromDb.GetType().GetProperties(BindingFlags.Instance))
//{
// if (prop.GetValue(objFromDb) != prop.GetValue(ojbDTO))
// {
// objFromDb.prop.GetValue(objFromDb) = prop.GetValue(ojbDTO);
// }
//}
if (objFromDb.FirstName != ojbDTO.FirstName) objFromDb.FirstName = ojbDTO.FirstName;
if (objFromDb.LastName != ojbDTO.LastName) objFromDb.LastName = ojbDTO.LastName;
if (objFromDb.UserName != ojbDTO.UserName) objFromDb.UserName = ojbDTO.UserName;
if (objFromDb.Password != ojbDTO.Password) objFromDb.Password = ojbDTO.Password;
if (objFromDb.AccessLevel != ojbDTO.AccessLevel) objFromDb.AccessLevel = ojbDTO.AccessLevel;
_db.T_IFS_Employee.Update(objFromDb);
_db.SaveChanges();
return _mapper.Map<T_IFS_Employee, T_IFS_EmployeeDTO>(objFromDb);
}
return ojbDTO;
I'm sure there is an easy way to do this but I haven't been able to figure it out. I do appreciate your time.
-Edit-
I think the following will work but will EF know if a field has not been modified and is it possible that it is as simple as this:
var objFromDb = _db.T_IFS_Employee.FirstOrDefault(u => u.EmployeeID == ojbDTO.EmployeeID);
var objFromCall = _mapper.Map<T_IFS_EmployeeDTO, T_IFS_Employee>(ojbDTO);
if (objFromDb != null)
{
objFromDb = objFromCall;
Entity Framework Core will check the values of your entity and check them against a snapshot of what they were like when you loaded them. See this for the details.
So you should be able to do:
var objFromDb = _db.T_IFS_Employee.FirstOrDefault(u => u.EmployeeID == ojbDTO.EmployeeID);
if (objFromDb != null)
{
_mapper.Map<T_IFS_EmployeeDTO, T_IFS_Employee>(ojbDTO, objFromDb);
//This overload of .Map sets properties in an existing object, as
//opposed to creating a new one
}
This will overwrite all properties in objFromDb with values from objDTO, But only the ones that are different will be written to the database when you call SaveChanges().
Setting objFromDb to objFromCall will overwrite your reference from the database and this won't be tracked at all.
And there's no need to call .Update() if you received the object from the DbContext and you haven't disabled change tracking.

Ignore null values in context update

I have a large model which has been partially updated via deserialization. Since it has only been partially updated I would like to ignore any null values when I pass this to my entity framework update. Ultimately the EntityState.Modified is set but the issue I am having is that all fields are updated. This means anything that was null is now blanked in the database.
Is it possible to change this default behavior through a setting or override a method to check for null? It seems that since the context is expecting the full model I cannot simply set only a few values.
I've verified this by mapping only what I need to modify and the same behavior occurs.
You could implement something like this.
In this case I'm using a generic repository with reflection, to iterate through the properties and exclude null values in the update method.
public virtual TEntity Update(TEntity entity)
{
dbSet.Attach(entity);
dbContext.Entry(entity).State = EntityState.Modified;
var entry = dbContext.Entry(entity);
Type type = typeof(TEntity);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.GetValue(entity, null) == null)
{
entry.Property(property.Name).IsModified = false;
}
}
dbContext.SaveChanges();
return entity;
}
public IHttpActionResult PutProduct(int id, Product product)
{
NorthwindEntities db = new NorthwindEntities();
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Attach(product);
// Only the fields you want to update, will change.
db.Entry(product).Property(p => p.ProductName).IsModified = true;
db.Entry(product).Property(p => p.UnitPrice).IsModified = true;
db.Entry(product).Property(p => p.UnitsInStock).IsModified = true;
// only if if the value is not null, the field will change.
db.Entry(product).Property(p => p.UnitsOnOrder).IsModified =
product.UnitsOnOrder != null;
db.SaveChanges();
return Ok(product);
}
This is a somewhat tedious problem I've had to solve.
For lack of a more straightforward solution, eventually I decided I'd rather not want to try solving it as much downstream as when EF's SaveChanges-and-the-likes' get called (i.e., no need to "hook into" EF so late), but instead as much higher upstream / earlier on as possible --
that is, to do so right after I obtain a satisfying deserialization, which is meaningful to mutate the model, on a per-entity instance basis (in my use case, none of the updatable properties would represent relationships, but only independent attributes, in E/R parlance -- YMMV)
So, I opted for a "Populate" helper, along the lines of:
static void Populate(object from, object to)
{
var sourceType = from.GetType();
foreach (PropertyInfo target in to.GetType().GetProperties())
{
// Is the property at the target object writable and *not* marked
// as `[NotMapped]'?
var isUpdatable =
target.CanWrite &&
(target.GetCustomAttribute<NotMappedAttribute>(true) == null);
if (isUpdatable)
{
// If so, just find the corresp. property with the same name at the source object
// (caller is assumed responsible to guarantee that there is one, and of the same type, here)
var source = sourceType.GetProperty(target.Name);
var #default = sourceType.IsValueType ? Activator.CreateInstance(sourceType) : null;
var equality = (IEqualityComparer)typeof(EqualityComparer<>).MakeGenericType(sourceType).GetProperty("Default", BindingFlags.Public | BindingFlags.Static).GetValue(null);
var value = source.GetValue(from);
// Test for <property value> != default(<property type>)
// (as we don't want to lose information on the target because of a "null" (or "default(...)") coming from the source)
if (!equality.Equals(value, #default))
{
target.SetValue(to, value, null);
}
}
}
}
where "from" is the fresh entity instance that just got partially populated by whatever deserialization code, and where "to" is the actual target entity that lives in the DbContext (be it an EF proxy or not);
and where NotMappedAttribute is the EF's usual.
You'd typically have Populate to be called some time after the deserialization (&/or DTO-mapping) onto your "from" instance is done, but anyway before SaveChanges() gets call on your DbContext for all the "to" entities -- obviously, we assume there is a feasible 1-to-1 mapping "from" ... "to", that the caller of Populate knows about / could figure out.
Note I still don't know if there is a more elegant (more straightforward) way to do that, without recourse to reflection -- so, there, FWIW.
Remarks
1) above code can (or should) be made more defensive in various ways, depending on the caller assumptions;
2) one may want to cache those IEqualityComparer's (&/or the PropertyInfo's) for whatever (good) reason may arise -- in my case, I didn't have to;
3) finally, my understanding is that third-party librairies such as AutoMapper are also specially designed for that sort of task, if you can afford the additional dependency
'HTH,

Failure to attach a detached entity (entity with the same key is already in the context)

I'm using Entity Framework 6, Code First approach. I'll try to present my problem with a simple piece of code:
public void ViewEntity(MyEntity Entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
Db.MyEntities.Attach(Entity); // Exception
}
}
The exception message is: Attaching an entity of type 'MyProgram.MyEntity' failed because another entity of the same type already has the same primary key value.
From what I've read on MSDN it's an expected behaviour. But what I want on that last line is to first check if there is an entity with the same key already attached to a context; if it is, use it instead, and only otherwise attach my entity to context.
But I've failed to find a way to do so. There are many utility methods on ObjectContext instance (for example GetObjectByKey). I can't test them all 'cause they all ultimately need a qualifiedEntitySetName, and I don't have any in my real imlpementation, because this method should be on an abstract class and it should work for all entity types. Calling Db.Entity(this) is no use, there is no EntityKey which would have EntitySetName.
So all of this became complex really fast. And in my terms I just want to check if the object is already in "cache" (context), use it, otherwise use my object and attach it to this context.
To be clear, I have a detached object from a TreeNode.Tag in the first place, and I just want to use it again, or if it's impossible; if there already is one in the context), use that one instead. Maybe I'm missing some crucial concepts of EF6, I'm just starting out with EF.
I've found a solution for me. As I guessed correctly ObjectContext.GetObjectByKey method does what I need, but first I needed to construct qualifiedEntitySetName, and I found a way to do so. A tad bit cumbersome (using reflection, iterating properties of MyDbContext), but does not compare to a headache of a problem I made out of all this. Just in case, here's the patch of code that is a solution for me:
public SdsAbstractObject GetAttachedToContext()
{
var ObjContext = (SdsDbContext.Current as IObjectContextAdapter).ObjectContext;
var ExistingItem = ObjContext.GetObjectByKey(GetEntityKey()) as SdsAbstractObject;
if (ExistingItem != null)
return ExistingItem;
else
{
DbSet.Attach(this);
return this;
}
}
public EntityKey GetEntityKey()
{
string DbSetName = "";
foreach (var Prop in typeof(SdsDbContext).GetProperties())
{
if (Prop.PropertyType.IsGenericType
&& Prop.PropertyType.GenericTypeArguments[0] == ObjectContext.GetObjectType(GetType()))
DbSetName = Prop.Name;
}
if (String.IsNullOrWhiteSpace(DbSetName))
return null;
else
return new EntityKey("SdsDbContext." + DbSetName, "Id", Id);
}
An Entity can be in one of five stages : Added, Unchanged, Modified, Deleted, Detached.
public void ViewEntity(MyEntity entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
// Set the Modified state of entity or you can write defensive code
// to check it before set the state.
if (Db.Entry(entity).State == EntityState.Modified) {
Db.Entry(entity).State = EntityState.Modified
}
// Attached it
Db.MyEntities.Attach(Entity);
Db.SaveChanges();
}
}
Since EF doesn't know which properties are different from those in the database, it will update them all.

Copy Properties of one object to another object

Relate to : Set Properties that not null using linq and reflection
Hi experts
I change the code in above link:
public static void MyCopy<T>(this T src, T dest)
{
var notNullProps = typeof(T).GetProperties()
.Where(x => x.GetValue(src, null) != null);
foreach (var p in notNullProps)
{
p.SetValue(dest, p.GetValue(src, null),null);
}
}
and I wrote this code to copy peroperties:
NorthwindModel1.Order ord1 = new NorthwindModel1.Order() {CustomerID="Nima",Freight=1.33m,ShipCity="Agha" };
NorthwindModel1.Order ord2 = new NorthwindModel1.Order() ;
ord1.MyCopy(ord2);
but I got this error:
The EntityReference has already been initialized. InitializeRelatedReference should only be used to initialize a new EntityReference during deserialization of an entity object.
please help me to solve this problem
As mentioned in the comments, your reflective code is not the problem, but the fact that (as the exception message explicitly tells) you are indirectly triggering a reset of one of your entity references. My advice is twofold: either modify your reflective code to ONLY copy scalar properties (strings, dates, etc..) -- alternatively ignore references and collections -- OR use serialization:
public static T CloneBySerialization<T>(this T source) where T : EntityObject {
var serializer = new DataContractSerializer(typeof(T));
using (var ios = new MemoryStream()) {
serializer.WriteObject(ios, source);
ios.Seek(0, SeekOrigin.Begin);
return ((T) serializer.ReadObject(ios));
}
}
I must warn you that with this approach you will end up the full object graph or references. If the cloned object is an entity, YOU WILL NOT BE ABLE to use it/attach it to another context, due to the fact that references and foreign keys have been also copied, "verbatim", and this will all likely result in conflicts. The problem gets worse if you're using identity columns in keys.
I've done a lot of magic in my previous work on these matters, and as far as cloning is concerned, the code above is all you need. All, really.
However, to fix the context issue and the usability of your cloned entity, you will have to clear-off the references and under the assumptions that you're also working with "root" entities in the ¹ ↔ * directional relational graph (I hope I am remotely clear, because the story is long) the following will also be necessary.
public static void ClearReferences(this EntityObject entity) {
if (entity == null)
return;
foreach (var p in entity.GetType().GetProperties()) {
if (p.PropertyType.IsGenericType) {
var propertyType = p.PropertyType.GetGenericTypeDefinition();
if (propertyType == typeof(EntityReference<>)) {
var reference = p.GetValue(entity) as dynamic;
if (reference.EntityKey != null) {
reference.EntityKey = null;
((EntityObject) reference.Value).ClearReferences();
}
}
if (propertyType == typeof(EntityCollection<>)) {
var children = (p.GetValue(entity) as IEnumerable<EntityObject>).ToList(); // covariance
foreach (var child in children)
child.ClearReferences();
}
}
}
}
So the idea is that you first clone (via serialization/deserialization), then you "purify".

c# copied property loses reference when referenced object is removed from list

Example
Have a look at the following code:
private void DeDuplicateOrganisations()
{
var profileOrgs = _organisations.Where(o => o.ExistsInProfile).ToList();
var kvkOrgs = _organisations.Where(o => !o.ExistsInProfile).ToList();
profileOrgs.ForEach(o =>
{
var duplicate = kvkOrgs.FirstOrDefault(k => k.KvK == o.KvK || k.Title == o.Title);
if (duplicate != null)
{
o.CompanyInfoOrganisation = duplicate.CompanyInfoOrganisation;
o.ExistsInBoth = true;
kvkOrgs.Remove(duplicate);
}
});
_organisations = profileOrgs.Concat(kvkOrgs).OrderBy(o => o.Title).ToList();
}
In this example the property CompanyInfoOrganisation (simply a get; set; property) is copied when an organisation is considered a duplicate. This all works as expected, duplicates are nicely deduplicated.
Also this is true inside this message:
_organisations.First(o => o.ExistsInBoth).CompanyInfoOrganisation != null;
Problem
Now I bind the _organisations list to a listbox
lbxCompanies.DataSource = null;
lbxCompanies.DataSource = _organisations;
lbxCompanies.DisplayMember = "Title";
lbxCompanies.SelectedIndex = -1;
and later on get the selected value:
var org = lbxCompanies.SelectedValue as Organisation;
gbxCompanyInfo.Visible = org != null;
if (gbxCompanyInfo.Visible)
if (org.CompanyInfoOrganisation != null)
// NEVER GETS HERE (but gbxComanpyInfo is visible)
If I try to read the CompanyInfoOrganisation property I always get null while I know the property was set.
Question
What is happening here? How come the property reference is destroyed? How can I prevent this from happening?
The reference you're using only has immediate scope and as soon as the query ends it exits scope and your reference disappears. So when you bind later, the reference is exactly right -- null.
profileOrgs.ForEach(o =>
{
// Right here -- var duplicate has scope ONLY within your query.
// As soon as the query is executed it leaves scope and the reference
// pointer will be null
var duplicate = kvkOrgs.FirstOrDefault(k => k.KvK == o.KvK || k.Title == o.Title);
if (duplicate != null)
{
o.CompanyInfoOrganisation = duplicate.CompanyInfoOrganisation;
o.ExistsInBoth = true;
kvkOrgs.Remove(duplicate);
}
});
Because you're using a class, you need to perform a deep MemberwiseClone on it to get a NEW copy of the object:
o.CompanyInfoOrganisation = (YourInfoType)duplicate.CompanyInfoOrganisation.MemberwiseClone();
When you load the data, load the CompanyInfoOrganisation property along with the root entity; that way it will be already loaded into memory. If using LINQ to SQL, you load via DataLoadOptions, and pass this to the context. If using Entity Framework, you use the Include method in the LINQ query.
It might have to do with capturing of variables inside the lambda. Try substituting the .ForEach to a regular foreach().
Or maybe the CompanyInfoOrganisation in duplicate was null to begin with.
The problem was I used string.Join() to show the values, and the first value to join was null (which is really annoying), resulting in an empty string, leaving me thinking the property was null. However it turned out the property was not null, but has a perfectly valid reference to the object needed. Using the debugger with a little more care would have saved me an hour or so...
Sorry!

Categories

Resources