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.
Related
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.
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.
For each of my tables there is the Key value so my question is how can I get the Key property?
I want to search that object I got in the paramater and update the table through this updated object. how do I find my object from the table?
public static void Update<TEntity>(TEntity UpdatedObject) where TEntity : class
{
DatabaseLinqDataContext ClientLinq = new DatabaseLinqDataContext(Patch);
ClientLinq.GetTable<TEntity>()// I want to search that object i got in the paramater and update it. how do I find my object from the table?
ClientLinq.SubmitChanges();
}
Any idea? what is the right way to solve this problem?
This action in written in the Business Logic Layer.
There's a few cases to consider.
If the entity instance was previously loaded by this datacontext, just call SubmitChanges. There's no need to do anything else (all modified instances will be updated at that point).
If the entity instance was previously loaded by a different datacontext, you need to make a new copy which will not be tracked, then follow the next case.
If the entity instance was not loaded by a datacontext (or the instance is a copy from above), then you need to call
//attach the changes to the tracked object graph
dataContext.GetTable<T>().Attach(t, true);
//if type uses optimistic concurrency, then you need to refresh
//load the "original" state for the tracked object graph
dataContext.Refresh(t, RefreshMode.KeepCurrentValues);
dataContext.SubmitChanges();
Or, it would be better to call:
dataContext.GetTable<T>().Attach(tChanged, tOriginal);
dataContext.SubmitChanges();
I'm very new to this Entity Framework Object Services Overview (Entity Framework), so forgive me if I use the wrong terminology here.
I'm using the EDMX file to connect to an SQLite database. What I'm trying to do is use the ObjectSet<T> normally, to access a collection of objects from a table in the database. However, I want to additionally store some run-time-only data in the objects in that set. In my case, I have a set of devices stored in the database, but upon startup, I want to mark them as "Connected" or "Disconnected", and keep track of this state throughout execution.
Since the (row) types generated by the EDMX are partial I've added another partial definition, and added my public bool Connected property there. This seems to work, I can set it, and future queries provide objects with the same value that I previously set. The problem is, I don't know a) how it is working, or b) whether I can trust it. These doubts come from the fact that these aren't really collections of objects we're dealing with, right?
Hopefully that made sense, else I can provide more detail.
What you're doing is completely safe.
ObjectSet is still a collection of objects. With a lot magic added underneath.
I am not an expert on the internals but here is how I think it works:
The Entity Framework has a StateTracker hat keeps track of all the entities you're working with.
Every class in your EDMX model is required to have a key. EF is using that key internally so that it loads that specific object only once into memory.
var foo = db.Foos.Single(x => x.Id == 1); // foo with Id 1 is unique (in memory)
var foo2 = db.Foos.Single(x => x.Id == 1); // same instance of foo, but with updated values
var foo3 = db.Foos.Single(x => x.Id == 2) // a new unique instance (Id = 2)
bool sameObject = Object.Equals(foo, foo2); // will return true;
At every select the following happens:
Is an instance of class Foo already tracked/does it already exist?
Yes -> update the properties of the existing instance from the database.
No -> create new instance of class Foo (take values from database)
Of course it can only ever update mapped properties. So the ones you defined in the partial class won't be overwritten.
In case you're going to use code first. There is also the [NotMapped] attribute, that makes sure that the property won't be included in the table if you generate a new database from your code first models.
I hope I could clarify some things for you.
This works:
public ActionResult Save(int id, string name)
{
var profile = _profileRepository.GetById(id);
profile.Name = name;
_profileRepository.Save(profile); //this calls SaveOrUpdate()
//session.Commit() gets called in global.asax on Application_EndRequest()
//profile.Name is changed in the database
}
Since my actual problem is more complicated than this simple example, I'd like to get the profile from the repo, use TryUpdateModel on the profile to update any changes, and then save. However, when I introduce TryUpdateModel into the mix, it fails with a NonUniqueObjectException (a different object with the same identifier value was already associated with the session):
public ActionResult Save(int id)
{
var profile = _profileRepository.GetById(id);
TryUpdateModel(profile); //this works from the looks of it
_profileRepository.Save(profile); //this also appears to work;
//fails on session.Commit()
//nothing changed in database
}
A "different" object with the same identifier value?? It looks as though TryUpdateModel is disconnecting my profile object from the session. Any thoughts?
The first example would work because you are updating the same object retrieved through the NHibernate Session.
The second example has the potential to fail because TryUpdateModel() may replace the properties of your profile object (if matching ValueProvider data exists). If by chance you are posting back data for one of these properties including the Id, the TryUpdateModel() method might replace one of the objects originally retrieved through the NHibernate Session. When you attempt to save the profile, NHibernate is going to recognize that you didn't edit the original object, but are attempting to save a different object with the exact same Id. This would result in a NonUniqueObjectException.
To resolve this issue, you should ensure that you are updating the values of the original entities retrieved from Session. I would recommend that you avoid using the DefaultModelBinder directly against entities from your domain which you expect to have change tracking performed for.