These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 13 posts ] 
Author Message
 Post subject: How to log object's change?
PostPosted: Tue Sep 18, 2007 3:45 am 
Newbie

Joined: Fri Jun 02, 2006 6:22 am
Posts: 8
In my application ,I want to log all change for some entity classes.For example ,one class A:

class A
{
private string name;
public string Name
{
get{return name;}
set{name=value;}
}
}

After loading an instance from db by nhibernate, when someone changes the its Name value, I want to log this change into database, including information:
the user that executed changing,the original value,current value.

how ?


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 18, 2007 9:55 am 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
This is beyond NHibernate's responsibility to provide support for.

We solved this by having all entity properties track their own "dirty" status, and each entity keeps a dictionary of pre-dirty property values (properties add to the dictionary when their values change, if an entry for that property is not in the dictionary yet). Then in our implementation of NHibernate.IInterceptor.PostFlush, we can see which entities were marked dirty and what the old/new property values are. After doing whatever you want PostFlush to do with that information, it then clears the dirty flag and original property values dictionary (by reflection since they shouldn't be publicly settable).


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 18, 2007 10:07 am 
Regular
Regular

Joined: Fri Jan 20, 2006 7:45 pm
Posts: 97
Location: San Antonio, TX
You have to handle that yourself. I've wrapped NHibernate calls in a generic DAO and have injected a class that has the responsibility of providing current use info and time stamps. Since the auditable features of each auditable class are only importance to this concern in partifuclar, the auditable properties are only accessible through the IAuditable interface. The class injected into the DAO get's current user information and populated the necessary fields. NHibernate can see the properties through the IAuditable interface and persists auditing info along with the rest of the class data.

Please excuse the VB.Net code (I have to use it on my current project), but here's the sort of thing that I'm doing:
Code:
Imports Saws.Erss.Foundation.Data
Imports Saws.Erss.Core.Domain

Namespace Domain

    'TODO: Add DAO interceptor and tests for auditable entities
    ''' <summary>
    ''' This is a <see cref="DomainEntity(Of Type)"/> that has properties
    ''' supporting common user activity auditing.
    ''' </summary>
    ''' <typeparam name="IdType">Type of the entity's ID.</typeparam>
    ''' <remarks>
    ''' Notice that the property declarations are scoped 'Private'.  This
    ''' restricts use of the methods to the IAuditable interface.
    ''' </remarks>
    Public MustInherit Class AuditableEntity(Of IdType)
        Inherits DomainEntity(Of IdType)
        Implements IAuditable

#Region "IAuditable implementation"

        ''' <summary>
        ''' Date object was last modified
        ''' </summary>
        Private Property DateModified() As Nullable(Of Date) Implements IAuditable.DateModified
            Get
                Return _dateModified
            End Get
            Set(ByVal value As Nullable(Of Date))
                _dateModified = value
            End Set
        End Property

        ''' <summary>
        ''' User that last modified the object.
        ''' </summary>
        Private Property ModifiedBy() As String Implements IAuditable.ModifiedBy
            Get
                Return _modifiedBy
            End Get
            Set(ByVal value As String)
                _modifiedBy = value
            End Set
        End Property

        ''' <summary>
        ''' Date object was added to system.
        ''' </summary>
        Private Property DateAdded() As Nullable(Of Date) Implements IAuditable.DateAdded
            Get
                Return _dateAdded
            End Get
            Set(ByVal value As Nullable(Of Date))
                SetFieldOnce(_dateAdded, value)
            End Set
        End Property

        ''' <summary>
        ''' User that added the object to the system.
        ''' </summary>
        Private Property AddedBy() As String Implements IAuditable.AddedBy
            Get
                Return _addedBy
            End Get
            Set(ByVal value As String)
                SetFieldOnce(_addedBy, value)
            End Set
        End Property

        Private _dateModified As Nullable(Of Date)
        Private _modifiedBy As String
        Private _dateAdded As Nullable(Of Date)
        Private _addedBy As String

#End Region

    End Class

    ''' <summary>
    ''' Interface enables classes to handle user audit information.
    ''' </summary>
    ''' <remarks>Hansen WO system already has audit columns in (most) tables.</remarks>
    Public Interface IAuditable
        ''' <summary>
        ''' User who added system entity.
        ''' </summary>
        ''' <remarks>
        ''' Use domain username.
        '''
        ''' Should not allow overwrite of values (write once).
        ''' </remarks>
        Property AddedBy() As String

        ''' <summary>
        ''' Date system entity was added.
        ''' </summary>
        ''' <remarks>
        ''' This is a nullable date to allow nulls in database.
        '''
        ''' Should not allow overwrite of values (write once).
        ''' </remarks>
        Property DateAdded() As Nullable(Of Date)

        ''' <summary>
        ''' User who last modified system entity.
        ''' </summary>
        ''' <remarks>Use domain username.</remarks>
        Property ModifiedBy() As String

        ''' <summary>
        ''' Date entity was last modified.
        ''' </summary>
        ''' <remarks>
        ''' This is a nullable date to allow nulls in database.
        ''' </remarks>
        Property DateModified() As Nullable(Of Date)
    End Interface

End Namespace


My tables each support the auditing information (not my design, but who really cares) needed. In each of my mapping files I cut and paste the same block supporting the Auditable interface:
Code:
    <!-- AuditableEntity fields -->
    <property name="AddedBy" column="ADDBY" />
    <property name="DateAdded" column="ADDDTTM" />
    <property name="ModifiedBy" column="MODBY" />
    <property name="DateModified" column="MODDTTM" />
    <!-- End AuditableEntity fields-->

_________________
Dum spiro, spero
-------------------------------
Rate my post if it helps...


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 18, 2007 9:57 pm 
Newbie

Joined: Fri Jun 02, 2006 6:22 am
Posts: 8
thanks a lot!
but I have some questions:
1.how to find the pre-dirty values? tracing in set method? Nhibernate will set value at first time,how do I find out the value set by user not by nhibernate?

2. Where do you keep pre-dirty values and new values? in dictionary or copys of every proterty? we can get current value by reflection ,but it is low performance.
3.does Keeping a dictionary instance in entity affect performace when load a batch entitys such as GetAll() method?

4.how and where do you implementation my PostFlush? is it a event?
5.Would you like to give me a example of your entity?
thanks again!

Nels_P_Olsen wrote:
This is beyond NHibernate's responsibility to provide support for.

We solved this by having all entity properties track their own "dirty" status, and each entity keeps a dictionary of pre-dirty property values (properties add to the dictionary when their values change, if an entry for that property is not in the dictionary yet). Then in our implementation of NHibernate.IInterceptor.PostFlush, we can see which entities were marked dirty and what the old/new property values are. After doing whatever you want PostFlush to do with that information, it then clears the dirty flag and original property values dictionary (by reflection since they shouldn't be publicly settable).


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 18, 2007 10:14 pm 
Newbie

Joined: Fri Jun 02, 2006 6:22 am
Posts: 8
I think the use informatation can be found in thread not in instance of entity ,such as token in web application.my questions are how to get the original values and current values, and where to inject the trace saving mothed ,in DAO layer or BLL(business logic layer)? wherever I inject ,I must apply it in every entry.

jlockwood wrote:
You have to handle that yourself. I've wrapped NHibernate calls in a generic DAO and have injected a class that has the responsibility of providing current use info and time stamps. Since the auditable features of each auditable class are only importance to this concern in partifuclar, the auditable properties are only accessible through the IAuditable interface. The class injected into the DAO get's current user information and populated the necessary fields. NHibernate can see the properties through the IAuditable interface and persists auditing info along with the rest of the class data.

Please excuse the VB.Net code (I have to use it on my current project), but here's the sort of thing that I'm doing:
Code:
Imports Saws.Erss.Foundation.Data
Imports Saws.Erss.Core.Domain

Namespace Domain

    'TODO: Add DAO interceptor and tests for auditable entities
    ''' <summary>
    ''' This is a <see cref="DomainEntity(Of Type)"/> that has properties
    ''' supporting common user activity auditing.
    ''' </summary>
    ''' <typeparam name="IdType">Type of the entity's ID.</typeparam>
    ''' <remarks>
    ''' Notice that the property declarations are scoped 'Private'.  This
    ''' restricts use of the methods to the IAuditable interface.
    ''' </remarks>
    Public MustInherit Class AuditableEntity(Of IdType)
        Inherits DomainEntity(Of IdType)
        Implements IAuditable

#Region "IAuditable implementation"

        ''' <summary>
        ''' Date object was last modified
        ''' </summary>
        Private Property DateModified() As Nullable(Of Date) Implements IAuditable.DateModified
            Get
                Return _dateModified
            End Get
            Set(ByVal value As Nullable(Of Date))
                _dateModified = value
            End Set
        End Property

        ''' <summary>
        ''' User that last modified the object.
        ''' </summary>
        Private Property ModifiedBy() As String Implements IAuditable.ModifiedBy
            Get
                Return _modifiedBy
            End Get
            Set(ByVal value As String)
                _modifiedBy = value
            End Set
        End Property

        ''' <summary>
        ''' Date object was added to system.
        ''' </summary>
        Private Property DateAdded() As Nullable(Of Date) Implements IAuditable.DateAdded
            Get
                Return _dateAdded
            End Get
            Set(ByVal value As Nullable(Of Date))
                SetFieldOnce(_dateAdded, value)
            End Set
        End Property

        ''' <summary>
        ''' User that added the object to the system.
        ''' </summary>
        Private Property AddedBy() As String Implements IAuditable.AddedBy
            Get
                Return _addedBy
            End Get
            Set(ByVal value As String)
                SetFieldOnce(_addedBy, value)
            End Set
        End Property

        Private _dateModified As Nullable(Of Date)
        Private _modifiedBy As String
        Private _dateAdded As Nullable(Of Date)
        Private _addedBy As String

#End Region

    End Class

    ''' <summary>
    ''' Interface enables classes to handle user audit information.
    ''' </summary>
    ''' <remarks>Hansen WO system already has audit columns in (most) tables.</remarks>
    Public Interface IAuditable
        ''' <summary>
        ''' User who added system entity.
        ''' </summary>
        ''' <remarks>
        ''' Use domain username.
        '''
        ''' Should not allow overwrite of values (write once).
        ''' </remarks>
        Property AddedBy() As String

        ''' <summary>
        ''' Date system entity was added.
        ''' </summary>
        ''' <remarks>
        ''' This is a nullable date to allow nulls in database.
        '''
        ''' Should not allow overwrite of values (write once).
        ''' </remarks>
        Property DateAdded() As Nullable(Of Date)

        ''' <summary>
        ''' User who last modified system entity.
        ''' </summary>
        ''' <remarks>Use domain username.</remarks>
        Property ModifiedBy() As String

        ''' <summary>
        ''' Date entity was last modified.
        ''' </summary>
        ''' <remarks>
        ''' This is a nullable date to allow nulls in database.
        ''' </remarks>
        Property DateModified() As Nullable(Of Date)
    End Interface

End Namespace


My tables each support the auditing information (not my design, but who really cares) needed. In each of my mapping files I cut and paste the same block supporting the Auditable interface:
Code:
    <!-- AuditableEntity fields -->
    <property name="AddedBy" column="ADDBY" />
    <property name="DateAdded" column="ADDDTTM" />
    <property name="ModifiedBy" column="MODBY" />
    <property name="DateModified" column="MODDTTM" />
    <!-- End AuditableEntity fields-->


Top
 Profile  
 
 Post subject:
PostPosted: Tue Sep 18, 2007 10:28 pm 
Newbie

Joined: Fri Jun 02, 2006 6:22 am
Posts: 8
I found a useful article in http://www.codeproject.com/csharp/NHibe ... ceptor.asp.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 19, 2007 12:25 am 
Regular
Regular

Joined: Tue Feb 21, 2006 9:50 am
Posts: 107
Another way to solve this problem is to use triggers in the database. We have defined Insert-, Update- and Delete-Trigger for each table we want to log. Inside the trigger you have access to the old and new values. To handle changes of database tables we use MyGeneration to generate the Trigger code.

Regards
Klaus


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 19, 2007 5:06 pm 
Regular
Regular

Joined: Fri Jan 20, 2006 7:45 pm
Posts: 97
Location: San Antonio, TX
wuxsh wrote:


That's sort of what I'm doing.

Here's an example entity class:
Code:
Namespace Domain.Resources
    <DataAccessRestricted(DataAccessRestrictionFlags.Delete)> _
    Public Class Manufacturer
        Inherits AuditableEntity(Of Integer)

        Public Property Code() As String
            Get
                Return _code
            End Get
            Set(ByVal value As String)
                _code = value
            End Set
        End Property

        Public Property Comments() As String
            Get
                Return _comments
            End Get
            Set(ByVal value As String)
                _comments = value
            End Set
        End Property

        Public Overloads Overrides Function GetHashCode() As Integer
            Return ([GetType]().FullName & _code).GetHashCode()
        End Function

        Private _code As String
        Private _comments As String
    End Class
End Namespace


As you see, AuditableEntity is inherited and provided the ID (primary key) type. When a programmer is working with a Manufacturer object, they can't see the auditable entity members without using the IAuditable interface. Since I've got NHibernate wrapped with a generic dao I don't need to write NHibernate interceptors (although I have in the past).

Here's the interface for my DAO
Code:
Public Interface IDao(Of EntityType, IdType)
    Function GetById(ByVal id As IdType) As EntityType
    Function GetById(ByVal id As IdType, ByVal shouldLock As Boolean) As EntityType
    Function Load(ByVal id As IdType) As EntityType
    Function Load(ByVal id As IdType, ByVal shouldLock As Boolean) As EntityType
    Function GetAll() As List(Of EntityType)
    Function GetAll(ByVal propertyName As String, ByVal orderBy As OrderBy) As List(Of EntityType)
    Function GetByExample(ByVal exampleInstance As EntityType, ByVal ParamArray propertiesToExclude As String()) As List(Of EntityType)
    Function GetUniqueByExample(ByVal exampleInstance As EntityType, ByVal ParamArray propertiesToExclude As String()) As EntityType
    Function Save(ByVal entity As EntityType) As EntityType
    Function SaveOrUpdate(ByVal entity As EntityType) As EntityType
    Sub Delete(ByVal entity As EntityType)
    Sub CommitChanges()
End Interface

The purpose of the DAO interface is that if the client pulls the plug on NHibernate (they've got a thing against open source it seems) I can put in my own little persistence layer implementation without mucking with too much of the code. I'll just change the back end, run my unit tests, and pray.
The AbstractNHibernateDao implements IDao and you're right, the user information is gleaned from the application context. The DAO factory currently takes the current userName from user context and feeds it to DAOs that it generates through an internal constructor. Like this:
Code:
    Public Function GetManufacturerDao() As IManufacturerDao Implements IDaoFactory.GetManufacturerDao
        Return New ManufacturerDao(GetAuditInfo())
    End Function

So the ManufacturerDao will have the current userName when its created. When the Save() or SaveAndUpdate() methods are called, the dao will add the user name to ModifiedBy and add the DateModified using the current time. If the object is transient, AddedBy and DateAdded get populated too.

I didn't bother with other operations because I only care about save right now. Also, I'm currently being lazy with dirty data and using session.IsDirty() to determine if I muck with IAuditable classes.

The solution is pretty simple...I probably could have come up with something a little more elegant, but the client wants everything in VB.Net and this is my first job ever using it and I'm still a bit uncomfortable with the language (confused regarding support of delegates for one).

Regarding triggers, I don't have ability to update the DB schema but really don't favor using triggers anyway, they tend to be abused and can cause maintenance issues.
From a programming standpoint, day-to-day development isn't even aware of IAuditable or related features.

_________________
Dum spiro, spero
-------------------------------
Rate my post if it helps...


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 19, 2007 6:06 pm 
Regular
Regular

Joined: Fri Jan 20, 2006 7:45 pm
Posts: 97
Location: San Antonio, TX
Side note, what I'm doing is just for auditing user activity (which is what my AuditableEntity is for). The client has a really simple model. It's also similar to what was being done in the article you referenced.

Now if you want to audit all fields on a class you that's a little more complicated. One problem that I see is that you are basically associating one class to two separate tables with one table holding the extra audit data. I'm not sure how one would support one class with alternate mappings that you could use within the same session.

Having two classes with names like Manufacturer and AuditableManufacturer seems a little silly, espicially if you used the latter in your business code. Of course a factory class for your entities could serve up objects as Manufacturer when the instances are really AuditableManufacturer. The basic structure is as follows:

If you wanted your mapping for a given class and its auditing to be in the same mapping file, you could do what I have in mind (but it may seem a little ugly). Basically you'd use an abstract base for each of your auditable members. For class Manufacturer I would have AbstractManufacturer (I have making my methods overridable and use interfaces anyway instead), and Manufacturer would inherit from that and implement something like IAuditable (with auditing-specific info). Then you'd map AbstractManufacturer to table Manufacturer and use a subclass_join section to map Manufacturer to table ManufacturererAudit. Both tables would support all the properties on the abstract base.
Note: Of course, you could use naming like in the previous paragraph.

Multiple inheritance would be nice in this case IMO. If anyone has a better approach with NHibernate please throw in your 2 cents. This is all I got right now.

_________________
Dum spiro, spero
-------------------------------
Rate my post if it helps...


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 19, 2007 10:47 pm 
Newbie

Joined: Fri Jun 02, 2006 6:22 am
Posts: 8
Using Gerneric DAO to inject audit action can not meet me:
we can find out whether the data is dirty or not,but you can not find out which one or some fields are dirty,thought that's nothing to you because you just audit who and when change data and do not care which field changed,
I must log not only who and when change data but also the values of field before changing and after change .In gerneric Dao,we can not get the original value before update.but in Nhibernate interceptor, it providers the data before upate,so I can decide which one is changed and log the its value.

I had finished a simple implement . In my Implement,I use one entity class(mapped a table in db) to save all audit information. when I need audit some entity ,I use custom attribute to decorate class, for example:

[AuditAttribute("person information","IDCode"]
public class Person
{
public IDCode
{...}

[AuditAttribute("English name")]
public string EngName
{...}
}

and in my nhibernate interceptor method espcial OnFlushDirty,I check the entity's auditAttribute, skip entity with no AuditAttribute decorated,and create a audit information class instance and save it into db when auditattribute decratoring property's value changed.


jlockwood wrote:
Side note, what I'm doing is just for auditing user activity (which is what my AuditableEntity is for). The client has a really simple model. It's also similar to what was being done in the article you referenced.

Now if you want to audit all fields on a class you that's a little more complicated. One problem that I see is that you are basically associating one class to two separate tables with one table holding the extra audit data. I'm not sure how one would support one class with alternate mappings that you could use within the same session.

Having two classes with names like Manufacturer and AuditableManufacturer seems a little silly, espicially if you used the latter in your business code. Of course a factory class for your entities could serve up objects as Manufacturer when the instances are really AuditableManufacturer. The basic structure is as follows:

If you wanted your mapping for a given class and its auditing to be in the same mapping file, you could do what I have in mind (but it may seem a little ugly). Basically you'd use an abstract base for each of your auditable members. For class Manufacturer I would have AbstractManufacturer (I have making my methods overridable and use interfaces anyway instead), and Manufacturer would inherit from that and implement something like IAuditable (with auditing-specific info). Then you'd map AbstractManufacturer to table Manufacturer and use a subclass_join section to map Manufacturer to table ManufacturererAudit. Both tables would support all the properties on the abstract base.
Note: Of course, you could use naming like in the previous paragraph.

Multiple inheritance would be nice in this case IMO. If anyone has a better approach with NHibernate please throw in your 2 cents. This is all I got right now.


Top
 Profile  
 
 Post subject:
PostPosted: Wed Sep 19, 2007 11:50 pm 
Regular
Regular

Joined: Fri Jan 20, 2006 7:45 pm
Posts: 97
Location: San Antonio, TX
That'll work. The intercepters are kind of fun to work with too. Sorry I was confused at first regarding what you needed exactly.

_________________
Dum spiro, spero
-------------------------------
Rate my post if it helps...


Top
 Profile  
 
 Post subject:
PostPosted: Thu Sep 20, 2007 9:43 am 
Expert
Expert

Joined: Fri Oct 28, 2005 5:38 pm
Posts: 390
Location: Cedarburg, WI
wuxsh, do you still need help determining which properties changed and what their original values were? If so I can post the code we use to do that.


Top
 Profile  
 
 Post subject: Re:
PostPosted: Sun May 09, 2010 5:30 am 
Newbie

Joined: Tue May 04, 2010 8:15 am
Posts: 5
Nels_P_Olsen wrote:
wuxsh, do you still need help determining which properties changed and what their original values were? If so I can post the code we use to do that.


Hello Nels_P_Olsen,

Can you post your code.

Best regards,
Zgort


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 13 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.