I'm trying to implement an audit trail that writes changes to another
table with user, timestamp, newvalue, oldvalue type info. I've got
the auditing working via a PreUpdateEvent listener and changes are
correctly persisted to the DB. Where I'm running into problems is
that after preforming a Save operation on an object into my
repository, I'd like the object to reflect it's current state
including the audit entries added by the listener, without having to
explicitly Get the object again from the repository. Hopefully, the
Test Method below will better explain what I'm trying to accomplish.
[TestMethod]
public void ProblemDemo()
{
Request request = CreateInitialTestRequest(); //Just creates a
fully populated request with RequestId = "R1"
repository.Add(request); //Add to repository and sets NH generated
Guid Primary Key
request.RequestTitle = "Request2"; //Change Request Title, request
automatically has new PK at this point.
repository.Save(request); //Title change, does trigger
PreUpdateEvent Listener, creates audit entry & persists
// to DB
//request = repository.GetRequestByRequestId("R1"); //This line
makes the test work, but requires explicitly
//
reloading the request from the repository after the
//
Save.
Assert.AreEqual(1, request.AuditEntries.Count(); //Fails 0 not 1,
I'm looking for a way of having the Save
//
automatically refresh the request object with the entries
//
added by PreUpdateEvent, like it does during the Add for
//
the NH generated PK.
}
Here's the mapping for the two objects, the domain object
implementations (Request and RequestAuditEntry) are straightforward
and should be easily inferred from the mappings, so I'll omit them.
public class RequestMap : ClassMap<Request>
{
public RequestMap()
{
WithTable("Requests");
SetAttribute("lazy", "false");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.RequestId).WithLengthOf(50);
Map(x => x.RequestTitle).WithLengthOf(100).Not.Nullable();
//<Omitting additional Maps for several properties unrelated to
the issue at hand >
HasMany(x => x.AuditEntries)
.Inverse()
.KeyColumnNames.Add("RequestGuid")
.Cascade.All()
.SetAttribute("lazy", "false");
//< One other similar WorklogEntries HasMany omitted for
clarity >
}
}
public class RequestAuditEntryMap : ClassMap<RequestAuditEntry>
{
public RequestAuditEntryMap()
{
WithTable("RequestAuditEntries");
SetAttribute("lazy", "false");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.ModificationAccount).WithLengthOf(50).Not.Nullable();
Map(x => x.ModificationTimestamp).Not.Nullable();
Map(x => x.PropertyName).WithLengthOf(100).Not.Nullable();
Map(x => x.NewValue).WithLengthOf(10000); //Forces nvarchar
(max)
Map(x => x.OldValue).WithLengthOf(10000); //Forces nvarchar
(max)
Map(x => x.IsDeleted).Not.Nullable();
References(x => x.Request)
.ColumnName("RequestGuid")
.SetAttribute("lazy", "false");
}
}
Here's how the request repository Save operation is implemented:
public void Save(Request request)
{
using (var session = sessionFactory.OpenSession())
{
using (session.BeginTransaction())
{
session.Merge(request); //Repository Add method is identical
except uses session.Save(request)
session.Transaction.Commit();
}
}
}
Just in case needed, here's the PreUpdateEvent listener
implementation. This is firing and adding audit entries which are
getting persisted to the DB tables and attached to Request object if
it is explicitly reloaded from the repository.
public bool OnPreUpdate(PreUpdateEvent e)
{
if (e.Entity is IAuditable) // Request objects are IAuditable,
which adds Guid Id &
//IList<RequestAuditEntry>
AuditEntries properties to the object.
{
ISession session = e.Persister.Factory.OpenSession();
var user = WindowsIdentity.GetCurrent().Name;
for (int i = 0; i < e.Persister.PropertyNames.Count(); i++)
{
if (e.State != null && e.OldState != null)
{
string previous = e.OldState[i] == null ? null : e.OldState
[i].ToString();
string current = e.State[i] == null ? null : e.State
[i].ToString();
if (current != previous)
{
RequestAuditEntry entry = new RequestAuditEntry(user,
e.Persister.PropertyNames[i],
previous == null ? "" : previous, current == null ?
"" : current, (Request)e.Entity);
session.Save(entry);
var entity = e.Entity as IAuditable;
entity.AuditEntries.Add(entry);
session.Flush();
}
}
}
return false;
}
I've tried peppering, Merges, Saves, SaveOrUpdates, Evicts, and
Refreshes at various different places (including even in a
PostUpdateEvent listener), but I can't seem to get anything to work.
Additionally, I've tried different Cascade options. At this stage
Lazy Loading is not in the mix, but hopefully can be added at some
point in the future.
To recap, I'd like the request object to reflect any added audit
entries after a Save, just like the it reflects the NH
generated PK after an Add, if possible.
Does anyone have any suggestions on how to achieve this? Thanks.
--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups
"nhusers" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to
[email protected]
For more options, visit this group at
http://groups.google.com/group/nhusers?hl=en
-~----------~----~----~----~------~----~------~--~---