1

In a C# application, using nHibernate & fluent nHibernate, we're looking to capture certain INSERT, UPDATE, DELETE queries performed on a Sqlite database in order to replicate them. We'd like to replicate only certain queries that affect certain entity types.

After several searches and tests, we haven't found a way to do this.
We have developed a solution using an Interceptor as well as Listeners, but neither solution seems to work. An Interceptor can be used to retrieve the generated SQL, but we lack context for its application. Listeners (IPostInsertEventListener, IPostUpdateEventListener, IPostDeleteEventListener) have many more parameters, but we can't find a way to retrieve the raw SQL query.

Using Interceptor by overriding OnPrepareStatement(SqlString sql) is the simplest method, but it lacks the context of the entity on which the operation has been performed.

public override SqlString OnPrepareStatement(SqlString sql)
{
    // Only raw SQL
    Console.WriteLine(sql);
    return base.OnPrepareStatement(sql);
}

And by using Listeners, we get a lot more information, we can understand the context and the entity that is being modified, but we can't find the SQL generated.

    public class QueryListener(Configuration cfg) : IPreUpdateEventListener, IPostUpdateEventListener, IPreInsertEventListener, IPostInsertEventListener
    {
        public void Register()
        {
            cfg.EventListeners.PreUpdateEventListeners = [.. cfg.EventListeners.PreUpdateEventListeners, this];
            cfg.EventListeners.PostUpdateEventListeners = [.. cfg.EventListeners.PostUpdateEventListeners, this];
            cfg.EventListeners.PreInsertEventListeners = [.. cfg.EventListeners.PreInsertEventListeners, this];
            cfg.EventListeners.PostInsertEventListeners = [.. cfg.EventListeners.PostInsertEventListeners, this];
        }

        public void OnPostUpdate(PostUpdateEvent @event)
        {
            // Capture SQL here ?
        }

        public bool OnPreUpdate(PreUpdateEvent @event)
        {
            // Capture SQL here ?
            return false;
        }

        public bool OnPreInsert(PreInsertEvent @event)
        {
            // Capture SQL here ?
            return false;
        }

        public void OnPostInsert(PostInsertEvent @event)
        {
            // Capture SQL here ?
        }

        #region Async methods
        public Task OnPostUpdateAsync(PostUpdateEvent @event, CancellationToken cancellationToken)
        {
            OnPostUpdate(@event);
            return Task.CompletedTask;
        }

        public Task<bool> OnPreUpdateAsync(PreUpdateEvent @event, CancellationToken cancellationToken)
        {
            return Task.FromResult(OnPreUpdate(@event));
        }

        public Task<bool> OnPreInsertAsync(PreInsertEvent @event, CancellationToken cancellationToken)
        {
            return Task.FromResult(OnPreInsert(@event));
        }

        public Task OnPostInsertAsync(PostInsertEvent @event, CancellationToken cancellationToken)
        {
            OnPostInsert(@event);
            return Task.CompletedTask;
        } 
        #endregion
    }

Ideally, we'd like to reproduce the delta mechanism proposed by SyncFramework (https://github.com/egarim/SyncFramework/tree/main).

Thanks in advance for your help!

1 Answer 1

0

keeping some form of context within your interceptor? If the order of the method calls is compatible and you set the context, it will be available when the OnPrepareStatement method is called.

Something like:

public class SqlLoggingInterceptor : EmptyInterceptor {
    private object _currentEntity;

    public override bool OnSave(object entity, 
                                object id, 
                                object[] state,
                                string[] propertyNames,
                                IType[] types)
    {
        _currentEntity = entity;

        return true;
    }

    public override SqlString OnPrepareStatement(SqlString sql)
    {
        // Use _currentEntity here

        return base.OnPrepareStatement(sql);
    }
    
}

If I've read the documentation correctly, interceptors are scoped per-session, so there's some potential issues with keeping track of the correct context, but it could be worth exploring.

The stumbling block may be that the OnPrepareStatement method is called before you can set the context.

2
  • 1
    Thank you for your answer, we will explore this solution. For now, we're trying to manage context between Interceptor and Listeners but it is quite challenging.
    – pp-mluthi
    Commented Jan 31 at 15:53
  • 1
    As expected, using Interceptor can easily capture simple queries and we can get an easy match of parameters using OnSave & OnDelete. But when queries are generated to update foreign keys or indexes, it is more complex to build the SQL queries. We also explored the source code of nHibernate and tried to extract the SQL query through Listeners but it seems far more complex than expected.
    – pp-mluthi
    Commented Jan 31 at 16:24

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.