问题
I am working on an NHibernate project and have a question regarding updating transient entities.
Basically the workflow is as follows:
- Create a DTO (projection) and send over the wire to client. This has a small subset of properties from the entity.
- Client sends back the changed DTO
- Map the DTO properties back onto the appropriate enitity so an UPDATE statement can be generated and executed by NH.
- Save the entity
Point 4 is where I have the issue. Currently I can achieve this update using the session.Merge() method, however it must first load the entity from the db (assume no 2LC) before updating. So, both a select and an update statement are fired.
What I would like to do is create a transient instance of the entity, map the new values from the DTO, then have NH generate a SQL statement using only the properties I have changed. The additional select should be unnecessary as I already have the entity ID and the values required for the SET clause. Is this possible in NH?
Currently using session.Update(), all properties will be included in the update statement and an exception is raised due to the uninitialized properties that are not part of the DTO.
Essentially I need a way to specify which entity properties are dirty so only these are included in the update.
== EDIT ==
For example...
public class Person
{
public virtual int PersonId { get; set; }
public virtual string Firstname { get; set; }
public virtual string Nickname { get; set; }
public virtual string Surname { get; set; }
public virtual DateTime BirthDate { get; set; }
}
And the test case.
// Create the transient entity
Person p = new Person()
p.id = 1;
using (ISession session = factory.OpenSession())
{
session.Update(p);
// Update the entity – now attached to session
p.Firstname = “Bob”;
session.Flush();
}
I was hoping to generate a SQL statement similar to ‘UPDATE Persons SET Firstname = ‘Bob’ WHERE PersonID = 1’. Instead I get a DateTime out of range exception due to BirthDate not being initialised. It shouldn’t need BirthDate as it is not required for the SQL statement. Maybe this isn’t possible?
== /EDIT ==
Thanks in advance, John
回答1:
Dynamic-update is what you're looking for. In your mapping file (hbm.xml):
<class name="Foo" dynamic-update="true">
<!-- remainder of your class map -->
Be aware of the potential problems that this may cause. Let's say you have some domain logic that says either FirstName or Nickname must not be null. (Completely making this up.) Two people update Jon "Jonboy" Jonson at the same time. One removes his FirstName. Because dynamic-update is true, the update statement just nulls out Jon and the record is now "Jonboy" Jonson. The other simultaneous update removes his Nickname. The intent is Jon Jonboy. But only the null-out of the Nickname gets sent to the database. You now have a record with no FirstName or Nickname. If dynamic-update had been false, the second update would have set it to Jon Jonboy. Maybe this isn't an issue in your situation, but setting dynamic-update="true" has consequences and you should think through the implications.
UPDATE: Thanks for the code. That helped. The basic problem is NHibernate not having enough information. When you say session.Update(p), NHibernate has to associated a disconnected entity with the current session. It has a non-default PK. So NHibernate knows that it's an update and not an insert. When you say session.Update(p), NHibernate sees the whole entity as dirty and sends it to the database. (If you use session.Merge(obj), NHibernate selects the entity from the database and merges obj with it.) This is not what you really mean. You want to associate your object with the current session, but mark it as clean. The API is somewhat non-intuitive. You use session.Lock(obj, LockMode.None) as below.
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction()) {
var p = new Person {PersonId = 1};
session.Lock(p, LockMode.None); // <-- This is the secret sauce!
p.Firstname = "Bob";
// No need to call session.Update(p) since p is already associated with the session.
tx.Commit();
}
(N.B. dynamic-update="true" is specified in my mapping.)
This results in the following SQL:
UPDATE Person
SET Firstname = 'Bob' /* @p0_0 */
WHERE PersonId = 1 /* @p1_0 */
来源:https://stackoverflow.com/questions/4220978/nhibernate-flagging-specific-properties-as-dirty