3

Background Context:


i have a database table called Project and a table called ProjectHistory with the same schema. I am using nhibernate so i also have a domain object called Project and a domain object called ProjectHistory (which derived from Project).

Goal


My goal is to retrieve data across these tables and put them back in order as the events that happened and have C# code output a typical audit trail screen of last n number of changes

Details


Both tables have the following fields and data types
  • Id -(int )- primary key in Project, NOTE: no PK in the history table
  • LastUpdated (datetime)
  • Name (varchar)
  • Description (varchar)
  • Timestamp (timestamp)

the goal of the Projecthistory table is that whenever i do an update to the Project table, the ProjectHistory table get inserted a new row with the old record so i can have a full audit trail.

I accomplish this through using this trigger:

 ALTER TRIGGER ProjectTrigger
 ON Project
 AFTER UPDATE
 AS
    SET NOCOUNT ON

    insert into ProjectHistory
    select * from deleted

My issue is that i now need some C# code to put together an audit history where a user select the most recent n number of events.

Example:


Here is an example of events:
  1. On Jan 10 - create project 1 (nothing in history yet)
  2. On Jan 11 - create project 2
  3. On Jan 15 - edit project 2 ( will put entry in history table of project 2 with Jan 11 date as lastupdated by)
  4. On Jan 25 - edit project 1 (will put entry in history table of project 1 with Jan 10 date on it)

so, as you can see if i sorted descending by lastupdated field in the history table, it was put the result from event 4 before the result from event 3 which is out of order.

Code


I have tried the following code to try to put them together but I will explain the flaw below:

   public IEnumerable<Project> GetHistory<Project, ProjectHistory>(int numberOfChanges)
    {
        IEnumerable<Project> current;
        IEnumerable<Project> history;
            current = Session.Query<Project>()
                .OrderByDescending(r => r.LastUpdated)
                .Take(numberOfChanges);

             history = Session.Query<ProjectHistory>()
            .OrderByDescending(r => r.LastUpdated).Cast<Project>();

            IEnumerable<Project> all = current.Concat(history);
            return all.OrderByDescending(r => r.Id).ThenByDescending(r => r.LastUpdated);
        }
    } 

My main issues with the above code are:

  1. The LastUpdated time on the history records are going to reflect the LastUpdated time of the previous Project update so i can't sort desc on that field in the history table and assume i will get a correct descending order of events.

    1. I could use the timestamp field for sorting (as that was the actual time that the record was entered) but it doesn't seem like you can sort on that field using C# nhibernate because that field is just a binary(8) so it translates to a byte[] in C# which doesn't support IComparable.
  2. I can use lastUpdated field on the Project table because sorting by lastupdate field will get me the latest recent events in order. I thought of looping through just the project table and then doing a separate query per each project to grab the latest history project but them this doesn't support a case where multiple changes to the same project id occurred. The issue is i can't figure out a way to get the data in the correct order.

My main point is that I want to accomplish what I think is a pretty generic thing that must be solved already so i must be just missing the basic pattern here.

I already have code that can compare one Project object with another project object and return the field diffs (which works perfect) but I just need a suggestion on figuring out how i get this data out of the database across these tables in the right order (either from my query or code after the fact) so I can show "the last n number of changes" and then use my logic to show the field diff for each of those changes.

svick
  • 236,525
  • 50
  • 385
  • 514
leora
  • 188,729
  • 360
  • 878
  • 1,366
  • How does that follow? A project history record's LastUpdated is the previous update's date/time. Previous implies "earlier in time", so sorting on that field should order the records chronologically. The second technique should work, too. And you shouldn't have to sort the two queries if you are then going to sort their concatenation. Can you give an example of the incorrect results you are receiving with the code you posted? – phoog Apr 08 '11 at 13:05
  • @phoog - the issue is that i CAN get the history of one project chronologically but putting that together across projects is the issue because lastupdated sorting in the history table is not accurate – leora Apr 08 '11 at 13:47
  • I don't understand why the lastupdated sorting is inaccurate. If I update the project on 1 May, 10 May, and 20 May, I should have a project record dated 20 May and two projecthistory records dated 10 May and 1 May. No? – phoog Apr 08 '11 at 13:53
  • @phoog - it is correct to sort on this field across a SINGLE project in terms of sorting order but to get the correct ordering of changes across multiple projects this field in not accurate because the LASTUPDATED field (because of the trigger is the last update of the previous change so you would get a wrong order of when the change happened. I think i may need to store a new field in the history table that is lastUpdated that maps onto when that record was entered . . – leora Apr 08 '11 at 13:56
  • 1
    But your code sorts first by project ID and then by date, which should account for that. To extend my example: I have another project that I updated on 5 May, 15 May, and 25 May. Then using LU for lastupdated, my project records should be (ID=1, LU=20 May) and (ID=2, LU=25 May) and my history recods (ID=1, LU=1 May), (ID=1, LU=10 May), (ID=2, LU=5 May), (ID=2, LU=15 May). – phoog Apr 08 '11 at 14:01
  • @phoog - the issue is if someone is looking for the last 10 events, by first sorting by project Id (to fix the first issue), i can no longer figure what the last 10 events were across all projects . . – leora Apr 08 '11 at 15:41
  • @phoog - i have updated the question with an example section to walk through 4 events to explain why they would be out of order on the history table. – leora Apr 08 '11 at 15:50
  • @ooo -- if you want the last 10 events across all projects, then sort only on the date, like `return all.OrderByDescending(r => r.LastUpdated);`. Regarding your example, it seems to me that the result of event four is stored in the project table with the date Jan25. The entry in the history table, dated Jan10, is the result of event one. Its presence in the history table is the result of event four, of course, but the audit trail is the union of the tables; it still seems to me that the results are in the correct order. – phoog Apr 08 '11 at 16:15
  • @phoog - the main issue is that the LastUpdated time in the history table doesn't actually represent the time it was added to the history table but rather the lastupdated timestamp from the Project table so in the example, an item can have a later date in lastUpdate in the history table but actually have happened (been inserted into history table) before. . . that is the crux of the issue – leora Apr 08 '11 at 17:26
  • if you need the time of a record's insertion in the history table, you can look at the next record, chronologically (or previous, in your descending sort), keeping in mind that for the most recent history row, you will need to look in the project table. – phoog Apr 09 '11 at 03:59

3 Answers3

0

If I've understood your question correctly you can simply sort on the timestamp converted to long.

// The last row in your question, 0 means start from the start of byte[]
return all.OrderByDescending(r => BitConverter.ToInt64(r.Timestamp,0));
flindeberg
  • 4,887
  • 1
  • 24
  • 37
0

Option 1: Here's a link that allows you to use the timestamp in an order by clause. I haven't used this myself, so use at your own risk.

Option 2: Add an auto increment field to ProjectHistory and then you can sort your results by the id of the project and then the auto-increment id. I haven't use the cast operation much, so I'm not sure of those implications, but I'm sure you'll have more work with mapping.

Option 3: Have you investigated NHibernate Envers, which is a framework for supporting and audit trail? This will be a new learning curve, but it may save you a lot of work in the long run.

Wibber
  • 41
  • 2
0

Here is my solution for one similar project. Create a view to union project and project history table. Let SQL server to do the heavy lifting.

Here is an example:

CREATE view [dbo].[VwProjectHistory] as select
    OtherFieldName,
    LastUpdated
from
    [dbo].[Project]

union all

select
    OtherFieldName,
    LastUpdated
from
    [dbo].[ProjectHistory]
tonyjy
  • 1,359
  • 3
  • 12
  • 24
  • can you clarify, i dont see how this solves the issue described above. – leora Apr 08 '11 at 13:47
  • @ooo - Unless I missed some of your requirements, can you select the records from vwProjectHistory order by projectid then by lastupdated? – tonyjy Apr 08 '11 at 16:09