Before I save an entity I want to check if its dirty.
So I have the entity object came back from the ajax. (with ID).
The object is not saved yet, and I want to get the entity entry with it:
// this will return null, I assume it is because the object it not persisted yet.
session.GetSessionImplementation().PersistenceContext.GetEntry(entity);
// this will return what I want, but merge will save the object...
session.GetSessionImplementation().PersistenceContext.GetEntry(session.Merge(entity));
Isnt this the entire idea behind dirty and un-dirty, is knowing them before the save action?
How can I get the entry of the object without having the persisted object yet?
Update
To give a real life use case, Lets say that I have the method NhiUtil.IsPropertyDirty that inside do the GetEntry from above.
if(NhiUtil.IsPropertyDirty("Password",userEntityFromClient, session)){
userEntityFromClient = Hush(userEntityFromClient.Password);
}
session.SaveOrUpdate(userEntityFromClient);
If I am forced inside IsPropertyDirty to merge/save userEntityFromClient in order to get GetEntry to work, I cant take business logic decisions before the actual save...
so the entire IsPropertyDirty is unusable...
Thanks
That's why we do have the NHiberante. If you go through your question and read about ORM, you must find that exactly that's why these tools are here. That's what they do for us.
Other words, do the standard steps:
1) Get the object from the Client (upper layers/tiers) via deserialization
optional get the object from session.Get(id) and bind it, skip the Merge later
optional do validation (business layer)
2) Merge the object, session.Merge() and call the SaveOrUpdate()
3) Flush the session
That's pretty it. 1) If we firstly get-by-id and bind the object from the recieved data (JSON) we already do have an object in the ISession. And the ISession is the one doing for us the dirty checking
In case 2) that we have detached object, use the session.Merge() which will again do all for us inside of the ISession.
Flush, only the dirty
The essence is in the 3) Flushing. It will be converted into the Update/Insert statement only if the object is dirty. If it is the same (unchanged) ... no call to DB engine
Some interesting sources:
Chapter 24. Best Practices
In a three tiered architecture, consider using SaveOrUpdate().
When using a distributed architecture, you could pass persistent objects loaded in the middle tier to and from the user interface tier. Use a new session to service each request. Use ISession.Update() or ISession.SaveOrUpdate() to update the persistent state of an object.
9.4.2. Updating detached objects
... SaveOrUpdate() ...
... using Merge(Object o). This method copies the state of the given object onto the persistent object with the same identifier. If there is no persistent instance currently associated with the session, it will be loaded. The method returns the persistent instance. If the given instance is unsaved or does not exist in the database, NHibernate will save it and return it as a newly persistent instance. Otherwise, the given instance does not become associated with the session. In most applications with detached objects, you need both methods, SaveOrUpdate() and Merge().
And also interesting: Ayende - The difference between Get, Load and querying by id
And the 12.1. Interceptors, which allows to hook on some events, e.g.:
public override bool OnFlushDirty(object entity,
object id,
object[] currentState,
object[] previousState,
string[] propertyNames,
IType[] types)
Related
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 have to show an object (a POCO class) in a form.
In my controller, i get the objects data from the objects repository.
But in the form, i have to show some extra data about the object as well, like the country name and not the countryid, the number of persons assigned (to fetch from a 1:N relation), the history of edits (to fetch from yet another table) and the bit 'CanBeCancelled'.
The question is: where should i put this logic?
I came up with these alternatives:
The repository itself: create an extra function which returns this
exact viewmodel
a conversionservice, which converts the class to the
viewmodel (it knows where to get the data)
the controller: it knows
what data to show in the view(model), so it should get all the data
from the different repositories
What is a good way to place this logic (with 'this logic' i mean the logic to know that the number of persons is fetched in repository A, the history is fetched by repository B and the countryname is fetched by the CountryRepository and the boolean 'CanBeCancelled' is fetched by the StateEngine service) ?
If there are no other constraints, I would follow simple rule stated by Single Responsibility Principle - each layer should do its own job and presume that other layers do their job properly. In this case repositories return the business object, services process the business object and the controller only knows how to display the object properly. In details:
Number of persons, history and country name are already in the storage, and should come from there. So repository should return a complete object - as long as the operations are about the same entity.
When several entities are involved in the process, service is responsible for calling corresponding repositories and constructing an object.
Things that are figured out according to the business rules are the job for service object as well.
Controller receives complete object by calling single method of a service and displays it
Benefits of this approach will be evident once you decide to change something, say business rule about how the object is allowed to be cancelled. This has nothing to do with access to the database, and does not involve application UI, so the only place you want to change in this case is service implementation. This approach allows you to do just that, without need to alter code of repositories and controllers.
Using EntityFramework, I am trying the following in combination with MVC 3.
I have a ObjectContext, which is being initialized at the start of every request, and disposed at the end of the request. Much like a simple UnitOfWork implementation. Now think of having a Order class (EF generated), which is going to be initialised. I pass the ObjectContext to be used trough a overloaded constructor.
var order = new Order(myObjectContext);
Then inside the Order class this instance is kept (private field). After I set some properties of this Order instance (using the same ObjectContext). I want to put this instance in the Users' session because a second webpage is needed to populate some information before the order is ready to be saved.
This is were I'm stuck, after the user submits the second webpage I retrieve the Order object from the session, set some more properties and hit .SaveChanges() on the ObjectContext references by the Order object. So the one that was made in the first request. I think this is required because during the first request (when the Order was created) some other entities are being added to one of the orders collection. So I have to use the same context again. The problem is that after hitting the .SaveChanges() the method returns no error or whatsoever, but the data is not saved in the database. Even the identity value does not change from zero. Nothing is being committed.
When I first try to add the object to the collection:
this._context.Order.AddObject(this);
this._context.SaveChanges();
Please note this code is within a method, placed in a partial Order class. This time, at the .AddObject() I get the message:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
I tried to detach the Order object from it's original context and reattach it to the context made in the second request but of course this is not working since one of the Order properties is a collection of other entities, retrieved and added in the first request, thus with the first ObjectContext.
Probably I could overcome this issue by calling .SaveChanges() at the end of the first request. Then in the next request I would have to pull the order from the db with that request's ObjectContext. The thing is, I do not want to save the Order before the end of the second request.
To me it looks like the ObjectContext which is kept with the Order instance in the session needs a 'connection-refresh'?
I am not really sure I understand what you are trying to do but it seems that you want to make several changes to the Order object and only save it when all changes have been applied. Correct me if I am wrong but I take it that you are passing the ObjectContext as a parameter to the Order object and then putting the Order object in the session to embed the connection and maintain it to the next page?
This is probably where your entity object and the ObjectContext gets disconnected.
I suggest you take a look at the Self-Tracking Entity Generator - http://msdn.microsoft.com/en-us/library/ff407090.aspx . Maybe this will help you - http://henrycomputerworld.blogspot.no/2012/01/save-temporary-data-with-entity.html
This should allow you to do as many postbacks as necessary and let the Order instance track its own changes. You should then be able to commit the changes on your single ObjectContext instance.
If this doesn´t help, maybe you can provide a little more code and explanation.
I use ISession.Query<T>().ToList() to acquire a list of objects, add them to a ComboBox so the user can browse all the objects, and then I edit one of these and call ISession.Update() with that object. However this throws a NonUniqueObjectReferenceException.
Why does this happen and what approach should I use to avoid/fix this?
Problem explanation
When you load an entity, Session keeps track of the database's primary key for the entity as well as the entity's object reference (location in memory) - for the duration of the Session.
The NonUniqueObjectReferenceException is thrown if you attempt to persist an entity that has the same primary key but a different object reference as an already loaded entity for this Session.
In other words the Session is telling you, "I have an entity in memory with the same primary key as the entity you are trying to persist, but the object reference of my copy doesn't match your copy."
Problem example
Open a Session (#1).
Load the entity (object reference = A, primary key in database = 1).
Close the Session.
Open a new Session (#2).
Load the same entity again (this time, object reference = B, primary key in database = 1).
Change a property on object A and persist it inside Session #2.
NonUniqueObjectReferenceException will be thrown.
It's worth noting that this exception will be thrown, even if object A is simply part of a larger object graph that is being persisted in session #2 (even if object A didn't change).
It's also worth noting that you can load an entity directly (Session.Load, Session.Get, Session.QueryOver, etc) or indirectly (with a query that doesn't return the object, but causes the object to be loaded into memory). The NonUniqueObjectReferenceException can be thrown for both a directly or in-directly loaded entity.
Important note: This problem can be caused in other ways, for example it can be caused with a single Session if you load and then clone an entity and then use this clone to persist some changes using the Session. Reason being, the clone's object reference would differ from the original entity.
Solution explanation
There is a method called Merge on the Session object:
object Session.Merge(object obj)
Merge will take an entity and, using the entity's primary key, retrieve the already loaded version of this entity from the current Session. It will also update the properties of the Session's already loaded entity if they differ from the entity you have just passed.
This method does not change the entity that you pass in, but instead returns a different object which you should use.
One last note about Merge is that if the Session you are in doesn't have a loaded copy of this entity in memory, Merge will go ahead and load the entity from the database before performing it's usual merging functionality.
Solution example
// using the example above, we are at the beginning of step 6 inside session #2
// we have 2 important objects = ISession sessionTwo, Option objectA.
// Option is an entity defined by you, it is not part of NH.
objectA.SomeProperty = "blah";
var optionFromSessionTwo = (Option) sessionTwo.Merge(objectA);
// this will not throw and it will persist the changes to objectA
sessionTwo.Flush();
Hope this helps!
Update method as stated by Denis is used for the persistance of detached objects.This exception is thrown probably because you are trying to use Update() on those objects you probably already have in the session. You should use a transaction or Flush() in order to update your objects, if you already do so; deleting session.Update() from your code should do it.
Here is the appropriate part in the NHibernate documentation.
You need to read the documentation on:
How to use NHibernate on Winforms
What "Update" means.
The quick fix is: Don't call session.Update(), just call session.Flush() so the changes are reflected to the DB.
session.Update() doesn't update the entity, session does that transparently. Update and Save methods aren't related to INSERT and UPDATE, instead, Save() makes transient object persistent, where Update() makes detached object persisted.
I have bunch of entities and I would like to know which one has been Save() with NHibernate (no flush yet). How can I do it ?
According to the Hibernate's Documentation, the Session has a method called Contains(obj).
boolean contains(Object object)
Check if this instance is associated with this Session.
Parameters:
object - an instance of a persistent class
Returns:
true if the given instance is associated with this Session
Well, I suppose this should work in your case since your newly created object does not get in touch with the ISession before you call Save (or Get). Give it a try and tell us what happens.
Another option would be creating an abstraction on top of the ISession and implement your own code to check if the entity has already been saved or not.
If you are wondering if something has been persisted by NHibernate you could probably just check the 'ID' field and see if it has changed from the default value