EF POCO's can't update navigation property - entity-framework

I'm using EF5 with MVC and POCO's and need a little help
I have an update function which is passed a disconnected POCO. The POCO has a 'navigation property' collection, eg: Provider has
public virtual ICollection<Company> Companies { get; set; }
When the Provider was loaded (and the old context closed) it had two Company objects, now it has four and I'd like to update.
I thought the code below might work but Companies is not updated (but the non-navigational properties of Provider (like string Name {get;set} are still updated ok) and there is no error
public void Update(Provider entity)
{
// Existing entity
_context.Entry(entity).State = EntityState.Modified;
if (entity.Companies.Any())
{
//try to tell EF about the companies
foreach (var company in entity.Companies)
{
//the company exists already - let the context know....
_context.Entry(company).State = EntityState.Modified;
_context.Companies.Attach(company);
}
}
}
... and later: _unitOfWork.SaveChanges();
For an insert of Provider with Companies I had successfully used:
if (entity.Companies.Any())
{
//these are not to be created - they exist -
//I want EF to add them as nav properties
foreach (var company in entity.Companies)
{
//the company exists already - let the context know....
_pvpContext.Companies.Attach(company);
}
}
// New entity
_pvpContext.Entry(entity).State = EntityState.Added;
I'm going to go and read Julia Lerman's book as EF is killing me - but I'd really appreciate any help updating 'Companies' in the meantime - Thx
Edit:
Taking #Manos' kind advice I tried:
List<Company> companies = new List<Company>();
if (entity.Companies != null && entity.Companies.Any())
{
//pull out the Companies from the POCO
companies = entity.Companies.ToList();
//remove them
entity.Companies = new Collection<Company>();
entity.Companies.Clear();
}
// pass existing entity to the context, tagged as modified
_pvpContext.Entry(entity).State = EntityState.Modified;
if (companies.Any())
{
//now re-add the companies while the context is listening. ffs.
foreach (var company in companies)
{
entity.Companies.Add(company);
}
}
If I add the Provider.Companies to the context (like in the insert) I get:
Violation of PRIMARY KEY constraint 'PK__tmp_ms_x__679519B7F943FD8D'.
Cannot insert duplicate key in object 'dbo.ProviderCompany'. The
duplicate key value is (5, 3)
which is odd as there is not composite key of (provider 5, company 3) - so maybe it's trying to add it in twice here?
If I don't pre-add the Provider.Companies I get:
at System.Data.Entity.Internal.InternalContext.SaveChanges() at
System.Data.Entity.Internal.LazyInternalContext.SaveChanges() at
System.Data.Entity.DbContext.SaveChanges()

I only have 4.1 to test, but try this as a basic logic:
public void Update(Provider entity)
{
// Existing entity
Provider contextProvider = _context.Entry(entity);
contextProvider.Companies.Clear();
foreach (var company in entity.Companies)
{
contextProvider.Companies.Add(company);
}
}
This needs a little refinement in order to only add new companies as opposed to doing a complete removal and reinstatement but it should work.
Edit in response to comment:
Try catching the exception thrown by SaveChanges() with the following:
try {
_unitOfWork.SaveChanges();
} catch (System.Data.Entity.Validation.DbEntityValidationException e) {
foreach (var k in e.EntityValidationErrors) {
foreach (var e1 in k.ValidationErrors) {
Console.WriteLine("{0} - {1}", e1.PropertyName, e1.ErrorMessage);
}
}
}
It should give you a little more information to go on.

Related

efcore change modified state to update entity is not working with sub data objects

I set up a Generic repository using this code for update
private void AttachIfNot(TEntity entityToActive)
{
if (_dbContext.Entry(entityToActive).State == EntityState.Detached)
{
_dbSet.Attach(entityToActive);
}
}
private void UpdateEntity(TEntity entityToUpdate)
{
AttachIfNot(entityToUpdate);
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
}
It just attach the entity and set the modified state to save.
But when I use efocre ownsone to map a value object,the update entity function is not working.
I found out that it only works when I set Valueobject to modified too.
_dbContext.Entry(entityToUpdate).State = EntityState.Modified;
_dbContext.Entry(entityToUpdate.Valueobject).State = EntityState.Modified;
But It is hard for me to specify all the value objects in a Generic Repository.
This is code also has problems with one to many or other relations.
The working way is like this:
Classroom classroom = new Classroom
{
Id = 1,
Name = "b",
Students = new List<Student>
{
new Student()
{
Name = "aa",
Id = 2
}
}
};
if (_defaultDbContext.Entry(classroom).State == EntityState.Detached)
{
_defaultDbContext.Classrooms.Attach(classroom);
foreach(var stu in classroom.Students)
{
_defaultDbContext.Students.Attach(stu);
}
}
_defaultDbContext.Entry(classroom).State = EntityState.Modified;
foreach (var stu in classroom.Students)
{
_defaultDbContext.Entry(stu).State = EntityState.Modified;
}
_defaultDbContext.SaveChanges();
I found out one way is get the entity form repo then update it using automapper:
targetEntity = repo.GetById(entityId);
automapper.map(souceEntity,targetEntity);
//or
automapper.map(souceDto,targetEntity);
_dbContext.Save();
The entity comes by query, so the change will be tracked.
But I have to configure the automapper with this entity map when I want to change entity
CreateMap<EntityType, EntityType>();
I think it's not the best solution. Is there a bettere way?
DbContext.Update would be fine to fix this problem.
see:
https://www.learnentityframeworkcore.com/dbcontext/change-tracker

Entity Framework - Update entity but not child entity

In my EF 6 MVC app, I have an entity Seller which has a 1:1 relationship to SellerShippingPolicies. When I update the seller entity, EF is also attempting to update the SellerShippingPolicies entity, and I don't want this to happen.
I have the following method that updates a Seller entity:
public Entities.Seller Save(Entities.Seller seller)
{
// Instantiate a helper method
HelperMethods helper = new HelperMethods(this.UnitOfWork);
// Map the domain entity to an EF entity
var sellerRecord = Mapper.Map<Seller>(seller);
// Attempt to prevent the updating of the SellerShippingPolicies entity
helper.GetDbContext().Entry(sellerRecord.SellerShippingPolicies).State = EntityState.Detached;
// Save the entity
sellerRecord = helper.SaveItem<Seller>(sellerRecord);
}
Here is the SaveItem method that gets called:
public T SaveItem(T entity)
{
var row = this._dbSet.Find(GetPrimaryKeyValue(entity));
if ( row == null )
return AddItem(entity);
else
return UpdateItem(entity);
}
And the Update method that eventually gets called:
public T UpdateItem(T entity)
{
// Retrieve the current copy of the entity to be updated.
var currentEntity = GetItem(GetPrimaryKeyValue(entity));
// Copy the contents of the modified entity on top of the copy we just retrieved. This way EF will save everything correctly.
currentEntity = Copy.ShallowCopy<T>(entity, currentEntity);
this._dbContext.SaveChanges();
return currentEntity;
}
Not sure it's necessary, but here is the method for ShallowCopy and GetItem.
public static T ShallowCopy<T>(object source, T target)
{
foreach (PropertyInfo pi in typeof(T).GetProperties())
{
var property = source.GetType().GetProperty(pi.Name);
if (property == null)
continue;
if (property.GetSetMethod() != null)
property.SetValue(target, pi.GetValue(source, null), null);
}
return target;
}
public T GetItem(object primaryKeyValue)
{
return this._dbSet.Find(primaryKeyValue);
}
All these methods share the same context object.
You can see that I'm attempting to prevent the updating of the SellerShippingPolicies entity by setting its state to detached. This does not work. I've also tried setting the state to Unchanged. That doesn't work either. In both cases, EF attempts to update the SellerShippingPolicies entity. What am I missing?

Is this really the best way to update detached entities?

I have the following code:
Get contact
public Contact LoadContactFromId(int contactId)
{
using(var ctx = new ContactContext())
{
var contact = ctx.Contacts.AsNoTracking().First(c => c.ContactId == contactId);
return contact;
}
}
Update Contact
public void UpdateExistingContact(Contact updatedContact)
{
using(var ctx = new ContactContext())
{
ctx.Contacts.Attach(updatedContact);
ctx.Entry(updatedContact).State = EntityState.Modified;
foreach (var item in updatedContact.ContactPoints)
{
ctx.Entry(item).State = (item.ContactPointId == 0) ? EntityState.Added : EntityState.Modified;
}
ctx.SaveChanges();
}
}
I am currently working with ASP.NET webforms and because of its stateless model, I can't keep working within the DbContext. Because of this, I end up basically recreating the entire Contact entity from the POST data and then update it.
This seems like a horrible way to do this because I lose conncurrncey resolution since I'm not working with the true original entity and it seems kind of cumbersome to always have to recreate the object.
Is there an easier way to do this? Am I looking at this all wrong?

How can I update my DTO's ID when inserting multiple new entities

I'm using EF4. I'm adding a series of new entities from a list of DTOs, and I'm not saving changes until after all of them are added. I'm wanting to set the IDs of the DTOs to what the new entities' IDs are. How on earth do I do this? Does EF provide a mechanism for this?
With a single entity I would do this:
public void InsertMyDto(MyDto a_dto)
{
var newEntity = new MyEntity
{
Name = a_dto.Name,
Type = a_dto.Type.ToString(),
Price = a_dto.Price
};
_dataContext.MyEntities.AddObject(newEntity);
_dataContext.SaveChanges();
a_dto.ID = newEntity.ID;
}
This works fine, but what do I do in this case?
public void InsertMyDtos(IEnumerable<MyDto> a_dtos)
{
foreach (var myDto in a_dtos)
{
var newEntity = new MyEntity
{
Name = myDto.Name,
Type = myDto.Type.ToString(),
Price = myDto.Price
};
// Does some validation logic against the database that might fail.
_dataContext.MyEntities.AddObject(newEntity);
}
_dataContext.SaveChanges();
// ???
}
I want to save all at once, because I have validation work (not shown above) that is done against the database and fails before it gets to SaveChanges, and if it fails I want it to fail as a whole transaction (i.e. rollback).
I don't think that EF can help you here. It even can't help you for a single instance which forces you to write a_dto.ID = newEntity.ID. The counterpart of this code for multiple entites is to keep track of the pairs of dtos and new entities:
public void InsertMyDtos(IEnumerable<MyDto> a_dtos)
{
Dictionary<MyDto, MyEntity> dict = new Dictionary<MyDto, MyEntity>();
foreach (var myDto in a_dtos)
{
var newEntity = new MyEntity
{
Name = myDto.Name,
Type = myDto.Type.ToString(),
Price = myDto.Price
};
dict.Add(myDto, newEntity);
// Does some validation logic against the database that might fail.
_dataContext.MyEntities.AddObject(newEntity);
}
_dataContext.SaveChanges();
foreach (var item in dict)
item.Key.ID = item.Value.ID; // Key is MyDto, Value is MyEntity
}

MVC2 RTM - model binding complex objects using Entity Framework

I am new to MVC, and am really struggling with what I seems like it should be a very common scenario. I'm using MVC2 RTM, and the Entity Framework for my model objects.
What I have working:
An edit view for a parent object that contains a collection of child objects. The form displays all the editable fields for the parent, and iterates through and displays all editable fields for all the associated child objects (in the same view). I am able to successfully handle the edit action in my controller, but run into issues when I try to bind values in the form collection to the EF model objects.
The problem:
In my controller function, when I call TryUpdateModel and pass the parent object, I get the following error:
"The EntityCollection has already been initialized. The InitializeRelatedCollection method should only be called to initialize a new EntityCollection during deserialization of an object graph."
I have seen a lot of other posts from people struggling with similar issues, but have not found a solution. Is this not possible without building a custom model binder? If anyone has a working example, I would greatly appreciate it. For some reason, I am able to iterate through the child collection and successfully execute TryUpdateModel on the child objects, but when I call it on the parent, the error above is thrown. Ideally I'd like to call it once for the parent, and have the whole object tree update from the form.
Here's the controller code:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
EFEntities ef = new EFEntities();
ParentObject parent = ef.ParentObjects.SingleOrDefault(p => p.ID == id);
if (ModelState.IsValid)
{
int i = 0;
foreach (child in parent.ChildObjects)
{
//this works fine
TryUpdateModel(child, "ChildObjects[" + i + "]");
i++;
}
//this blows up
if (TryUpdateModel(parent))
{
ef.SaveChanges();
return RedirectToAction("Details", new { id = parent.ID });
}
}
return View(parent);
}
Thanks for this question, even though it wasn't answered it gave me my answer. The best thing I can find to do is this (using your example):
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(int id, FormCollection formValues)
{
EFEntities ef = new EFEntities();
ParentObject parent = ef.ParentObjects.SingleOrDefault(p => p.ID == id);
if (ModelState.IsValid)
{
int i = 0;
foreach (child in parent.ChildObjects)
{
//this works fine
TryUpdateModel(child, "ChildObjects[" + i + "]");
i++;
}
//exclude the collections and it won't blow up...
if (TryUpdateModel(parent, "Parent", null, new string[] {"ChildObjects"}))
{
ef.SaveChanges();
return RedirectToAction("Details", new { id = parent.ID });
}
}
return View(parent);
}
Ultimately I found a more elegant solution, but never came back to post it. Here it is - sorry for the delay:
if (ModelState.IsValid)
{
if (TryUpdateModel(parent, new[] "prop1", "prop2", "prop3" })) //specify parent-only properties to include
{
if (TryUpdateModel(parent.ChildObjects, "ChildObjects"))
{
_ef.SaveChanges();
return RedirectToAction("Details", new { id = parent.ID }) }
}
}
return View(parent);
I'm converting this code from a real life app, so my apologies for any typos.