The Fairway Technologies Blog

blog

Getting Your Hooks Into SharpRepository

In Blog, CSharp, Jeff Treuting, SharpRepository, software development No Comments

Overview

A little while back I noticed that a GitHub user had forked SharpRepository and added some simple hooks into it in order to automatically index entities into Lucene when they are added, updated or deleted through SharpRepository.  This was such a good idea that I knew we needed to get some proper hooks into the library itself.  We decided to use an Aspect Oriented Programming approach and allow developers to decorate their entities with attributes that would hook into the proper part of SharpRepository to give them the controls they need.

Some details on the hooks

Basically there are hooks before and after each CRUD operation and each query.  The before hooks allow a developer to stop the operation from occurring or alter the parameters used for the operation.  More details on that later.  Each method receives a Context that provides access to the main Repository instance itself as well as helpful properties that are specific to the operation.  Here are the hooks that are available:

General Hooks

OnInitialized

This runs when the repository is initialized and would allow you to set special settings for the underlying data store or the repository itself (maybe you wanted to change the caching options based on some rules you set).

OnError

Custom error handling would occur in here.  It will be called if something goes wrong.

CRUD Hooks

OnAddExecuting

This allows you to take some action before the entity is added to the data store, for example you could change a property before it is saved.  You can return false to stop the entity from being added.

OnAddExecuted

This runs after the item is added to the data store.  At this point you would have access to the primary key if it was generated by the data store (like SQL and an auto-increment primary key).

OnUpdateExecuting, OnUpdateExecuted, OnDeleteExecuting, OnDeleteExecuted

These are the same as the OnAdd but for updating and deleting.  Again you can return false in the -ing methods if you want to stop them from happening.

Query Hooks

OnGetExecuting

This fires before the Get command is run.  You have access to the key provided here like the previous events you can return false if you want the execution to stop and return a default value.

OnGetExecuted

As you might have guessed, this runs after the Get command has run.  In here you have access to the entity that was retrieved and can basically do what you want with it.  Maybe you want to translate the DateTime fields to a specific timezone or check a property and return NULL if it matches some criteria.

OnFindExecuting, OnFindExecuted

Very similar to the OnGet methods but the Context provides access to the query itself.  This allows the -ing method to alter the query before it is run if it needs to.

OnGetAllExecuting, OnGetAllExecuted, OnFindAllExecuting, OnFindAllExecuted

I'm guessing you can figure out what these do.

How to implement your own attribute

In order to get your own code into these hooks you must create an attribute that inherits from RepositoryActionBaseAttribute.  This class simply provides empty implementations of all the hooks so that you only need to override the methods that you want to deal with.  So inherit from RepositoryActionBaseAttribute and then start overriding methods, that's all there is to it.

Here is what the RepositoryActionBaseAttribute class looks like so you can see all the hooks and the parameters they receive:

    [AttributeUsage(AttributeTargets.Class, Inherited = true)]
public abstract class RepositoryActionBaseAttribute : Attribute
{
protected RepositoryActionBaseAttribute()
{
Enabled = true;
Order = 9999; // high number so if they don't set it, it happens last
}

public int Order { get; set; }
public bool Enabled { get; set; }

public virtual void OnInitialized(RepositoryActionContext context) where T : class
{
}

public virtual void OnError(RepositoryActionContext context, Exception ex) where T : class
{
}

public virtual bool OnAddExecuting(T entity, RepositoryActionContext context) where T : class
{
return true;
}

public virtual void OnAddExecuted(T entity, RepositoryActionContext context) where T : class
{
}

public virtual bool OnUpdateExecuting(T entity, RepositoryActionContext context) where T : class
{
return true;
}

public virtual void OnUpdateExecuted(T entity, RepositoryActionContext context) where T : class
{
}

public virtual bool OnDeleteExecuting(T entity, RepositoryActionContext context) where T : class
{
return true;
}

public virtual void OnDeleteExecuted(T entity, RepositoryActionContext context) where T : class
{
}

public virtual bool OnSaveExecuting(RepositoryActionContext context) where T : class
{
return true;
}

public virtual void OnSaveExecuted(RepositoryActionContext context) where T : class
{
}

/* Queries */
public virtual bool OnGetExecuting(RepositoryGetContext context) where T : class
{
return true;
}

public virtual void OnGetExecuted(RepositoryGetContext context) where T : class
{
}

public virtual bool OnGetAllExecuting(RepositoryQueryMultipleContext context) where T : class
{
return true;
}

public virtual void OnGetAllExecuted(RepositoryQueryMultipleContext context) where T : class
{
}

public virtual bool OnFindExecuting(RepositoryQuerySingleContext context) where T : class
{
return true;
}

public virtual void OnFindExecuted(RepositoryQuerySingleContext context) where T : class
{
}

public virtual bool OnFindAllExecuting(RepositoryQueryMultipleContext context) where T : class
{
return true;
}

public virtual void OnFindAllExecuted(RepositoryQueryMultipleContext context) where T : class
{
}
}

Logging

Since logging what is going on within SharpRepository seemed like a common cross-cutting concern that many users of SharpRepository would have, we decided to provide a Logging aspect.  It uses Common.Logging in order to allow you to log to basically any logging framework you currently use.  All you have to do is configure Common.Logging.

In order to implement logging within SharpRepository now all you have to do is add the SharpRepository.Logging NuGet package and then decorate your entity with the [RepositoryLogging] attribute like so:

[RepositoryLogging]
public class MyEntity
{
public string Title { get; set; }
}

By default, this will log using the Debug methods, so you can control whether you log the SharpRepository statements by setting the minimum log level in your specific logging framework.

In the logs you will see information on all the CRUD commands being run plus all queries, if they returned results and if the cache was used.   This information has proved useful to us while dogfooding it, but if we missed anything that might be helpful just let us know.

If you would like to add Logging to all Entity Framework entites see my post on Using a Common Base Class with Entity Framework.

What's Next?

My next posts will show you how I've used custom aspects to implement simple auditing and soft-deleting as cross-cutting concerns. 

 

New Call-to-action

Sign Up For Our Monthly Newsletter

New Call-to-action