3

I am using NHibernate to bulk-insert rows into my database. Because of the amount of data I am inserting, I am using IStatelessSession instead of ISession. The objects I am inserting use assigned identities (ie no hilo or guids generated -- the unique ids are assigned to the objects).

My problem is that I have an object (say Foo) which has a many-to-one reference to another object (say Bar). I insert all of the Bar objects first and that is no problem.

The problem comes when I want to insert the Foo objects. I know the unique identifier of each Bar object, but I don't want to have to retrieve each Bar object from the database in order to set the property on the Foo object before inserting it.

Now might be a good time to show a simple example:

public class Foo {
    // Unique identifier (assigned)
    public virtual int Id { get; set; }

    // Many-to-one reference to a Bar object
    public virtual Bar Bar { get; set; }
}

public class Bar {
    // Unique identifier (assigned)
    public virtual int Id { get; set; }
}

Let's say that I want to create a new Foo object with an Id of (say) 1234 that references a Bar object that has an Id of (say) 4567. I know that there is already a Bar object with this identifier because I have added all the Bar objects previously.

How do I go about adding the Foo object without having to retrieve the Bar object from the database again?

John Jeffery
  • 990
  • 5
  • 19

4 Answers4

4

Strange that sometimes if you take the time to formulate your question, you realise the answer shortly after.

What you do is create a dummy object that has the Id and nothing else set.

STEP 1: Insert the Bar Object

using (var session = SessionFactory.OpenStatelessSession())
{
    using (var tx = session.BeginTransaction())
    {
        var bar = new Bar
        {
            Id = 1234,
            // and populate all of the other
            // properties that you would put here
        };
        session.Insert(bar);
        tx.Commit();
    }
}

STEP 2: Insert the Foo Object with a dummy Bar object

using (var session = SessionFactory.OpenStatelessSession())
{
    using (var tx = session.BeginTransaction())
    {
        var foo = new Foo
        {
            Id = 4567,
            // dummy Bar object that has an Id and nothing else
            Bar = new Bar {Id = 1234}
        };
        session.Insert(foo);
        tx.Commit();
    }
}

But if anyone has a better way (for example, that does not require creating lots of dummy objects), I'd be grateful for advice.

John Jeffery
  • 990
  • 5
  • 19
2

Store the Bar objects in a Dictionary<int, Bar> after you insert them and assign the reference to the correct Bar object:

var foo = new Foo();
foo.Bar = bars[1234];
session.Save(foo); // there is no session.Insert method

You solution works too but requires a public setter for Bar.Id.

Jamie Ide
  • 48,427
  • 16
  • 81
  • 117
  • 1
    Thanks Jamie. What you are suggesting here would work. It is providing the same functionality as the session cache of a stateful session (`ISession`). For a small number of objects this would work fine. For a large number of objects this can use up all available memory. – John Jeffery Mar 21 '12 at 20:29
  • 1
    Your comment about a public setter is a good one. What I do in my code is have a constructor that accepts an Id parameter. The Id property has a protected setter for NHibernate to use, and the class also has a protected, default constructor for NHibernate to use. This makes the Id property immutable, but it means I have to create lots of objects which seem to get garbage collected quickly enough. – John Jeffery Mar 21 '12 at 20:32
  • If you're inserting enough data to worry about using up available memory then I would suggest an ETL solution instead of NHibernate. In fact, I would profile the app using an ISession and Load (flushing the ISession every 500 inserts or so) before going to IStatelessSession. – Jamie Ide Mar 21 '12 at 21:40
  • Yes, ETL is probably a more conventional solution for this sort of thing. My application is loading approx 2 million entities. My reason for using NHibernate in this situation is that it makes it database agnostic (which is something I need for this application). Performance is reasonable using the solution outlined above, but it would be slower than using an ETL solution (such as bulk-copy in MS SQL Server) – John Jeffery Mar 22 '12 at 01:38
  • 1
    As for profiling using the stateful session, that would be fine as long as I have the referenced objects in my session cache. If I am clearing out every 500 or so inserts, then I will find that the objects I need to refer to are not in the session cache, and I need a database hit to bring them back in. With a statefull session you cannot use the solution outlined above of creating a dummy object with only the Id set -- you need to have a persistent object and that means getting it back from the database. – John Jeffery Mar 22 '12 at 01:41
  • Given the number of rows, I think your solution is better and I upvoted it. – Jamie Ide Mar 22 '12 at 15:10
  • Cheers Jamie. Thanks for talking this through with me. – John Jeffery Mar 23 '12 at 04:27
0

You can use session.Get(id), if the session has got Bar's entityies, it return a proxy, and you'll create Foo object by proxy reference without any call to the DataBase.

Anton
  • 1,583
  • 12
  • 17
0

This does NOT make a trip to the the database and is a way to populate the foreign-key without having to load the entity.

var foo = new Foo
{
  Id = 4567,
  Bar = new Session.Get<Bar>(1234)
};

Ignore.

Rippo
  • 22,117
  • 14
  • 78
  • 117
  • 1
    Thanks Rippo. You would be correct about this in the case of a normal, stateful session (`ISession`) where the object with id 1234 is in the session cache. I am talking about a stateless session (`IStatelessSession`), which does not have a session cache. In this case the `Get` method will always go to the database. The stateful session has a `Load` method which does not go to the database because it creates a proxy, but the stateless session does not have this method. – John Jeffery Mar 21 '12 at 20:24
  • Ah I see sorry, however LOAD does not require the entity to be in level one cache (I think, I will test this) but you are right about the `IStatelessSession` – Rippo Mar 22 '12 at 06:16