How to clear an entity collection in mvc - c#

Using MVC3.0 with the razor view engine. I have an entity titled "Vendors" which contains a many to many reference to another entity called "Contacts". All I want to do is remove all of the references from a vendor in a specific controller. Here is my code:
public ActionResult _Edit(Vendor vendor)
{
if (ModelState.IsValid)
{
//Clear contact list
vendor.Contacts.Clear();
db.Vendors.Attach(vendor);
db.ObjectStateManager.ChangeObjectState(vendor, EntityState.Modified);
db.SaveChanges();
}
}
For some reason this doesn't work. and I am not sure why? Thanks

You should load vendor and its contacts from database, then update its properties and SaveChanges:
var vendor = ...retrieve vendor from db...;
vendor.Contacts.Load();
vendor.Contacts.Clear();
...update vendor properties...
db.SaveChanges();
I know it is not as efficient as attaching entity and saving state, but no one said EF is perfect.
By the way, direct binding to Entity Framework object can be very dangerous. Malicious user can prepare POST with values, that are not present in form you provided for him and change values of field, that you didn't want to be even modified in this action. You should create special view models.

Related

Updating a model with bind params is nulling out ones not in form

I'm new to C# and .NET core.
I'm wondering why when editing a model using bind, it creates a new model and binds the attributes from the post. But if you do not place all your fields in the form post as hidden and the bind it will null them out?
Shouldn't it load a model and update the bind parameters and leave the ones alone?
For example if I'm updating a person and person has
Id, Name, Age, updated, Created
Edit(int id, [Bind("Id,Name,Age") Person p]
When I go to _context.update(p), it's nulling out updated and Created because they weren't bound.
WHY does it work like that?
How can I make it only update the bound parameters without nulling out the ones I don't need to load?
What you pass in is a deserialized block of data that MVC is mapping into an entity definition. It doesn't auto-magically open a DbContext, load the entity, and overwrite values, it just creates an instance of the entity, and copies in the values across. Everything else would be left as defaults.
As a general rule I advise against ever passing entities from the client to the server to avoid confusion about what is being sent back. When performing an update, accept a view model with the applicable properties to update, and ideally the data model and view model should include a row version #. (I.e. Timestamp column in SQL Server that can be converted to/from Base64 string to send/compare in ViewModel)
From there, when performing an update, you fetch the entity by ID, compare the timestamp, then can leverage Automapper to handle copying data from the ViewModel into the Entity, or copy the values across manually.
That way, when your ViewModel comes back with the data to update:
using (var context = new AppDbContext())
{
var entity = context.Persons.Single(x => x.Id == id);
if (entity.TimeStampBase64 != viewModel.TimestampBase64)
{
// Handle fact that data has changed since the client last loaded it.
}
Mapper.Map(entity, viewModel);
context.SaveChanges();
}
You could use the entity definition as-is and still load the existing entity and use Automapper to copy values from the transit entity class to the DbContext tracked one, however it's better to avoid confusing instances of entities between tracked "real" entity instances, and potentially incomplete untracked transit instances. Code will often have methods that accept entities as parameters to do things like validation, calculations, etc. and it can get confusing if later those methods assume they will get "real" entities vs. getting called somewhere that has only a transient DTO flavour of an entity.
It might seem simpler to take an entity in and just call DbContext.Update(entity) with it, however this leaves you with a number of issues including:
You need to pass everything about the entity to the client so that the client can send it back to the server. This requires hidden inputs or serializing the entire entity in the page exposes more about your domain model to the browser. This increases the payload size to the client and back.
Because you need to serialize everything, "quick fixes" like serializing the entire entity in a <script> block for later use can lead to lazy-load "surprises" as the serializer will attempt to touch all navigation properties.
Passing an entity back to the server to perform an Update() means trusting the data coming back from the client. Browser debug tools can intercept a Form submit or Ajax POST and tamper with the data in the payload. This can open the door to unexpected tampering. DbContext.Update also results in an UPDATE statement that overwrites all columns whether anything changed or not, where change tracked entities will only build UPDATE statements to include values that actually changed only if anything actually changed.

My Stack is losing its value when I redirect to another controller

I have tried this using a List and it retains the value fine, but when i change it to a stack the value is lost. I have been stepping through but I can't understand why its gone.
​
public ActionResult AddToOrder()
{
//Get logged in student from db
var student = _db.Students.Find(User.Identity.GetUserId());
//Create a new Order
var order = new Order();
//add all elements from Student collection to Order collection
foreach (var app in student.Applications)
{
order.Applications.Add(app);
}
//assign order FK
order.StudentId = User.Identity.GetUserId();
//Push the order to the students Stack collection
student.Orders.Push(order); //(When the Student implements a List collection the value
//passes to the Stripe controller with no problems)
//add order to db & save changes
_db.Orders.Add(order);
_db.SaveChanges();
//redirect to stripe controller
return RedirectToAction("Create", "Stripe");
}
//STRIPE CONTROLLER
public ActionResult Create(string stripeToken)
{
var student = _db.Students.Find(User.Identity.GetUserId());
student.Orders.Count // = 0 with Stack
// = 1 with List
}
The only thing I change is Stack to List in the Student entity and push to add. Anybody any ideas whats going on?
I guess I could achieve what I want (LIFO) by using a list in this way. I would still like to know what's going on with my stack though.
myList.Insert(0, order) - - - my List.First()
Student is an Entity Framework entity - an object representation of a database table. Its Orders property represents a relationship to another table. When you add order objects to a Student's Orders property, Entity Framework does a ton of stuff under the hood to make sure that all of the object data is correctly written to the database as table data, with all of the proper foreign keys and such, when you call SaveChanges. Same thing when you call Students.Find: There's a lot of stuff going on to translate your code to a SQL query, run it, then turn the resulting SQL table data into a convenient Student object.
Entity Framework does all of that automatically, but in order for it to work, entity classes like Student need to be defined in a particular way such that EF knows how to handle them. To put it succinctly: properties like Orders that represent relationships to another table need to be Lists. When you redefine Student.Orders to be a Stack, the Entity Framework machinery breaks, and EF can no longer use Orders to work with that portion of the database.
When your app redirects from one controller to another, the orders data is not retained in memory in the app, only in the database. When the target controller loads, your code is trying to load the data from the database that the previous controller was supposed to have saved there. One of two things is happening:
Your change to use Stack broke the Students entity such that the data doesn't get saved to the database at all, OR
Entity Framework can manage to handle Stack well enough to save the data, but not load it.
I'm not sure which is happening with this particular change, but either one is undesirable.
Orders needs to be a List. If you need to preserve some kind of ordering to the list, that needs to be explicit within the data model so that it ends up in the database. Maybe your Order class needs a LineNumber property or something similar. If you do something like that, keep in mind that both the code and the database have to be updated. Search for "Entity Framework migrations" for information about that, and ask a separate question if you get stuck on it.

MVC 4 C# problems with my controller updating database

I have an issue with my method that updates the database with user information.
during HttpGet it uses a view model to ensure the integrity of the data, and in HttpPost, the data is passed back and is there. (ran some checks with breakpoints and everything was holding the correct user data that they should have been)
However, when I run my .Save() method, this information is not stored into the DB.
I've checked to see if its pointing to the correct database by changing the data manually, it comes up in a list view just fine. I'M missing something, just can't figure out what ><
Below is the code there is form data in all of the relevant view models, but it just doesn't save!
[HttpPost]
public ActionResult HouseCreate(CreateHouseViewModel viewModel)
{
if (ModelState.IsValid)
{
var house = new House();
house.address = viewModel.address;
house.postCode = viewModel.postCode;
house.noOfDisputes = viewModel.noOfDisputes;
_db.Save();
return RedirectToAction("House");
}
return View(viewModel);
}
house is an object of my house database, modelled correctly with correct Primary keys in place (double checked this in the database viewer.)
save calls:
void DataSources.Save()
{
SaveChanges();
}
No errors come up, which makes it even worse.
using (var context = new UnicornsContext())
{
var unicorn = new Unicorn { Name = "Franky", PrincessId = 1};
context.Unicorns.Add(unicorn); // your missing line
context.SaveChanges();
}
Source
Your "_db" object, I assume is entity framework.
This will have a collection on it, maybe _db.Houses ? You need to add the object to that.
Since it appears you are using Entity Framework, you'll need to inform Entity Framework that your model has been updated. Inbetween the HttpGet and the HttpPost methods, the model becomes dissassociated from the database context. I think the following code work to re-attach it to the context so you can save the changes:
context.Entry(myEntity).State = EntityState.Modified;
context.MyEntities.Attach(myModel);
context.SaveChanges();
Edit: Forgot to add the re-attach method part, assuming this is an existing record in the DB that your updating.
This I find is a far more reliable means to adding items to the database:
// Note: db is your dbContext, model is your view model
var entity = db.Entry(model); // Required to attach the entity model to the database
// you don't need the "var entity = " but its useful
// when you want to access it for logging
db.Entry(model).State = EntityState.Added; // Required to set the entity as added -
// modified does not work correctly
db.SaveChanges(); // Finally save

Entity Framework: Adding items to a collection wont persist in database

I created a project with a model first approach, in my model I declared a many to many relationship between the entities, Musician and Session (representing some kind of rehearsal or performance). As expected, this generated a pivot table in my database to handle the relationship, but no C# model class for the intermediate table. So, on my Session object I have a collection of Musicians declared as an ICollection:
public virtual ICollection<Musician> Musicians { get; set; }
Which is initialised as a HashSet in the Session constructor:
public Session()
{
this.Musicians = new HashSet<Musician>();
}
Now, somewhere else in my solution (in an MVC controller, if it matters), I want to assign an existing Musician to a given Session, so I have this code:
Musician musician = db.Musicians.First(m => m.Id == selectedMusician);
session.Musicians.Add(musician);
db.SaveChanges();
Just after session.Musicians.Add(musician) is called, I've inspected that the musician has been added to the collection, but calling db.SaveChanges doesn't seem to persist the change to the database.
By the way, db is my repository containing all my models, for example, this code successfully adds a new Session to the database:
db.Sessions.Add(session);
db.SaveChanges();
If somebody could point me in the right direction on how to add an object to a collection in this circumstance that would be a great help.
Cheers
EDIT: Turns out the problem had nothing to do with the fact that it was a collection. Like I mentioned in my post, I was trying to make the changes/saving from a controller class in an ASP MVC project. The session object was being passed in from the view, but I discovered any changes I made to this object wouldn't persist in the database (doh, silly me, should of checked this straight away). Looks like the object passed in by the view was just a copy as it wasn't coming directly from the context (in this case, my db object).
To fix it, I just queried the context for a Session object with the same Id in order to have access to the object that my changes would actually persist.

ASP.NET MVC with EF 4.1 Navigation properties

After days studying EF to understand (kinda..) how it works, I finally realized that I might have a big problem.
Imagine that I have two entities: Pais and UF. The relationship between them is Pais (0..1) ... (*) UF. A screenshot: http://i.imgur.com/rSOFU.jpg.
Said that, consider that I have a controller called UFController and it has actions for Edit and Create, which are just fine. My views are using the EditorFor helper (or similar ones) for inputs, so when I submit the form the controller will receive a UF object filled with all the data (automatically) with a reference to an almost-empty Pais. My view code (part of it):
#* UF attributes *#
#Html.EditorFor(m => m.Sigla)
#Html.EditorFor(m => m.Descricao)
#Html.EditorFor(m => m.CodigoIBGE)
#Html.EditorFor(m => m.CodigoGIA)
#* Pais primary key ("ID") *#
#Html.EditorFor(m => m.Pais.Codigo) // Pais id
The controller Edit action code:
[HttpPost]
public ActionResult Edit(UF uf)
{
try
{
if (ModelState.IsValid)
{
db.UFs.Attach(uf);
db.ObjectStateManager.ChangeObjectState(uf, EntityState.Modified);
db.SaveChanges();
return this.ClosePage(); // An extension. Just ignore it.
}
}
catch (Exception e)
{
this.ModelState.AddModelError("Model", e.Message.ToString());
}
return View(uf);
}
When I submit the form, this is what the action receives as uf:
{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
Descricao: "Foobar 2001"
ID: 936
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
And uf.Pais:
{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
Codigo: 0
CodigoBACEN: null
CodigoGIA: null
CodigoIBGE: null
Descricao: null
UF: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}
The original information (the one on the database) is uf.Pais.Codigo == 716. So, right now I'm receiving the updated information. The problem on that the controller is not upading the FK in the database.
I don't want to set the EntityState from uf.Pais to Modified because the entity itself wasn't changed (I didn't changed the information from that entry), but the relationship was.
In other words, what I'm trying to do is change the value of the FK, pointing the uf.Pais to another instance of Pais. Afaik, it's impossible to change the relationship state to Modified (throw an exception), so I'm looking for alternative solutions.
I've read a bunch of topics I've found on Google about this kind of problem, but I still didn't find a simple and elegant solution. The last ones I read here on stackoverflow:
How to work with navigation properties (/foreign keys) in ASP.NET MVC 3 and EF 4.1 code first
Strongly-Typed ASP.NET MVC with ADO.NET Entity Framework
Getting Error 3007 when I add my Entity Model
I asked a question a few days ago about a similar problem ( Entity Framework 4.1 - default EntityState for a FK? ). I didn't understand how EF works that time, so now a bunch of things look clear to me (that's why I'm opening a new question).
For the Create action I've been testing this solution (proposed by Ladislav on my other question), but it generates an additional select (which can be eventually slow for us):
// Here UF.Pais is null
db.UFs.AddObject(uf);
// Create dummy Pais
var pais = new Pais { Id = "Codigo" };
// Make context aware of Pais
db.Pais.Attach(pais); // <- Executing a SELECT on the database, which -can- be slow.
// Now make the relation
uf.Pais = pais;
db.SaveChanges();
I can replicate this for the Edit (I guess), but I don't want that additional SELECT.
So, in resume: I'm trying to use navigation properties to send data to the controller and save them directly in the database using a fast and easy way (without messing too much with the entity - these ones are simple, but we have huge and very complex ones with a lot of FKs!). My question is: there's a solution that doesn't involve executing another query in the database (a simple one)?
Thanks,
Ricardo
PS: sorry for any english mistakes and any confusions.
Update 1: using BennyM's solution (kind of..)
I tested the following code, but it doesn't work. It throws an exception: "An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key." Probably because Pais is already in the context, I guess?
I'm using a Entities (created by EF) class as context. Also, I don't know what is the method Entry, and I don't know where is it. Just for "fun", I tested this:
// Attach the Pais referenced on editedUF, since editedUF has the new Pais ID, not the old one.
Pais existingPais = new Pais { Codigo = editedUF.Pais.Codigo };
db.Paises.Attach(existingPais);
// Attach the edited UF.
db.UFs.Attach(editedUF);
// Set the correct Pais reference (ignoring the current almost-null one).
editedUF.Pais = existingPais;
// Change the object state to modified.
db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);
// Save changes.
db.SaveChanges();
return this.ClosePage();
The exception is throwed when I try to attach the editedUF to the current context. I'm working with this idea right now, trying to find other solutions. Also, you're right BennyM, attaching the Pais to the context is not generating an additional SELECT. I don't know what happened that time, it really doesn't do anything with the database.
Still, this is a manual solution: I have to do that for each FK. That's what I'm trying to avoid. You see, some programmers, even if you explain 100 times, won't remember to do that with each FK. Eventually that'll come back to me, so I'm trying to avoid anything that can lead into errors (database or code ones) to make sure everyone can work without any stress. :)
I'm answering my own question because I've found a simple solution (at least in my case). My scenario uses a lot of Views for data input (which means that I have a lot of entities). I needed a simple and easy to use solution, so I deleted my entire Entities EDMX file (Ctrl+A, Delete!).
Then I decided to add again Pais and UF entities, but checking the checkbox for exposing the FK attribute. On first I though they can't work together, but they can, but you need to be a little careful on how to use it. They're now linked with navigation properties and the exposed FK.
The reason I couldn't add the FK attribute is because I was doing it manually. Using the "Update model from database" again checking the correct option it worked flawless.
In my edit view, I'm setting the ID of Pais into the FK attribute, not the Pais.Codigo. The reason why I do that is because the FK attribute is a scalar property and then I can detect changes.
This is the current view code for the Pais input (it's not exactly it, but it's similar to this):
#Html.EditorFor(m => m.PaisCodigo)
Btw, PaisCodigo is the FK. Yes, it can get a little confusing with Pais.Codigo, but we didn't decided any naming rules (yet). Any suggestions on this idea would be appreciated.
The final Edit action code is like this (I removed error processing to make it look simple!):
[HttpPost]
public ActionResult Edit(UF editedUF)
{
if (ModelState.IsValid)
{
// Attach the edited UF into the context and change the state to Modified.
db.UFs.Attach(editedUF);
db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);
// Save changes.
db.SaveChanges();
// Call an extension (it's a redirect action to another page, just ignore it).
return this.ClosePage();
}
}
This is what is received when I submit the form for editedUF:
{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
CodigoPais: 0 <-- new Pais ID!
Descricao: "Foobar 2000"
ID: 902
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
Sigla: "RI"
Usuarios: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.Usuario>}
As you can see, CodigoPais is pointing to the new Pais ID.
About the editedUF.Pais navigation property, there's a small detail. Before attaching it into the context, it's null. But, hey, after adding, this is what happens:
{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
(...)
Codigo: 0
CodigoBACEN: 1058
CodigoGIA: 0
CodigoIBGE: null
Descricao: "Brasil"
UFs: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}
So, it has been filled. The cost for that should be one query, but I couldn't capture it on the monitor.
In other words, just expose the FK, change it using the View and use the navigation property to make the code a little more clear. That's it! :)
Thanks everyone,
Ricardo
PS: I'm using dotConnect for Oracle as a base for the EF 4.1. We don't use SQL Server (at least for now). The "monitor" I said before was devArt's dbMonitor, so I can see all queries sent to the Oracle database. And, again, sorry for any english mistakes!
If you include the foreign keys in your model. So add a PaisId property to the UF entity, you can directly set it and it will update the association.
using (var db = new Context())
{
db.UFs.Attach(editedUF);
editedUF.PaisId = theForeignKey;
db.Entry(editedUF).State = EntityState.Modified;
db.SaveChanges();
}
Also I've tested the approach you already mentioned in your question, and I don't get that extra select when attaching an entity. So this works as well:
using (var db = new Context())
{
ExistingPais pais = new Pais{Id =theId};
db.Pais.Attach(pais);
db.UF.Attach(editedUF);
editedUF.Pais = pais;
db.Entry(editedUF).State = EntityState.Modified;
db.SaveChanges();
}
Assuming your code looks like this:
public class Pais
{
public int Id { get; set; }
public virtual ICollection<UF> UFs { get; set; }
}
public class UF
{
public int Id { get; set; }
public virtual Pais Pais { get; set; }
public int PaisId { get; set; }
}

Categories

Resources