Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NHibernate: How to inject dependency on an entity

NHibernate 3.2/Fluent NHibernate 1.3/StructureMap 2.6.3 -

Trying to follow DDD as an architectural strategy, I typically don't have dependencies on domain entities. However, I'm experimenting right now with adding more behavior to my domain entities so that they are not so anemic. Everything was going well until I hooked up NHibernate. I've got two issues:

  1. NH requires a parameterless constructor and I'd rather not have a ctor that shouldn't be used.
  2. When NH tries to instantiate my entity, it needs to resolve my dependencies but I haven't given NH anything with which it can do that.

I've been reading on the web, but most (if not all) of the examples I have found are outdated (or just old). Even though the NH camp probably doesn't approve of what I'm doing, I'm looking for the NH way to do this.

like image 721
Byron Sommardahl Avatar asked Nov 05 '11 21:11

Byron Sommardahl


2 Answers

The solution ended up an implementation of NHibernate's IInterceptor. It is actually a very simple implementation when you inherit from EmptyInterceptor and override JUST the Instantiate() and SetSession() methods. Here's my interceptor using StructureMap:

public class DependencyInjectionEntityInterceptor : EmptyInterceptor
{
    IContainer _container;
    ISession _session;

    public DependencyInjectionEntityInterceptor(IContainer container)
    {
        _container = container;            
    }

    public override void SetSession(ISession session)
    {
       _session = session;            
    }

    public override object Instantiate(string clazz, EntityMode entityMode, object id)
    {
        if (entityMode == EntityMode.Poco)
        {
            var type = Assembly.GetAssembly(typeof (SomeClass)).GetTypes().FirstOrDefault(x => x.FullName == clazz);
            var hasParameters = type.GetConstructors().Any(x => x.GetParameters().Any());
            if (type != null && hasParameters)
            {
                var instance = _container.GetInstance(type);

                var md = _session.SessionFactory.GetClassMetadata(clazz);
                md.SetIdentifier(instance, id, entityMode);
                return instance;
            }
        }
        return base.Instantiate(clazz, entityMode, id);
    }
}

Then, all you have to do is tell NHibernate to use your interceptor:

public FluentConfiguration GetFluentConfiguration(IContainer container)
{
    return Fluently.Configure()
        .Database(MsSqlConfiguration.MsSql2008
                  .ConnectionString(c => c.FromConnectionStringWithKey("Database"))
                      .ShowSql())
        .Mappings(m => 
            m.AutoMappings.Add(AutoMap.AssemblyOf<SomeClass>()))
        .ExposeConfiguration(x => 
            x.SetInterceptor(new DependencyInjectionEntityInterceptor(container)));                
}

When I was researching this, some suggested passing in the SessionFactory into the ctor of the interceptor class. Honestly, from a session management perspective, this approach would be better.

like image 85
Byron Sommardahl Avatar answered Nov 15 '22 11:11

Byron Sommardahl


If you need additional dependencies in your entities don't use constructor injection. Instead create an additional parameter in the entity method.

Now you will ask yourself how do you get the dependency. For this you can use CommandHandlers and Commands. The command handler takes the dependency within its constructor and calls the method of the entity. In the UI you create a command message and send it to a command processor which is responsible for calling the correct command handler.

I hope my explanation is comprehensible to you.

Domain:

public class Employee
{
    public int Id { get; set; }
    public string Name { get; set; }

    public void SendNotification(string message, INotifier notifier)
    {
        notifier.SendMessage(string.Format("Message for customer '{0}' ({1}): {2}", Name, Id, message));
    }
}

The INotifier infrastructure component is passed through the method and not the constructor!

Infrastructure:

public interface INotifier
{
    void SendMessage(string message);
}

class EmailNotifier : INotifier
{
    public void SendMessage(string message)
    {
        // SmtpClient...
    }
}

class SMSNotifier : INotifier
{
    public void SendMessage(string message)
    {
        // SMS ...
    }
}

Command and CommandHandler:

public class NotificationCommandHandler : ICommandHandler<NotificationCommand>
{
    private readonly INotifier _notifier;

    public NotificationCommandHandler(INotifier notifier)
    {
        _notifier = notifier;
    }

    public void Execute(NotificationCommand commandMessage)
    {
        commandMessage.Employee.SendNotification(commandMessage.Message, _notifier);
    }
}

public class NotificationCommand
{
    public string Message { get; set; }
    public Employee Employee { get; set; }
}

The CommandHandler gets the INotifier through constructor injection. So you do not need to use your IoC Container like a ServiceLocator.

Usage i.e. in the UI in a controller:

public class Controller
{
    private readonly IMessageProcessor _messageProcessor;

    public Controller(IMessageProcessor messageProcessor)
    {
        _messageProcessor = messageProcessor;
    }

    public void SendNotification (Employee employee, string message)
    {
        var sendMailCommand = new NotificationCommand
        {
            Employee = employee,
            Message = message
        };

        _messageProcessor.Process(sendMailCommand);
    }
}

If you have questions about the command processor have a look at the mvccontrib project or ask a separate question.

like image 26
Rookian Avatar answered Nov 15 '22 13:11

Rookian