Automapper: To do an update on existing entity object - asp.net-mvc

I want to update an existing entity object from another model. But each time I got a new object (Having the mapped properties and default values for other properties.) Instead, I want a partially updated destination object.
AutoMapper.Mapper.Initialize(cfg => cfg.CreateMap<Customer, MYPOCO>().ReverseMap());
public void UpdateEntity(Customer customerSrc)
{
MYPOCO pocoDesc= dbContext.DD_POCO.SingleOrDefault(m => m.Id == 123);
pocoDesc = AutoMapper.Mapper.Map<Customer, MYPOCO>(customerSrc, pocoDesc);
// Here "pocoDesc" is a new object, I got only "customerSrc" data and lost all other existing properties values.
}
Automapper: 6.2.2(version)
Tried Automapper: Update property values without creating a new object
Any Idea?

if it still issue, try the following :
public void UpdateEntity(Customer customerSrc)
{
MYPOCO pocoDesc= dbContext.DD_POCO.SingleOrDefault(m => m.Id == 123);
AutoMapper.Mapper.Map<Customer, MYPOCO>(customerSrc, pocoDesc);
dbContext.DD_POCO.Update(pocoDesc);
dbContext.Save();
}

Related

Error in Updating dbModel of entity framework

This blow code for my post edit action :
public ActionResult EditProduct(EditProductModel viewModel,HttpPostedFileBase file)
{
if (Session["AdminId"] != null && Session["AdminName"] != null)
{
repository = new Repository();
var pro = repository.FindProductById(viewModel.Id);
// Automappper configoration.
var config = new MapperConfiguration(cgf => cgf.CreateMap<EditProductModel, Product>());
var mapper = config.CreateMapper();
pro = mapper.Map<Product>(viewModel);
repository.UpdateProduct(pro);// Error
repository.SaveChanges();
return RedirectToAction("ShowProduct",new{id = AdminId});
}
return RedirectToAction("AdminLogin");
}
The repository.UpdateProduct() Containing this code:
public void UpdateProduct(Product obj)
{
entities.Entry(obj).State = EntityState.Modified;
}
But this top code throws this error :
Attaching an entity of type 'MobileShop.Models.Product' 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.
How I can solve it?
Your pro object is already loaded from context, and it is tracked by EF. Changing it's state to modified in Update method is redundant, and hence giving this error.
So you can either skip the code in your Update method that changes the state,
OR comment out the following lines, because you are anyway building your entity from viewModel
repository = new Repository();
var pro = repository.FindProductById(viewModel.Id);
As a rule; if an object has been loaded from context, dont attach it
And if it has been created outside the context, set its state to modified.
A good read - https://msdn.microsoft.com/en-us/data/jj592676.aspx
Add this lines for updating and saving:
using (var Context = new MobileDatabase_1_Entities())
{
Context.Entry(pro).State = EntityState.Modified;
Context.SaveChanges();
}

Repository pattern giving exception while updating record

In my MVC application, I have been using Repository pattern for DAL.
Now, when I do select one entity record and and update the entity field value and do Update operation then getting below error.
Attaching an entity of type 'DAL.User' 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."} System.Exception
Below is repository stuff:
public void Update(TEntity entity)
{
if (_context.Entry(entity).State != EntityState.Modified)
{
_dbSet.Attach(entity);
_context.Entry(entity).State = EntityState.Modified;
}
}
Calling as follow:
In Bussines layer library:
Manager class :
private readonly IUnitOfWork _unitOfWork;
private IRepository <User , int> UserRepository
{
get
{
return _unitOfWork.GetRepository<AccountUser, int>();
}
}
public void UpdateUserEntity(UserDTO u)
{
try
{
User model = new User ();
UserRepository.Update(Mapper.Map(u, model));
_unitOfWork.SaveChanges();
}
catch (Exception ex)
{
throw;
}
}
Please guide me how I could resolve above error.
The exception says that there is another entity with the same key that has been attached, but different reference.
The exception could be caused by previous attached entity.
db.Set<Entity>().Attach(new Entity { Id = 123 });
db.Set<Entity>().Attach(new Entity { Id = 123 }); // different reference but same key
Or could be also caused by tracked entity that automatically attached.
db.Set<Entity>().FirstOrDefault(e => e.Id == 123); // automatically attached
db.Set<Entity>().Attach(new Entity { Id = 123 }); // different reference but same key
The second cause can be solved by mentioning AsNoTracking when retrieving item.
db.Set<Entity>().AsNoTracking().FirstOrDefault(e => e.Id == 123);
Or to be safe you can use this extension to always detach any attached entity.
public static class DbSetExtension
{
public static void SafeAttach<T>(
this DbContext context,
T entity,
Func<T, object> keyFn) where T : class
{
var existing = context.Set<T>().Local
.FirstOrDefault(x => Equals(keyFn(x), keyFn(entity)));
if (existing != null)
context.Entry(existing).State = EntityState.Detached;
context.Set<T>().Attach(entity);
}
}
Usage.
db.SafeAttach(entity, e => e.Id);
It's because of the reason,
"TEntity entity as a new object instead of the one which already exists".
Means,Entity framework treats each new object as new entry.(eventhough with same existing old data,PK & all).
Solution is,
First retrieve the object from database
Do/assign the changes to the same object (preferably without changing Primary key)
Then do state as Modified ,Update,SaveChange()

Is it possible to auto update only selected properties on an existent entity object without touching the others

Say I have a bunch of boolean properties on my entity class public bool isActive etc. Values which will be manipulated by setting check boxes in a web application. I will ONLY be posting back the one changed name/value pair and the primary key at a time, say { isActive : true , NewsPageID: 34 } and the default model binder will create a NewsPage object with only those two properties set. Now if I run the below code it will not only update the values for the properties that have been set on the NewsPage object created by the model binder but of course also attempt to null all the other non set values for the existent entity object because they are not set on NewsPage object created by the model binder.
Is it possible to somehow tell entity framework not to look at the properties that are set to null and attempt to persist those changes back to the retrieved entity object and hence database ? Perhaps there's some code I can write that will only utilize the non-null values and their property names on the NewsPage object created by model binder and only attempt to update those particular properties ?
[HttpPost]
public PartialViewResult SaveNews(NewsPage Np)
{
Np.ModifyDate = DateTime.Now;
_db.NewsPages.Attach(Np);
_db.ObjectStateManager.ChangeObjectState(Np, System.Data.EntityState.Modified);
_db.SaveChanges();
_db.Dispose();
return PartialView("MonthNewsData");
}
I can of course do something like below, but I have a feeling it's not the optimal solution. Especially considering that I have like 6 boolean properties that I need to set.
[HttpPost]
public PartialViewResult SaveNews(int NewsPageID, bool isActive, bool isOnFrontPage)
{
if (isActive != null) { //Get entity and update this property }
if (isOnFontPage != null) { //Get entity and update this property }
}
API is not strongly typed but you can do it as follows. DbContext API has better support for this.
[HttpPost]
public PartialViewResult SaveNews(NewsPage Np)
{
Np.ModifyDate = DateTime.Now;
_db.NewsPages.Attach(Np);
var entry = _db.ObjectStateManager.GetObjectStateEntry(Np);
var cv = entry.CurrentValues;
if (isActive)
{
cv.SetBoolean(cv.GetOrdinal("isActive"), true);
}
_db.SaveChanges();
_db.Dispose();
return PartialView("MonthNewsData");
}
You can go for two options
Register a custom model binder for that action. In the custom model binder you have to get the complete object from the database and only update the POSTed properties.
Use a view model. Instead of directly having the NewsPage model as the action parameter. You can create a custom view model that wraps the necessary properties. Inside the action you have to make a call to db to get the complete NewsPage instance and update only the corresponding properties from the view model.
Somewhat ugly, but did the trick in my case without having to create and register custom model binder or using multiple if statements.
[HttpPost]
public void SaveNews(string propname, bool propvalue, int PageID)
{
var prop = typeof(NewsPage).GetProperties().FirstOrDefault(x => x.Name.ToLower() == propname.ToLower());
var Np = _db.NewsPages.FirstOrDefault(x => x.PageID == PageID);
prop.SetValue(Np, propvalue, null);
Np.ModifyDate = DateTime.Now;
_db.SaveChanges();
_db.Dispose();
}

Best way to transfer an Entity Framework object over the web and back via JSON

I've got some MVC code that serializes an EF 3.5 object into an anonymous type for return as a JSON result to an AJAX call on my page. The hurdle I have is that when I send the object back to the server via JSON, (and let the ModelBinder deserialize it for me into my EF type), I have to update it in my Entity Framework context manually. Or at least that's what I'm doing now. It has no EntityKey, so attaching it fails. I end up having to look up the old object and update it property by property. Any ideas around this? Is the solution to pass the EntityKey around with my object?
Here's what I have:
public void Update(Album album)
{
using (var db = new BandSitesMasterEntities())
{
var albumToUpdate = db.Album.First(x => x.ID == album.ID);
albumToUpdate.AlbumTitle = album.AlbumTitle;
albumToUpdate.Description = album.Description;
albumToUpdate.ReleaseYear = album.ReleaseYear;
albumToUpdate.ImageURL = album.ImageURL;
albumToUpdate.OtherURL = album.OtherURL;
db.SaveChanges();
}
}
And here's what I'd like to do, or something similar:
public void Update(Album album)
{
using (var db = new BandSitesMasterEntities())
{
db.Attach(album)
db.SaveChanges();
}
}
or you could use AutoMapper to map those fields for you, so you'd just add one extra line to your example.
Why not just use the UpdateModel or TryUpdateModel controller methods instead? It works really well with EF and you can even explicitly set the included property list.
The id parameter will auto-map via the MVC framework to the hidden field on your form specifying the id.
public void Update(int id, FormCollection collection)
{
using (var db = new BandSitesMasterEntities())
{
var albumToUpdate = db.Album.First(x => x.ID == id);
//use UpdateModel to update object, or even TryUpdateModel
UpdateModel(albumToUpdate, new string[] { "AlbumTitle", "Description", "ReleaseYear", "ImageURL", "OtherURL" });
db.SaveChanges();
}
}
This became much easier for us in EF 4.0. This is what we did in EF 3.5:
public static void AttachAsModified(this ObjectContext objectContext, string setName, object entity,
IEnumerable<String> modifiedFields)
{
objectContext.AttachTo(setName, entity);
ObjectStateEntry stateEntry = objectContext.ObjectStateManager.GetObjectStateEntry(entity);
foreach (String field in modifiedFields)
{
stateEntry.SetModifiedProperty(field);
}
}
And then:
using (var db = new BandSitesMasterEntities())
{
db.AttachAsModified("Album", album, new string[] { "AlbumTitle", "Description", "ReleaseYear", "ImageURL", "OtherURL" })
db.SaveChanges();
}
It becomes more complicated if you have foreign key constraints, but it looks like you don't.
There is no way around the entity key issue. You either have to add it to your anonymous type or I would recommend you port your code to using data services.
http://www.hanselman.com/blog/jQueryToShipWithASPNETMVCAndVisualStudio.aspx
which would allow you to do all of the db manipulation on the client side.
http://msdn.microsoft.com/en-us/data/bb931106.aspx
Did you try something like:
object original;
var key = contexte..CreateEntityKey("EntitySet", modified);
if(contexte.TryGetObjectByKey(key, out original))
{
var originalEntity = (YourEntityType)original;
// You have to mannualy set your entityKey
originalEntity.YourEntityReference.EntityKey = new EntityKey("Entities.EntitySet", "Id", modified.YourEntity.Id);
contexte.ApplyPropertyChanges("EntitySet", modified);
}
contexte.SaveChanges();
Assuming that your EntityReference are set by dropDown, you'll still have the Id
In your Album entity's partial class you may define a CopyFrom function and call it from your Update function
partial class Album
{
public void CopyFrom(Album album)
{
//individual field copying here
}
}
public void Update(Album album)
{
...
albumToUpdate.CopyFrom(album);
...
}

Calling UpdateModel with a collection of complex data types reset all non-bound values?

I'm not sure if this is a bug in the DefaultModelBinder class or what.
But UpdateModel usually doesn't change any values of the model except the ones it found a match for.
Take a look at the following:
[AcceptVerbs(HttpVerbs.Post)]
public ViewResult Edit(List<int> Ids)
{
// Load list of persons from the database
List<Person> people = GetFromDatabase(Ids);
// shouldn't this update only the Name & Age properties of each Person object
// in the collection and leave the rest of the properties (e.g. Id, Address)
// with their original value (whatever they were when retrieved from the db)
UpdateModel(people, "myPersonPrefix", new string[] { "Name", "Age" });
// ...
}
What happens is UpdateModel creates new Person objects, assign their Name & Age properties from the ValueProvider and put them in the argument List<>, which makes the rest of the properties set to their default initial value (e.g. Id = 0)
so what is going on here?
UPDATE:
I stepped through mvc source code (particularly DefaultModelBinder class) and here is what I found:
The class determines we are trying to bind a collection so it calls the method: UpdateCollection(...) which creates an inner ModelBindingContext that has a null Model property. Afterwards, that context is sent to the method BindComplexModel(...) which checks the Model property for null and creates a new instance of the model type if that is the case.
That's what causes the values to be reset.
And so, only the values that are coming through the form/query string/route data are populated, the rest remains in its initialized state.
I was able to make very few changes to UpdateCollection(...) to fix this problem.
Here is the method with my changes:
internal object UpdateCollection(ControllerContext controllerContext, ModelBindingContext bindingContext, Type elementType) {
IModelBinder elementBinder = Binders.GetBinder(elementType);
// build up a list of items from the request
List<object> modelList = new List<object>();
for (int currentIndex = 0; ; currentIndex++) {
string subIndexKey = CreateSubIndexName(bindingContext.ModelName, currentIndex);
if (!DictionaryHelpers.DoesAnyKeyHavePrefix(bindingContext.ValueProvider, subIndexKey)) {
// we ran out of elements to pull
break;
}
// **********************************************************
// The DefaultModelBinder shouldn't always create a new
// instance of elementType in the collection we are updating here.
// If an instance already exists, then we should update it, not create a new one.
// **********************************************************
IList containerModel = bindingContext.Model as IList;
object elementModel = null;
if (containerModel != null && currentIndex < containerModel.Count)
{
elementModel = containerModel[currentIndex];
}
//*****************************************************
ModelBindingContext innerContext = new ModelBindingContext() {
Model = elementModel, // assign the Model property
ModelName = subIndexKey,
ModelState = bindingContext.ModelState,
ModelType = elementType,
PropertyFilter = bindingContext.PropertyFilter,
ValueProvider = bindingContext.ValueProvider
};
object thisElement = elementBinder.BindModel(controllerContext, innerContext);
// we need to merge model errors up
VerifyValueUsability(controllerContext, bindingContext.ModelState, subIndexKey, elementType, thisElement);
modelList.Add(thisElement);
}
// if there weren't any elements at all in the request, just return
if (modelList.Count == 0) {
return null;
}
// replace the original collection
object collection = bindingContext.Model;
CollectionHelpers.ReplaceCollection(elementType, collection, modelList);
return collection;
}
Rudi Breedenraed just wrote an excellent post describing this problem and a very helpful solution. He overrides the DefaultModelBinder and then when it comes across a collection to update, it actually updates the item instead of creating it new like the default MVC behavior. With this, UpdateModel() and TryUpdateModel() behavior is consistent with both the root model and any collections.
You just gave me an idea to dig into ASP.NET MVC 2 source code.
I have been struggling with this for two weeks now. I found out that your solution will not work with nested lists. I put a breakpoint in the UpdateCollection method ,and it never gets hit. It seems like the root level of model needs to be a list for this method to be called
This is in short the model I have..I also have one more level of generic lists, but this is just a quick sample..
public class Borrowers
{
public string FirstName{get;set;}
public string LastName{get;set;}
public List<Address> Addresses{get;set;}
}
I guess that, I will need to dig deeper to find out what is going on.
UPDATE:
The UpdateCollection still gets called in asp.net mvc 2, but the problem with the fix above is related to this HERE

Resources