Object gets disconnected from nHibernate session after TryUpdateModel is called - c#

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.

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.

Can I tell linq 2 SQL not to update certain columns after attachment or use of UpdateModel?

Let's assume I have the following situation, the update method in my service accepts a model (the one that is going to be updated) as an input parameter. The model can be unattached (in which case attach method is called before submitting changes) or attached (in which case we just submit changes). Edit actions just call this update method in my service. Now let's assume I cannot change the code in those actions (the code that produces the model to be updated). Can I still somehow prevent certain columns from updating from within the update method. Note that I might want to set those columns using linq to SQL, but only during insert method.
I'm quite sure I'm trying something unconventional here, but it might help me write some easy to reuse code. If it cannot be done, then I'll solve it differently, but it never hurts to try something new.
The Attach method does provide an override to accept both a modified and original entity.
Attach Modified Entity on Data Context
When using this the internal change tracker will figure out which columns have been updated and will only update those ones on the datasource which have changed, rather than updating all columns.
Alternatively if you want more explicit control over which properties are updated, you can reattach your entity as unmodified in its original state:
Attach Modified/Unmodified Entity on Data Context
This will hook up the internal change tracker to the PropertyChanging events on the entity so it can be tracked. You would then simply change the values of the properties on that entity in the Update method on your Service.
void Update(MyModel model)
{
using (MyContext ctx = new MyContext())
{
ctx.DeferredLoadingEnabled = false;
ctx.MyEntities.Attach(model.OriginalEntity);
model.OriginalEntity.Value = model.ModifiedEntity.Value;
ctx.SubmitChanges();
}
}
The pitfall of these approaches means you must maintain both the original and modified entities in your model, but could be set when your entities are loaded - a simple shallow copy of the object should do the trick by deriving from ICloneable in a partial class for each entity.

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.

Why model = newModel does not work but model.title = newModel.title works?

I need to edit my record in database. I don't think that I'm doing it good enough. I've tried make code shorter by dev = newDev; but it is not saving it then.
Code
[HttpPost]
[ValidateInput(false)]
public ActionResult Edit(Dev newDev)
{
try
{
if (TryUpdateModel(newDev) == true)
{
var dev = _db.Devs.Where(x => x.ID == newDev.ID).Single();
dev.Title = newDev.Title;
dev.Body = newDev.Body;
dev.Tags = newDev.Tags;
dev.Image1 = newDev.Image1;
dev.Image2 = newDev.Image2;
dev.Image3 = newDev.Image3;
dev.Image4 = newDev.Image4;
_db.SubmitChanges();
return RedirectToAction("");
}
else
{
return Content("Fail.");
}
}
catch
{
return View();
}
}
Can you help me optimize my code here?
You'll want to read up on the memory model used by .NET. Setting dev = newDev is going to change the value of the dev variable, but variables have a local scope. It won't change the value of other variables that were referencing the same object.
In this case, the context that you pulled dev from is probably keeping track of the dev that it pulled out and gave to you, so if you change its properties and call SaveChanges then it will know which values to change.
If you're just trying to copy all the values from one object to another without having to manually write a line of code for each property, you should be able to use a tool like AutoMapper to automatically map all the similarly-named properties from one object to another.
If you're using Entity Framework (it looks like it?) then you can't update entities this way.
The Entity Framework uses change tracking to understand what changes have happened to an entity. To do this, it keeps a list of the specific instances that are loaded from that context, and what their initial state was, so it can detect what changes have been made to those instances before you call SaveChanges on the context.
When you assign directly to a reference type, you're just reseating the reference. You aren't changing the initial object instance. Existing references to that instance (like the one the Entity Framework keeps internally to keep track of changes) don't change, and you will end up pointing at different object instances.
So, just use your code the way you have it now. This is the only way to blindly update every field. If nothing was updated, the Entity Framework's change tracking should cause SaveChanges to do nothing. If something was updated, it will perform the corresponding SQL to persist the changes to the DB.
If that doesn't work, then:
1. dev is a reference type
2. A reference of dev is being changed to point to another object (newDev)
Which means, that you're already working with an object out of context. And to be able to update that object, you first ned to somehow attach that object to the context and make the context be aware of that its the updated object of some existing entity there.
Hope this will help.

WCF Data Services/Entity Framework : Logging client's username, Authentication, Authorization

I am using Entity Framework with WCF Data Services and I have the following table in my database :
Table Contract
Id (int)
Name (varchar)
byUser (varchar)
isDeleted (bit)
Entity Framework class
Id (int)
Name(string)
byUser(string)
isDeleted(boolean)
whenever the user is inserting/updating/deleting a contract(through a client app), I need to log who did the action.
so, I created Stored procedures for insert/update/delete that will receive the username from the client when an insertion/deletion/update is performed.
the issue is that the delete operation does not send over who is performing the operation :
var ctx = Context;
var contractToDelete = ctx.Contracts.Where(c => c.ContractId == 1).First();
contractToDelete.ByUser = username;
ctx.DeleteObject(contractToDelete);
ctx.SaveChanges();
at the server side, the byUser is always null.
Questions :
1) How do I make it so that the byUser parameter is sent to the server ?
2) Is there a better way to handle this kind of scenario ? (logging/authentication/authorization) with Entity Framework
It doesn't send null "always". It sends the old value always. That is some internal logic in entity framework. For each tracked object EF keeps both original and current values. When you are deleting object EF doesn't use current values - it uses original values (don't ask me why, simply this is how it works).
So you need to cheat EF:
var ctx = Context;
var contractToDelete = ctx.Contracts.Where(c => c.ContractId == 1).First();
contractToDelete.ByUser = username;
ctx.Contracts.ApplyOriginalValues(contractToDelete);
ctx.DeleteObject(contractToDelete);
ctx.SaveChanges();
Calling ApplyOriginalValues will force EF to override original values with values passed in parameter = you will override original values with current values.
In my opinion the better way is storing deleted records in separate table because it will avoid a lot of problems with passing isDeleted=false to every query where both eager and lazy loading will load deleted records as well. The only way to avoid problems with isDeleted is using conditional mapping but in such case you will not be able to load deleted records even if you want to unless you use stored procedures or direct SQL queries.
The way I managed this is, when my user logs in, I store basic information about them in the session. I then have a class that sits on top of my actions to context.
Whenever I commit back changes, I go through the same routine which checks what changed. I developed the ability to trigger actions based upon the entity being worked with (so I can keep an eye on something such as contracts). Then I have the user able to be logged.
[Edit]
This is tougher to clarify than I realised, but I'll try.
I'm creating a web application. Heavily using Ninject.
When the user logs in, I store their information in an IUserSession object (this is really held in Session, but a custom Ninject scope makes this neat for me and prevents me from having to expose my data layer to Web Session). This user session object contains username, user id etc.
I created a class that contains the context,and wraps all the SELECT,CREATE,DELETE and COMMIT calls. i.e. SELECT;
public IQueryable<TEntity> All<TEntity>( ) {
return Context.Set<TEntity>();
}
This class also has a Commit method, this is the call to SaveChanges.
Before calling SaveChanges, you have access to the changes in the form of Context.ChangeTracker.Entities
For each entity that has changed, you can test to see if it was added, deleted or modified.To get the type of the element being modified;
Type baseEntityType = ObjectContext.GetObjectType( entity.Entity.GetType( ) );
I do plan on writing up a tutorial soon, based upon my personal experience with doing this (not that that helps you right now).

Categories

Resources