6
votes

I'm looking for a way of adding an association between two entities and having a settable Id for the foreign key. I have searched through previous posts, but the closest I can find is a suggestion to .Load the association - which isn't what I'm hoping for. I know this can be done in Entity Framework with the .HasForeignKey binding, but I can't seem to find a way to do it in Fluent NHibernate.

Take the two example entities:

public class Ticket
{
    public virtual int Id { get; set; }
    public virtual string Title { get; set; }
    public virtual string ServiceId { get; set; }
    public virtual Service Service { get; set; }
}

public class Service
{
    public virtual string Id { get; set; }
}

I want to be able to create a new instance of Ticket and assign a Service to it using the following means (assume that the associated Service already exists in the table):

Ticket ticket = new Ticket() {
    Title = "Problem with MS Word",
    ServiceId = "Microsoft Word 2012"
};

What I don't want to do is the following:

Ticket ticket = new Ticket() {
    Title = "Problem with MS Word",
    Service = Session.Load<Service>("Microsoft Word 2012")
};

I do have valid reasons for this, and like I've said this can be done in Entity Framework, but I'm really stumped as to how to achieve the same thing in Fluent NHibernate. My mappings currently look like this:

public class TicketMapping : ClassMap<Ticket>
{
    public TicketMapping()
    {
        Id(m => m.Id);
        Map(m => m.Title).Column("Title");
        Map(m => m.ServiceId).Column("ServiceId");
        HasOne(m => m.Service).ForeignKey("ServiceId");

        Schema("dbo");
        Table("Tickets");
    }
}

public class ServiceMapping : ClassMap<Service>
{
    public ServiceMapping()
    {
        Id(m => m.Id);

        Schema("dbo");
        Table("Services");
    }
}

Any help always appreciated!


Just a quick edit for Jay - the reason I don't want to Session.Load my element is because I don't want my presentation layer (MVC 3) knowing anything about NHibernate - therefore I'm using a repository pattern and injecting a single repository into the controller. So for example, I'll have a TicketRepository which adheres to the following contract

public interface IRepository<T>
{
    T GetById(object id);
    void Create(T entity);
    void Update(T entity);
    void Delete(T entity);
}

I don't want to have to inject a ServiceRepository also just to get a reference to the Service for the Ticket.

2
Can you elaborate on the valid reasons for not wanting to use Session.Load? - Jay
@Jay, not sure of Terric's reason...but it can be ridiculous overhead at times to load an object just in order to be able to reference the key you already have before saving, IMHO. We had a strict SLA on a previous project where we had to do the same work-around on Entity Framework before it had FK support. - Kevin Nelson
@Jay have updated my post with reasons. Also, Kevin makes a good point, I don't want the extra overhead of loading another entity for this associated. - Paul Aldred-Bann
@Terric As I mentioned above, Session.Load does not load the entity. Beyond that, though, I would advise against trying to keep NHibernate out of your controllers. NHibernate is the abstraction, and you give up most of what is powerful if you try to hide it. See ayende.com/blog/4567/… - Jay
@KevinNelson It is a matter of debate whether one will actually save effort if switching the ORM. It seems you just take the pain up front and throughout development instead of when (IF!) actually making that change. Less debatable I think is whether having a repository-per-entity is a good practice -- it isn't. The repository should be per-aggregate-root, so you don't need to stitch together associations manually using several repositories. - Jay

2 Answers

3
votes

The way I see it you can't really avoid using Session.Load(id) when using NHibernate. As mentioned in the comments this will NOT hit the database, just create a proxy object with the id.

Some possible options:

  1. Inject a second generic repository (ServiceRepository) into the controller. I can't really see an issue with that, but for some reason you want to avoid it. You could add a LoadById method to the generic interface and implement that differently in each implementation for NH and EF (or others). In EF impl that method may work just like GetById, while in NH impl it calls Session.Load
  2. Implement a non-generic repository for the AR (aggregate root) which in this case would be Ticket. This could have specific methods to Load a Service as well as Ticket.
  3. Implement another abstraction on top of the two repositories and inject that into the controller instead of the two repositories in option 1. This could f.ex be a persistance ignorant UnitOfWork as outlined here: Persistance ignorant UoW, or some kind of Application Service that orchestrates the creation of Tickets, or a TicketFactory.

Of the 3 options, option 1 is probably the simplest, while 3 might provide better abstraction and more maintainability down the road.

0
votes

You could use a trick. To keep your code clean.

Like this is a issue of NH you must implement it solution in NH Repositories so, i resolve it like this.

before Add or Update

if (!string.IsNullOrEmpty(ServiceId) && Service == null)
{
   Service = new Service{ Id = ServiceId };
}

normal repository work...

I test it and work. Your architecture still clean of ORM decisions