6

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.

Paul Aldred-Bann
  • 5,840
  • 4
  • 36
  • 55
  • 1
    Can you elaborate on the valid reasons for not wanting to use `Session.Load`? – Jay Aug 14 '12 at 15:45
  • 1
    @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 Aug 14 '12 at 16:20
  • @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 Aug 14 '12 at 16:51
  • @KevinNelson `Session.Load` *does not load an object*; that is why this is the method for referencing a foreign key with a known identifier. Only if you try to access properties of the proxied object would the database be hit. – Jay Aug 14 '12 at 17:43
  • 3
    @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 http://ayende.com/blog/4567/the-false-myth-of-encapsulating-data-access-in-the-dal – Jay Aug 14 '12 at 17:43
  • @Jay - ah, cool, didn't know that NHibernate uses a proxy or whatever to avoid loading the entity...probably one of the many reasons so many considered it way beyond EF when EF first came out. However, to Terric's point, if they switch from NHibernate to Entity Framework, they will most definitely not want their controllers littered with NHibernate...so using DI on the controller with an IRepository interface is a good practice. – Kevin Nelson Aug 14 '12 at 18:30
  • 2
    @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 Aug 14 '12 at 18:41
  • @Jay - That (IF!) is something I've paid the piper for. However, on some apps, even a smart-ui anti-pattern isn't a bad idea. I wasn't saying there's a right or wrong, but saying that Terric's pattern is a good/legitimate practice. – Kevin Nelson Aug 14 '12 at 19:16
  • Thanks for the debate guys, some very interesting points - especially the repository per-aggregate-root, something I've not considered. The main reason I'm trying to keep NHibernate out of the presentation layer, is because it might be getting shipped off to other teams who will plug their own DAL assembly in (which follows the contract and entity model I've already specified). This DAL could be EF, ADO.Net, NHibernate or anything else really. I do think I need to have another look at my repository strategy however as Jay says. – Paul Aldred-Bann Aug 15 '12 at 08:33

2 Answers2

3

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.

Trygve
  • 2,445
  • 1
  • 19
  • 23
  • Thanks, having a better understanding of `Session.Load` meant I'm not concerned about using it anymore. I did end up using entity framework instead and dropped NHibernate (mainly because I'm more comfortable with EF but wanted to see what NH had to offer). – Paul Aldred-Bann Sep 07 '12 at 10:20
  • I have been throwing around the idea of converting my foreign keys into ints instead of objects because I did not want to have to hit the DB for every linked object on every insert or update... until I came across this question and learned about Session.Load(). After comparing the nhibernate log results side-by-side for simple operations - wow! HUGE savings! Thanks! – Brett Mar 01 '13 at 18:37
0

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

RJardines
  • 850
  • 9
  • 18
  • suppose if Service class have multiple fields, in that case do we need to assign all the fields here? as I am facing same problem.. – jkyadav Aug 28 '15 at 08:15