Ayende @ Rahien

Hi!
My name is Ayende Rahien
Founder of Hibernating Rhinos LTD and RavenDB.
You can reach me by phone or email:

ayende@ayende.com

+972 52-548-6969

, @ Q c

Posts: 5,949 | Comments: 44,548

filter by tags archive

Some thoughts about Indigo


So I was at the Israeli C# User Group meeting today, and the topic was Indigo, or in the odious name Windows Communications Foundation. Ido had a great presentation about it. I was surprised by how familiar the concepts were to me. There were quite a bit of stuff that that was very familiar to me from Castle (like TransactionScope, TransactionalAttribute, etc).

The architecture looks very extensible and well thought out, and I like the extensability points that they have there. The ability to intercept a method is invaluable in complex scenarios, since you can add behavior to it easily.

The Indigo way of doing it is just cool:

public class IndigoCacheAttribute : Attribute, IOperationInvoker 
{
...
}

The attribute is both the marker and the actor in this case. 

I wish I had something to do with it now, beyond writing the umpteenth calculator service, of course. One really nice thing that I liked about it was that you don't see XML if you don't want to (and I usually won't want to :-)). The programming model seems very easy to work with, with one exception.

You've better not expose ordinary method signatures, but expose methods that takes and return DTOs (or messages, if you like to think about it this way). The reason for that is that you can add stuff later without breaking your code. I know that usually SOA guys like to say Message This & Message That, but when I see something like:

[DataContract]
public class Book
{
 [DataMember]
 public string Title;
 [DataMember]
 public string ISBN;
}

I think DTO, not Message. The fact that it is XML under the cover has better remain under the cover, I much rather work with objects than with raw XML. Now I have another new thing to add to the stack of stuff waiting to be explored, it's just about my height now, and it's getter bigger.

Israeli C# User Group Meeting


I was at the C# User Group meeting today, and Ido gave a talk about Indigo (Windows Communication Foundation - Horrible name) which I found facinating. I'll post about it shortly, I hope.  There was also a huge raffle, but I didn't win anything.

It was the first time that I managed to dedicate time to go to the user group meeting, and if all the talks are at this level, it's well worth it.

Tiny IIS Tip


Yesterday I wrote a long post and lost it because my session has timed out and I had no warning. Today I got a question about this subject, how to move a client browser to a logout page when their client expires.

The solution for this is very easy, actually (but won't help losing work because of a session timeout, unfortantely), you can use this command in the HTML:

<META HTTP-EQUIV="Refresh" CONTENT="1170; URL=/logout.aspx">

This requires work, though, and can get a little tedious after a while. But what I found is that you can set IIS to send it automatically on all requests, so you set it up once, and forget about it. Very simple to setup (Web Site Properties -> Http Headers -> Add), and will work on everything without a single line of code changing.

If you want to prevent users from losing work, you need to use javascript to popup a warning, or make sure that the state is not lost if the client session timed out. (The warning in ati.com site is extremely annoying, for instance).

Sql Tips


The sp_helptext stored procedure can give you the text of any non-table object in the database. I find this incredibly useful, as it saves the need to locate the object in the management studio UI.

Here is an example:
sp_helptext Very_Nasty_Trigger

Results:

Text
------------
CREATE TRIGGER Very_Nasty_Trigger
   ON  NASTY INSTEAD OF INSERT
AS
BEGIN
    SET NOCOUNT ON;
    Declare @stmt nvarchar(max)
    SET @stmt = (select [text] from INSERTED);
    exec sp_executesql @stmt

END



Now I need to find something that works on the table level as well.

Triggers & Auditing: Take 2


Okay, my post about auditing & triggers generated some good questions, so here are a couple of answers.

Before anything else, I made a mistake in the schema and didn't change the defination of the [Posts_Audit_Trail]'s post_id, it shouldn't be an identity, here is the new schema:

CREATE TABLE [dbo].[Posts_Audit_Trail] (

      [audit_post_id] [int] IDENTITY (1, 1) NOT NULL,

    [post_id] [int] NOT NULL ,

    [post_title] [varchar] (50) NULL ,

    [post_blogid] [int] NULL ,

      [changed_by] nvarchar(255),

      [changed_on] datetime,

      [changed_action] nchar(10)

) ON [PRIMARY]

First, what about NHibernate, and other tools that relies on the changed rows count, here is a simple way to avoid breaking them by using the SET NOCOUNT ON/OFF option (I'm showing it for the update trigger only, but it should be clear how to use this):

CREATE TRIGGER Posts_AuditUpdate ON [Posts] FOR UPDATE

AS

      SET NOCOUNT ON

      INSERT INTO [Posts_Audit_Trail]

      (

            [post_id],[post_title],[post_blogid],

            [changed_by],[changed_on],[changed_action]

      )

      SELECT

            post_id,post_title,post_blogid,

            CURRENT_USER,GETDATE(), 'UPDATE-NEW'

      FROM Inserted

      UNION ALL

      SELECT

            post_id,post_title,post_blogid,

            CURRENT_USER,GETDATE(), 'UPDATE-OLD'

      FROM Deleted;

      SET NOCOUNT OFF

Second, about getting the application user instead of the database user (which is a problem in any 3-tiers application), this one require help from the application to get this information. Usually it will be written to the table anyway (last_changed_by, or something like that), and then copied to the auditing table.

Third, using this with complex scenarios. This is a fairly simple scenario, where all you need is to have some sort of tracking on what happened to your data. If you need to have complex things happening (log outside of SQL Server, send mail when sensitive rows change, etc), that should happen on the business layer. While you can make it happen in the DB, you really don't want it to happen there, think about poor you in 5 months time, staring at the screen trying to figure out why the CEO is getting flooded with emails. Like I mentioned, triggers are usually invisible, and that can be bad if you want to find out why stuff is happening. I wouldn't want to track an email sent from the database (and boy, did I see much worse done in the DB layer) by a trigger.

Just think about the time that it would take to think about looking at the triggers. This is something for the quick & simple solutions, not logic whatsoever, just log all the changes and forget about it until you need it.

For anything that require real logic, go with the code, if you've a good DAL, it should be easy enough to add that in (even with extensability) without affecting the rest of the application.

Changes


I look back at my posts for the last couple of weeks, and the are full of SQL stuff. One of the nicest things in having a blog is that you can go back and see what you did at various points in time. I knew that I blog about it, and indeed, I found out that I wrote my first trigger less than three months ago. That was a half day ordeal involving me and a couple of co-workers, checking out all sorts of crazy stuff (recursive triggers, for instance). But writing the last post took me 5 minutes for the SQL, and ten for the text between them :-)

Triggers and auditing


There is some discussion in the Castle Mailing List about auditing using ActiveRecord. I firmly believe that auditing is one of those things that should be done in the database layer. It's very simple to set up, and then you can forget about it until you need it. Here is an example of how simple it is.

Like most of my database samples, it is going to be a Blog->Posts sample. The issue is auditing in a multiply users per blog environment. Here is how the Posts table looks:

CREATE TABLE [dbo].[Posts] (

    [post_id] [int] IDENTITY (1, 1) NOT NULL ,

    [post_title] [varchar] (50) NULL ,

    [post_blogid] [int] NULL

) ON [PRIMARY]

Now, let's add support for auditing. First, the audting table:

CREATE TABLE [dbo].[Posts_Audit_Trail] (

    [post_id] [int] IDENTITY (1, 1) NOT NULL ,

    [post_title] [varchar] (50) NULL ,

    [post_blogid] [int] NULL ,

    [changed_by] nvarchar(255),

    [changed_on] datetime,

    [changed_action] nchar(10)

) ON [PRIMARY]

As you can see, it's an exact duplicate of the Posts table, but with a couple of extra columns to track what happened, when and by whom. Next step, writing a trigger for saving deleted posts:

CREATE TRIGGER Posts_AuditDelete ON [Posts] FOR DELETE

AS

      INSERT INTO [Posts_Audit_Trail]

      (

            [post_id],[post_title],[post_blogid],

            [changed_by],[changed_on],[changed_action]

      )

      SELECT

            post_id,post_title,post_blogid,

            CURRENT_USER,GETDATE(), 'DELETE'

      FROM Deleted;

As you can see, there is nothing much to it, we shove all the deleted records into the audit trail table, and we are done. Doing the same for inserts is very easy:

CREATE TRIGGER Posts_AuditInsert ON [Posts] FOR INSERT

AS

      INSERT INTO [Posts_Audit_Trail]

      (

            [post_id],[post_title],[post_blogid],

            [changed_by],[changed_on],[changed_action]

      )

      SELECT

            post_id,post_title,post_blogid,

            CURRENT_USER,GETDATE(), 'INSERT'

      FROM Inserted;

The only midly tricky part is saving both the old and new version of a post when we are doing an update, and even that is very simple:

CREATE TRIGGER Posts_AuditUpdate ON [Posts] FOR UPDATE

AS

      INSERT INTO [Posts_Audit_Trail]

      (

            [post_id],[post_title],[post_blogid],

            [changed_by],[changed_on],[changed_action]

      )

      SELECT

            post_id,post_title,post_blogid,

            CURRENT_USER,GETDATE(), 'UPDATE-NEW'

      FROM Inserted

      UNION ALL

      SELECT

            post_id,post_title,post_blogid,

            CURRENT_USER,GETDATE(), 'UPDATE-OLD'

      FROM Deleted;

As you can see, it's very simple to add auditing in the database layer. The next step would be writing some sort of UI for this (if this is needed for someone other than the sys admin), but you would need to do it anyway.

The complexity of doing it somewhere else is not trivial. If you've a good DAL, you can add it there, but why bother when the database can do it for you for free?

The only reasons I can think about is that your database doesn't support triggers, or you need to have some business rules involved with the auditing.

UPDATE: What about NHibernate? Since it check the modified rows count, wouldn't that break it (and other tools that rely on the rows count? In general, yes, but there is a very simple solution. In this case I didn't bother, but all you need to do is to use:

SET NOCOUNT ON  

-- Trigger actions

SET NOCOUNT OFF

Feeling Great


I went to work today expecting to be there until it would all work, and I expected it to start working (if at all) only very late into the night. I got off today before I usually leave, with a really great feeling. Everything that should've worked by tonight worked. It is a great feeling. I'm not sure how we did it, because last week I would've probably said that it can't be done by the time frame we had, but we did :-)

Now it's a show & tell and off to the next stage, spit & polish.

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. The RavenDB Comic Strip (3):
    28 May 2015 - Part III – High availability & sleeping soundly
  2. Special Offer (2):
    27 May 2015 - 29% discount for all our products
  3. RavenDB Sharding (3):
    22 May 2015 - Adding a new shard to an existing cluster, splitting the shard
  4. Challenge (45):
    28 Apr 2015 - What is the meaning of this change?
  5. Interview question (2):
    30 Mar 2015 - fix the index
View all series

RECENT COMMENTS

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats