NHibernate query by Transient instance results in "save the transient instance"-exception

508 Views Asked by At

I have some old code which is performing a query where a model can be transient. That is, a model with some fields populated from user input, which are then used as part of the query. It worked under NH 2.1.x, but is failing under the latest version.

The exception raised is "object references an unsaved transient instance - save the transient instance before flushing". This happens when NH attempts to perform a query using a non-persisted object as part of the query.

A simplified version to illustrate the problem.

abstract class BaseModel
   public virtual long Id { get; set; }

class Car : BaseModel
    public virtual Engine Engine { get;set; }

class Engine : BaseModel
    public virtual string Kind { get; set; }


public static IList<Car> GetByEngine(Engine eng) {
  ICriteria c = Session.CreateCriteria<Car>();
  c.Add(Expression.Eq("Engine", eng));
  return c.List<Car>(); // <--- Error occurs here
}

And calling code is equivalent to this:

    Engine obj = new Engine { Id = 42 }; // Transient instance
    var x = GetByEngine(obj);

What I expected to happen (Which appears to be the behaviour of the old NHibernate version), is that the Engine passed is used only for getting the Id. That is, generating SQl like select .... from Cars where Engine = 42

But with the new version NHibernate seems to check that the engine used in the Expression is actually persisted.

Is there a way to avoid having to load a persisted Engine before performing the query ?

3

There are 3 best solutions below

1
On BEST ANSWER

yes using Session.Load() which returns the object if already in the session or a lazyLoadingProxy if not present.

public static IList<Car> GetByEngine(Engine eng) {
    ICriteria c = Session.CreateCriteria<Car>();
    c.Add(Expression.Eq("Engine", Session.Load<Engine>(eng.Id)));
    return c.List<Car>();
}
0
On

You can use the Session.Load method, which exist for this kind of scenarios.
The Load method will return a Proxy to the Entity and won't hit the Data Base untill you access one of it's properties, (except the Primary key property which won't hit the DB at all).

Usage:

Engine obj = session.Load<Engine>(42);
var x = GetByEngine(obj);

check this article about Session.Get and Session.Load

1
On

I think you could do something like this:

public static IList<Car> GetByEngine(Engine eng) {
    ICriteria c = Session.CreateCriteria<Car>().CreateCriteria("Engine");
    c.Add(Expression.Eq("Id", eng.Id));
    return c.List<Car>();
}

Anyway... how it's possible that a car with that engine exists if you haven't saved it yet?