MVC2 RTM - model binding complex objects using Entity Framework - 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.

Related

Clear related items followed by parent entity update - Many to Many EntityFramework with AutoMapper

I am trying to remove all references followed by adding them back from a list of disconnected objects.
using(var scope = new TransactionScope())
{
_autoIncidentService.AddNewCompanyVehicles(
autoIncidentModel.CompanyVehiclesInvolved.Where(v => v.Id == 0));
_autoIncidentService.ClearCollections(autoIncidentModel.Id);
_autoIncidentService.Update(autoIncidentModel);
scope.Complete();
}
return Json(ResponseView);
The ClearCollections removes items references. The GetAutoIncident includes the collection.
public void ClearCollections(int id)
{
var autoIncident = GetAutoIncident(id);
foreach (var vehicle in autoIncident.CompanyVehiclesInvolved.ToArray())
{
autoIncident.CompanyVehiclesInvolved.Remove(vehicle);
}
db.SaveChanges();
}
When I try to update the entity right after the ClearCollections method it fails.
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
I am using a singleton to get the DbContext so there shouldn't be any situation where the context is different. It is being stored in the HttpContext.Current.Items.
The update method is as follows:
public override void Update(AutoIncidentModel model)
{
var data = GetData(model.Id);
Mapper.CreateMap<AutoIncidentModel, AutoIncident>()
.ForMember(m => m.CompanyVehiclesInvolved, opt => opt.ResolveUsing(m =>
{
var ids = m.CompanyVehiclesInvolved.Select(v => v.Id);
return db.Vehicles.Where(v => ids.Contains(v.Id)).ToList();
}));
Mapper.Map(model, data);
db.SaveChanges();
}
Obviously, I am missing something important here. Do the entites from my ResolveUsing method need to somehow be associated with the parent entity or is automapper overwriting the property (CompanyVehiclesInvolved) and causing a problem?

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?

EF POCO's can't update navigation property

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.

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
}

MVC3: delete an object in an database from an array of selected objects

I've successfully selected the Objects I want to delete. But the problem is when I remove an item from Object array, it doesn't make any changes. My code is following below..
My database
public List<Product> db = new ProductRepository().GetProducts();
Here it shows all the products with checkbox..
public ActionResult MultipleDeletes()
{
return View(db);
}
On Submitting "Button named Delete", I got problem.
[HttpPost]
public ActionResult MultipleDeletes(int[] selectedProducts)
{
var del_products = from x in selectedProducts
from y in db
where y.ProductId == x
select y;
foreach (var item in del_products)
{
//Product p = db.Find(item.ProductId);
//db.Remove(p);
//db.SaveChanges();
}
return View(db);
}
Could anyone help me?
can you also tell me, how to write Lambda expression instead of LinQ?
I think in this case you need to make use of Delete rather than remove.
Use following and see if it works
db.DeleteObject(p)
db.SaveChanges()
The problem was in the model. I used NBuilder.. so it didn't really save the data.
I built an DbContext. Then it worked.
Solution is ..
public ProductDBContext db = new ProductDBContext();
[HttpPost]
public ActionResult MultipleDeletes(int[] selectedProducts)
{
foreach (int item in selectedProducts)
{
Product product = db.Where(p => p.ProductId == item).SingleOrDefault();
db.Remove(product);
db.SaveChanges();
}
return View();
}